Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toast #23

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import dev.latvian.kubejs.block.BlockStatePredicate;
import dev.latvian.kubejs.client.painter.Painter;
import dev.latvian.kubejs.client.painter.screen.*;
import dev.latvian.kubejs.client.toast.NotificationBuilder;
import dev.latvian.kubejs.entity.EntityJS;
import dev.latvian.kubejs.event.IEventHandler;
import dev.latvian.kubejs.fluid.FluidBuilder;
Expand Down Expand Up @@ -353,11 +354,10 @@ public void addBindings(BindingsEvent event) {
//matrix
event.add("Matrix3f", Matrix3f.class);
event.add("Matrix4f", Matrix4f.class);
//
event.add("BlockPos", BlockPos.class);

//block
event.add("BlockPos", BlockPos.class);
event.add("BlockProperties", BlockStateProperties.class);
event.add("Notification", NotificationBuilder.class);

KubeJS.PROXY.clientBindings(event);
}
Expand Down Expand Up @@ -464,6 +464,8 @@ public void addTypeWrappers(ScriptType type, TypeWrappers typeWrappers) {
typeWrappers.register(BlockTintFunction.class, BlockTintFunction::of);
typeWrappers.register(ItemTintFunction.class, ItemTintFunction::of);

typeWrappers.register(NotificationBuilder.class, NotificationBuilder::of);

//registry
for (val wrapperFactory : RegistryTypeWrapperFactory.getAll()) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package dev.latvian.kubejs.client.toast;

import dev.latvian.kubejs.bindings.TextWrapper;
import dev.latvian.kubejs.client.toast.icon.*;
import dev.latvian.kubejs.util.UtilsJS;
import dev.latvian.mods.rhino.BaseFunction;
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.NativeJavaObject;
import dev.latvian.mods.rhino.mod.util.color.Color;
import dev.latvian.mods.rhino.mod.util.color.SimpleColor;
import dev.latvian.mods.rhino.native_java.type.info.TypeInfo;
import dev.latvian.mods.rhino.util.HideFromJS;
import lombok.val;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;

import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

public class NotificationBuilder {
public static final Component[] NO_TEXT = new Component[0];
public static final Duration DEFAULT_DURATION = Duration.ofSeconds(5L);
public static final Color DEFAULT_BORDER_COLOR = new SimpleColor(0x472954);
public static final Color DEFAULT_BACKGROUND_COLOR = new SimpleColor(0x241335);

private static final int FLAG_ICON = 1;
private static final int FLAG_TEXT_SHADOW = FLAG_ICON << 1;
private static final int FLAG_DURATION = FLAG_TEXT_SHADOW << 1;

@HideFromJS
public static NotificationBuilder of(Context cx, Object object, TypeInfo target) {
if (object instanceof NotificationBuilder b) {
return b;
} else if (object instanceof Map<?, ?> map) {
return null; // FIXME
} else if (object instanceof BaseFunction func) {
val consumer = (Consumer<NotificationBuilder>) NativeJavaObject.createInterfaceAdapter(cx, Consumer.class, func);
return make(consumer);
}
return new NotificationBuilder(TextWrapper.componentOf(object));
}

public static NotificationBuilder ofText(Component title) {
return new NotificationBuilder(title);
}

public static NotificationBuilder ofTitles(Component title, Component subTitle) {
return new NotificationBuilder(title.copy().append("\n").append(subTitle));
}

public static NotificationBuilder make(Consumer<NotificationBuilder> consumer) {
val b = new NotificationBuilder();
consumer.accept(b);
return b;
}

public Duration duration;
public Component text;
public ToastIcon icon;
public int iconSize;
public Color outlineColor;
public Color borderColor;
public Color backgroundColor;
public boolean textShadow;

public NotificationBuilder(Component text) {
duration = DEFAULT_DURATION;
this.text = text;
iconSize = 16;
outlineColor = SimpleColor.BLACK;
borderColor = DEFAULT_BORDER_COLOR;
backgroundColor = DEFAULT_BACKGROUND_COLOR;
textShadow = true;
}

public NotificationBuilder() {
this(new TextComponent(""));
}

public NotificationBuilder(FriendlyByteBuf buf) {
int flags = buf.readVarInt();
text = buf.readComponent();

duration = ((flags & FLAG_DURATION) != 0)
? Duration.ofMillis(buf.readVarLong())
: DEFAULT_DURATION;

if ((flags & FLAG_ICON) != 0) {
icon = ToastIcon.read(buf);
iconSize = buf.readByte();
} else {
icon = null;
iconSize = 16;
}

outlineColor = UtilsJS.readColor(buf);
borderColor = UtilsJS.readColor(buf);
backgroundColor = UtilsJS.readColor(buf);
textShadow = (flags & FLAG_TEXT_SHADOW) != 0;
}

public void write(FriendlyByteBuf buf) {
int flags = 0;

if (icon != null) {
flags |= FLAG_ICON;
}

if (textShadow) {
flags |= FLAG_TEXT_SHADOW;
}

if (duration != DEFAULT_DURATION) {
flags |= FLAG_DURATION;
}

buf.writeVarInt(flags);
buf.writeComponent(text);

if (duration != DEFAULT_DURATION) {
buf.writeVarLong(duration.toMillis());
}

if (icon != null) {
icon.write(buf);
buf.writeByte(iconSize);
}

UtilsJS.writeColor(buf, outlineColor);
UtilsJS.writeColor(buf, borderColor);
UtilsJS.writeColor(buf, backgroundColor);
}

public void setTextureIcon(ResourceLocation textureLocation) {
this.icon = new TextureIcon(textureLocation);
}

public void setItemIcon(ItemStack stack) {
icon = new ItemIcon(stack);
}

public void setAtlasIcon(ResourceLocation atlas, ResourceLocation sprite) {
this.icon = new AtlasIcon(Optional.ofNullable(atlas), sprite);
}

public void setAtlasIcon(ResourceLocation sprite) {
setAtlasIcon(null, sprite);
}

@Environment(EnvType.CLIENT)
public void show() {
val mc = Minecraft.getInstance();
mc.getToasts().addToast(new NotificationToast(mc, this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package dev.latvian.kubejs.client.toast;

import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Matrix4f;
import dev.latvian.kubejs.bindings.TextWrapper;
import dev.latvian.kubejs.client.toast.icon.*;
import lombok.val;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.util.FastColor;
import net.minecraft.util.FormattedCharSequence;

import java.util.ArrayList;
import java.util.List;

public class NotificationToast implements Toast {

private final NotificationBuilder notification;

private final long duration;
private final ToastIcon icon;
private final List<FormattedCharSequence> text;
private int width, height;

private long lastChanged;
private boolean changed;

public NotificationToast(Minecraft mc, NotificationBuilder notification) {
this.notification = notification;
this.duration = notification.duration.toMillis();

this.icon = notification.icon;

this.text = new ArrayList<>(2);
this.width = 0;
this.height = 0;

if (!TextWrapper.isEmpty(notification.text)) {
this.text.addAll(mc.font.split(notification.text, 240));
}

for (val l : this.text) {
this.width = Math.max(this.width, mc.font.width(l));
}

this.width += 12;

if (this.icon != null) {
this.width += 24;
}

this.height = Math.max(this.text.size() * 10 + 12, 28);

if (this.text.isEmpty() && this.icon != null) {
this.width = 28;
this.height = 28;
}

//this.width = Math.max(160, 30 + Math.max(mc.font.width(component), component2 == null ? 0 : mc.font.width(component2));
}

@Override
public int width() {
return this.width;
}

@Override
public int height() {
return this.height;
}

private void drawRectangle(Matrix4f m, int x0, int y0, int x1, int y1, int r, int g, int b) {
val tesselator = Tesselator.getInstance();
val buf = tesselator.getBuilder();
buf.begin(7, DefaultVertexFormat.POSITION_COLOR);
buf.vertex(m, x0, y1, 0F).color(r, g, b, 255).endVertex();
buf.vertex(m, x1, y1, 0F).color(r, g, b, 255).endVertex();
buf.vertex(m, x1, y0, 0F).color(r, g, b, 255).endVertex();
buf.vertex(m, x0, y0, 0F).color(r, g, b, 255).endVertex();
tesselator.end();
}

@Override
public Toast.Visibility render(PoseStack poseStack, ToastComponent toastComponent, long l) {
if (this.changed) {
this.lastChanged = l;
this.changed = false;
}

val mc = toastComponent.getMinecraft();

poseStack.pushPose();
poseStack.translate(-2D, 2D, 0D);
val m = poseStack.last().pose();
val w = width();
val h = height();

val oc = notification.outlineColor.getRgbKJS();
val ocr = FastColor.ARGB32.red(oc);
val ocg = FastColor.ARGB32.green(oc);
val ocb = FastColor.ARGB32.blue(oc);

val bc = notification.borderColor.getRgbKJS();
val bcr = FastColor.ARGB32.red(bc);
val bcg = FastColor.ARGB32.green(bc);
val bcb = FastColor.ARGB32.blue(bc);

val bgc = notification.backgroundColor.getRgbKJS();
val bgcr = FastColor.ARGB32.red(bgc);
val bgcg = FastColor.ARGB32.green(bgc);
val bgcb = FastColor.ARGB32.blue(bgc);

RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
drawRectangle(m, 2, 0, w - 2, h, ocr, ocg, ocb);
drawRectangle(m, 0, 2, w, h - 2, ocr, ocg, ocb);
drawRectangle(m, 1, 1, w - 1, h - 1, ocr, ocg, ocb);
drawRectangle(m, 2, 1, w - 2, h - 1, bcr, bcg, bcb);
drawRectangle(m, 1, 2, w - 1, h - 2, bcr, bcg, bcb);
drawRectangle(m, 2, 2, w - 2, h - 2, bgcr, bgcg, bgcb);

if (icon != null) {
icon.draw(mc, poseStack, 14, h / 2, notification.iconSize);
}

val th = icon == null ? 6 : 26;
val tv = (h - text.size() * 10) / 2 + 1;

for (var i = 0; i < text.size(); i++) {
if (notification.textShadow) {
mc.font.drawShadow(poseStack, text.get(i), th, tv + i * 10, 0xFFFFFF);
} else {
mc.font.draw(poseStack, text.get(i), th, tv + i * 10, 0xFFFFFF);
}
}

poseStack.popPose();
return l - this.lastChanged < duration ? Toast.Visibility.SHOW : Toast.Visibility.HIDE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dev.latvian.kubejs.client.toast.icon;

import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import lombok.val;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.InventoryMenu;

import java.util.Optional;

/**
* @author ZZZank
*/
public record AtlasIcon(Optional<ResourceLocation> atlas, ResourceLocation sprite) implements ToastIcon {
public static final Codec<AtlasIcon> CODEC = RecordCodecBuilder.create(
instance -> instance.group(
ResourceLocation.CODEC.optionalFieldOf("altas").forGetter(AtlasIcon::atlas),
ResourceLocation.CODEC.fieldOf("sprite").forGetter(AtlasIcon::sprite)
).apply(instance, AtlasIcon::new)
);

@Override
public void draw(Minecraft mc, PoseStack poseStack, int x, int y, int size) {
val m = poseStack.last().pose();
val sprite = mc
.getTextureAtlas(this.atlas.orElse(InventoryMenu.BLOCK_ATLAS))
.apply(this.sprite);

val p0 = -size / 2;
val p1 = p0 + size;

val u0 = sprite.getU0();
val v0 = sprite.getV0();
val u1 = sprite.getU1();
val v1 = sprite.getV1();

val tesselator = Tesselator.getInstance();
val buf = tesselator.getBuilder();
buf.begin(7, DefaultVertexFormat.POSITION_TEX_COLOR);
buf.vertex(m, x + p0, y + p1, 0F).uv(u0, v1).color(255, 255, 255, 255).endVertex();
buf.vertex(m, x + p1, y + p1, 0F).uv(u1, v1).color(255, 255, 255, 255).endVertex();
buf.vertex(m, x + p1, y + p0, 0F).uv(u1, v0).color(255, 255, 255, 255).endVertex();
buf.vertex(m, x + p0, y + p0, 0F).uv(u0, v0).color(255, 255, 255, 255).endVertex();
tesselator.end();
}

@Override
public ToastIconType getType() {
return ToastIconRegistry.ATLAS;
}
}
Loading
Loading