diff --git a/.editorconfig b/.editorconfig index ec1745c9..73a4ec9b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,7 +15,7 @@ ij_visual_guides = ij_wrap_on_typing = false [*.java] -max_line_length = 150 +max_line_length = 140 ij_java_align_consecutive_assignments = false ij_java_align_consecutive_variable_declarations = false ij_java_align_group_field_declarations = false diff --git a/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java b/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java index 11e01932..7833d57d 100644 --- a/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java +++ b/src/main/java/com/cleanroommc/modularui/ModularUIConfig.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; import net.minecraftforge.common.config.Config; import net.minecraftforge.fml.relauncher.FMLLaunchHandler; @@ -20,7 +20,7 @@ public class ModularUIConfig { // Default direction @Config.Comment("Default tooltip position around the widget or its panel.") - public static Tooltip.Pos tooltipPos = Tooltip.Pos.VERTICAL; + public static RichTooltip.Pos tooltipPos = RichTooltip.Pos.VERTICAL; @Config.Comment("If true, widget outlines and widget information will be drawn.") public static boolean guiDebugMode = FMLLaunchHandler.isDeobfuscatedEnvironment(); diff --git a/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java b/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java index 61d303b9..ebb5e9fa 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java +++ b/src/main/java/com/cleanroommc/modularui/api/IMuiScreen.java @@ -4,6 +4,7 @@ import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.inventory.Slot; @@ -82,7 +83,7 @@ default boolean isGuiContainer() { } /** - * Hovering widget is handled by {@link com.cleanroommc.modularui.screen.viewport.GuiContext}. + * Hovering widget is handled by {@link ModularGuiContext}. * If it detects a slot, this method is called. Only affects {@link GuiContainer GuiContainers}. * * @param slot hovered slot diff --git a/src/main/java/com/cleanroommc/modularui/api/ITheme.java b/src/main/java/com/cleanroommc/modularui/api/ITheme.java index 70a3e150..f65bff8c 100644 --- a/src/main/java/com/cleanroommc/modularui/api/ITheme.java +++ b/src/main/java/com/cleanroommc/modularui/api/ITheme.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.api; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.theme.WidgetSlotTheme; import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; import com.cleanroommc.modularui.theme.WidgetTheme; @@ -64,5 +64,5 @@ default T getWidgetTheme(Class clazz, String id) { boolean getSmoothProgressBarOverride(); - Tooltip.Pos getTooltipPosOverride(); + RichTooltip.Pos getTooltipPosOverride(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/MCHelper.java b/src/main/java/com/cleanroommc/modularui/api/MCHelper.java index ef5a0ac8..e399a90f 100644 --- a/src/main/java/com/cleanroommc/modularui/api/MCHelper.java +++ b/src/main/java/com/cleanroommc/modularui/api/MCHelper.java @@ -2,7 +2,14 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.TextFormatting; + +import java.util.Collections; +import java.util.List; public class MCHelper { @@ -45,4 +52,23 @@ public static GuiScreen getCurrentScreen() { Minecraft mc = getMc(); return mc != null ? mc.currentScreen : null; } + + public static FontRenderer getFontRenderer() { + if (hasMc()) return getMc().fontRenderer; + return null; + } + + public static List getItemToolTip(ItemStack item) { + if (!hasMc()) return Collections.emptyList(); + if (getMc().currentScreen != null) return getMc().currentScreen.getItemToolTip(item); + List list = item.getTooltip(getPlayer(), getMc().gameSettings.advancedItemTooltips ? ITooltipFlag.TooltipFlags.ADVANCED : ITooltipFlag.TooltipFlags.NORMAL); + for (int i = 0; i < list.size(); ++i) { + if (i == 0) { + list.set(i, item.getItem().getForgeRarity(item).getColor() + list.get(i)); + } else { + list.set(i, TextFormatting.GRAY + list.get(i)); + } + } + return list; + } } diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java b/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java index 3dd42ca7..62812567 100644 --- a/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/IDrawable.java @@ -3,6 +3,7 @@ import com.cleanroommc.modularui.drawable.DrawableArray; import com.cleanroommc.modularui.drawable.Icon; import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widget.sizer.Area; @@ -171,7 +172,7 @@ public DrawableWidget(IDrawable drawable) { @SideOnly(Side.CLIENT) @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { this.drawable.drawAtZero(context, getArea(), widgetTheme); } } diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/IHoverable.java b/src/main/java/com/cleanroommc/modularui/api/drawable/IHoverable.java new file mode 100644 index 00000000..277290a2 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/IHoverable.java @@ -0,0 +1,25 @@ +package com.cleanroommc.modularui.api.drawable; + +import com.cleanroommc.modularui.screen.RichTooltip; + +import com.cleanroommc.modularui.widget.sizer.Area; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public interface IHoverable extends IIcon { + + /** + * Called every frame this hoverable is hovered inside a {@link com.cleanroommc.modularui.drawable.text.RichText}. + */ + default void onHover() {} + + @Nullable + default RichTooltip getTooltip() { + return null; + } + + void setRenderedAt(int x, int y); + + Area getRenderedArea(); +} diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/IIcon.java b/src/main/java/com/cleanroommc/modularui/api/drawable/IIcon.java index f4d5af0a..75760996 100644 --- a/src/main/java/com/cleanroommc/modularui/api/drawable/IIcon.java +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/IIcon.java @@ -1,5 +1,7 @@ package com.cleanroommc.modularui.api.drawable; +import com.cleanroommc.modularui.drawable.HoverableIcon; +import com.cleanroommc.modularui.drawable.InteractableIcon; import com.cleanroommc.modularui.widget.sizer.Box; /** @@ -22,5 +24,13 @@ public interface IIcon extends IDrawable { */ Box getMargin(); + default HoverableIcon asHoverable() { + return new HoverableIcon(this); + } + + default InteractableIcon asInteractable() { + return new InteractableIcon(this); + } + IIcon EMPTY_2PX = EMPTY.asIcon().height(2); } diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/IKey.java b/src/main/java/com/cleanroommc/modularui/api/drawable/IKey.java index 82164091..206a3beb 100644 --- a/src/main/java/com/cleanroommc/modularui/api/drawable/IKey.java +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/IKey.java @@ -1,18 +1,14 @@ package com.cleanroommc.modularui.api.drawable; -import com.cleanroommc.modularui.drawable.AnimatedText; -import com.cleanroommc.modularui.drawable.StyledText; -import com.cleanroommc.modularui.drawable.TextRenderer; -import com.cleanroommc.modularui.drawable.keys.CompoundKey; -import com.cleanroommc.modularui.drawable.keys.DynamicKey; -import com.cleanroommc.modularui.drawable.keys.LangKey; -import com.cleanroommc.modularui.drawable.keys.StringKey; +import com.cleanroommc.modularui.drawable.Icon; +import com.cleanroommc.modularui.drawable.text.*; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.JsonHelper; import com.cleanroommc.modularui.widgets.TextWidget; +import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -31,7 +27,9 @@ public interface IKey extends IDrawable { TextRenderer renderer = new TextRenderer(); - IKey EMPTY = new StringKey(""); + IKey EMPTY = str(""); + IKey LINE_FEED = str("\n"); + IKey SPACE = str(" "); /** * Creates a translated text. @@ -104,10 +102,18 @@ static IKey str(@NotNull String key) { * @param args arguments * @return text key */ - static IKey format(@NotNull String key, @Nullable Object... args) { + static IKey str(@NotNull String key, @Nullable Object... args) { return new StringKey(key, args); } + /** + * @deprecated renamed to str() + */ + @Deprecated + static IKey format(@NotNull String key, @Nullable Object... args) { + return str(key, args); + } + /** * Creates a composed text key. * @@ -129,10 +135,17 @@ static IKey dynamic(@NotNull Supplier getter) { } /** - * @return the current formatted string + * @return the current unformatted string */ String get(); + /** + * @return the current formatted string + */ + default String getFormatted() { + return get(); + } + @SideOnly(Side.CLIENT) @Override default void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { @@ -141,7 +154,7 @@ default void draw(GuiContext context, int x, int y, int width, int height, Widge renderer.setAlignment(Alignment.Center, width, height); renderer.setScale(1f); renderer.setPos(x, y); - renderer.draw(get()); + renderer.draw(getFormatted()); } @Override @@ -157,6 +170,13 @@ default AnimatedText withAnimation() { return new AnimatedText(this); } + IKey format(TextFormatting formatting); + + default IKey format(TextFormatting... formatting) { + for (TextFormatting tf : formatting) format(tf); + return this; + } + default StyledText alignment(Alignment alignment) { return withStyle().alignment(alignment); } @@ -173,6 +193,15 @@ default StyledText shadow(boolean shadow) { return withStyle().shadow(shadow); } + @Override + default Icon asIcon() { + return new Icon(this); + } + + default KeyIcon asTextIcon() { + return new KeyIcon(this); + } + @Override default void loadFromJson(JsonObject json) { if (json.has("color") || json.has("shadow") || json.has("align") || json.has("alignment") || json.has("scale")) { diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/IRichTextBuilder.java b/src/main/java/com/cleanroommc/modularui/api/drawable/IRichTextBuilder.java new file mode 100644 index 00000000..7179061f --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/IRichTextBuilder.java @@ -0,0 +1,89 @@ +package com.cleanroommc.modularui.api.drawable; + +import com.cleanroommc.modularui.drawable.text.Spacer; +import com.cleanroommc.modularui.utils.Alignment; + +public interface IRichTextBuilder> { + + T getThis(); + + IRichTextBuilder getRichText(); + + default T add(String s) { + getRichText().add(s); + return getThis(); + } + + default T add(IDrawable drawable) { + getRichText().add(drawable); + return getThis(); + } + + default T addLine(ITextLine line) { + getRichText().addLine(line); + return getThis(); + } + + default T addLine(IDrawable line) { + getRichText().add(line).newLine(); + return getThis(); + } + + default T newLine() { + return add(IKey.LINE_FEED); + } + + default T space() { + return add(IKey.SPACE); + } + + default T spaceLine(int pixelSpace) { + return addLine(Spacer.of(pixelSpace)); + } + + default T addElements(Iterable drawables) { + for (IDrawable drawable : drawables) { + getRichText().add(drawable); + } + return getThis(); + } + + default T addDrawableLines(Iterable drawables) { + for (IDrawable drawable : drawables) { + getRichText().add(drawable).newLine(); + } + return getThis(); + } + + default T addStringLines(Iterable drawables) { + for (String drawable : drawables) { + getRichText().add(drawable).newLine(); + } + return getThis(); + } + + default T clearText() { + getRichText().clearText(); + return getThis(); + } + + default T alignment(Alignment alignment) { + getRichText().alignment(alignment); + return getThis(); + } + + default T textColor(int color) { + getRichText().textColor(color); + return getThis(); + } + + default T scale(float scale) { + getRichText().scale(scale); + return getThis(); + } + + default T textShadow(boolean shadow) { + getRichText().textShadow(shadow); + return getThis(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/api/drawable/ITextLine.java b/src/main/java/com/cleanroommc/modularui/api/drawable/ITextLine.java new file mode 100644 index 00000000..550ecbef --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/drawable/ITextLine.java @@ -0,0 +1,17 @@ +package com.cleanroommc.modularui.api.drawable; + +import com.cleanroommc.modularui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.FontRenderer; + +public interface ITextLine { + + int getWidth(); + + int getHeight(FontRenderer fr); + + void draw(GuiContext context, FontRenderer fr, float x, float y, int color, boolean shadow); + + Object getHoveringElement(FontRenderer fr, int x, int y); + +} diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IViewport.java b/src/main/java/com/cleanroommc/modularui/api/layout/IViewport.java index 8d802f58..2afd1d3a 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IViewport.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IViewport.java @@ -1,7 +1,7 @@ package com.cleanroommc.modularui.api.layout; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.HoveredWidgetList; import java.util.function.Predicate; @@ -45,7 +45,7 @@ default void getSelfAt(IViewportStack stack, HoveredWidgetList widgets, int x, i * @param context gui context * @param transformed if transformation from this viewport is active */ - default void preDraw(GuiContext context, boolean transformed) {} + default void preDraw(ModularGuiContext context, boolean transformed) {} /** * Called during drawing twice (after children are drawn). Once with transformation of this viewport and once without @@ -53,7 +53,7 @@ default void preDraw(GuiContext context, boolean transformed) {} * @param context gui context * @param transformed if transformation from this viewport is active */ - default void postDraw(GuiContext context, boolean transformed) {} + default void postDraw(ModularGuiContext context, boolean transformed) {} static void getChildrenAt(IWidget parent, IViewportStack stack, HoveredWidgetList widgetList, int x, int y) { for (IWidget child : parent.getChildren()) { diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IDraggable.java b/src/main/java/com/cleanroommc/modularui/api/widget/IDraggable.java index a3356bb0..37249dff 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IDraggable.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IDraggable.java @@ -2,7 +2,7 @@ import com.cleanroommc.modularui.api.layout.IViewport; import com.cleanroommc.modularui.api.layout.IViewportStack; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.HoveredWidgetList; import com.cleanroommc.modularui.widget.sizer.Area; @@ -19,11 +19,11 @@ public interface IDraggable extends IViewport { /** * Gets called every frame after everything else is rendered. * Is only called when {@link #isMoving()} is true. - * Translate to the mouse pos and draw with {@link com.cleanroommc.modularui.widget.WidgetTree#drawTree(IWidget, GuiContext, boolean)}. + * Translate to the mouse pos and draw with {@link com.cleanroommc.modularui.widget.WidgetTree#drawTree(IWidget, ModularGuiContext, boolean)}. * * @param partialTicks difference from last from */ - void drawMovingState(GuiContext context, float partialTicks); + void drawMovingState(ModularGuiContext context, float partialTicks); /** * @param button the mouse button that's holding down diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IFocusedWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IFocusedWidget.java index 1ac6254c..98b1dae8 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IFocusedWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IFocusedWidget.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.api.widget; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; /** * An interface for {@link IWidget}'s, that makes them focusable. @@ -17,12 +17,12 @@ public interface IFocusedWidget { * * @param context gui context */ - void onFocus(GuiContext context); + void onFocus(ModularGuiContext context); /** * Called when the focus is removed from this widget * * @param context gui context */ - void onRemoveFocus(GuiContext context); + void onRemoveFocus(ModularGuiContext context); } diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java b/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java index 7e6098ba..e6a0067b 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java @@ -3,7 +3,7 @@ import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.widget.sizer.Area; /** @@ -49,7 +49,7 @@ default Area getParentArea() { * * @param context gui context */ - void draw(GuiContext context); + void draw(ModularGuiContext context); /** * Called when the mouse enters the area of this element diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/ITooltip.java b/src/main/java/com/cleanroommc/modularui/api/widget/ITooltip.java index f08b7040..fbaff656 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/ITooltip.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/ITooltip.java @@ -2,7 +2,9 @@ import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.api.drawable.ITextLine; +import com.cleanroommc.modularui.drawable.text.StyledText; +import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.utils.Alignment; import org.jetbrains.annotations.NotNull; @@ -15,19 +17,27 @@ * * @param widget type */ -public interface ITooltip { +public interface ITooltip> { /** * @return the current tooltip of this widget. Null if there is none */ @Nullable - Tooltip getTooltip(); + RichTooltip getTooltip(); /** * @return the current tooltip of this widget. Creates a new one if there is none */ @NotNull - Tooltip tooltip(); + RichTooltip tooltip(); + + /** + * Overwrites the current tooltip with the given one + * + * @param tooltip new tooltip + * @return this + */ + W tooltip(RichTooltip tooltip); /** * @return true if this widget has a tooltip @@ -51,7 +61,7 @@ default W getThis() { * @param tooltipConsumer tooltip function * @return this */ - default W tooltip(Consumer tooltipConsumer) { + default W tooltip(Consumer tooltipConsumer) { return tooltipStatic(tooltipConsumer); } @@ -62,7 +72,7 @@ default W tooltip(Consumer tooltipConsumer) { * @param tooltipConsumer tooltip function * @return this */ - default W tooltipStatic(Consumer tooltipConsumer) { + default W tooltipStatic(Consumer tooltipConsumer) { tooltipConsumer.accept(tooltip()); return getThis(); } @@ -74,7 +84,7 @@ default W tooltipStatic(Consumer tooltipConsumer) { * @param tooltipBuilder tooltip function * @return this */ - default W tooltipBuilder(Consumer tooltipBuilder) { + default W tooltipBuilder(Consumer tooltipBuilder) { return tooltipDynamic(tooltipBuilder); } @@ -85,7 +95,7 @@ default W tooltipBuilder(Consumer tooltipBuilder) { * @param tooltipBuilder tooltip function * @return this */ - default W tooltipDynamic(Consumer tooltipBuilder) { + default W tooltipDynamic(Consumer tooltipBuilder) { tooltip().tooltipBuilder(tooltipBuilder); return getThis(); } @@ -96,7 +106,7 @@ default W tooltipDynamic(Consumer tooltipBuilder) { * @param pos tooltip pos * @return this */ - default W tooltipPos(Tooltip.Pos pos) { + default W tooltipPos(RichTooltip.Pos pos) { tooltip().pos(pos); return getThis(); } @@ -126,7 +136,7 @@ default W tooltipAlignment(Alignment alignment) { /** * Sets if the tooltip text should have shadow enabled by default. - * Can be overridden with {@link com.cleanroommc.modularui.drawable.StyledText} lines. + * Can be overridden with {@link StyledText} lines. * * @param textShadow true if text should have a shadow * @return this @@ -189,7 +199,7 @@ default W tooltipAutoUpdate(boolean update) { * @return this */ default W tooltipHasTitleMargin(boolean hasTitleMargin) { - tooltip().setHasTitleMargin(hasTitleMargin); + //tooltip().setHasTitleMargin(hasTitleMargin); return getThis(); } @@ -200,7 +210,22 @@ default W tooltipHasTitleMargin(boolean hasTitleMargin) { * @return this */ default W tooltipLinePadding(int linePadding) { - tooltip().setLinePadding(linePadding); + //tooltip().setLinePadding(linePadding); + return getThis(); + } + + default W addTooltipElement(String s) { + tooltip().add(s); + return getThis(); + } + + default W addTooltipElement(IDrawable drawable) { + tooltip().add(drawable); + return getThis(); + } + + default W addTooltipLine(ITextLine line) { + tooltip().addLine(line); return getThis(); } @@ -211,7 +236,7 @@ default W tooltipLinePadding(int linePadding) { * @return this */ default W addTooltipLine(IDrawable drawable) { - tooltip().addLine(drawable); + tooltip().add(drawable).newLine(); return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java index b36880d1..d48bb30f 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -5,7 +5,7 @@ import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Flex; @@ -48,30 +48,30 @@ public interface IWidget extends IGuiElement { * @param context gui context * @param widgetTheme widget theme of this widget */ - void drawBackground(GuiContext context, WidgetTheme widgetTheme); + void drawBackground(ModularGuiContext context, WidgetTheme widgetTheme); /** * Draws additional stuff in this widget. * x = 0 and y = 0 is now in the top left corner of this widget. - * Do NOT override this method, it is never called. Use {@link #draw(GuiContext, WidgetTheme)} instead. + * Do NOT override this method, it is never called. Use {@link #draw(ModularGuiContext, WidgetTheme)} instead. * * @param context gui context */ @ApiStatus.NonExtendable @Deprecated @Override - default void draw(GuiContext context) { + default void draw(ModularGuiContext context) { draw(context, getWidgetTheme(context.getTheme())); } /** - * Draws extra elements of this widget. Called after {@link #drawBackground(GuiContext, WidgetTheme)} and before - * {@link #drawOverlay(GuiContext, WidgetTheme)} + * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetTheme)} and before + * {@link #drawOverlay(ModularGuiContext, WidgetTheme)} * * @param context gui context * @param widgetTheme widget theme */ - void draw(GuiContext context, WidgetTheme widgetTheme); + void draw(ModularGuiContext context, WidgetTheme widgetTheme); /** * Draws the overlay of this theme. @@ -79,7 +79,7 @@ default void draw(GuiContext context) { * @param context gui context * @param widgetTheme widget theme */ - void drawOverlay(GuiContext context, WidgetTheme widgetTheme); + void drawOverlay(ModularGuiContext context, WidgetTheme widgetTheme); /** * Draws foreground elements of this widget. For example tooltips. @@ -87,7 +87,7 @@ default void draw(GuiContext context) { * * @param context gui context */ - void drawForeground(GuiContext context); + void drawForeground(ModularGuiContext context); default void transform(IViewportStack stack) { stack.translate(getArea().rx, getArea().ry); @@ -190,7 +190,7 @@ default boolean hasParent() { /** * @return the context the current screen */ - GuiContext getContext(); + ModularGuiContext getContext(); /** * @return flex of this widget. Creates a new one if it doesn't already have one. diff --git a/src/main/java/com/cleanroommc/modularui/config/Value.java b/src/main/java/com/cleanroommc/modularui/config/Value.java index 03c1e9ba..a61b56bb 100644 --- a/src/main/java/com/cleanroommc/modularui/config/Value.java +++ b/src/main/java/com/cleanroommc/modularui/config/Value.java @@ -1,7 +1,7 @@ package com.cleanroommc.modularui.config; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import net.minecraft.network.PacketBuffer; @@ -22,7 +22,7 @@ public Value(String key) { } @Nullable - public IWidget buildGuiConfig(GuiContext context) { + public IWidget buildGuiConfig(ModularGuiContext context) { return null; } diff --git a/src/main/java/com/cleanroommc/modularui/core/mixin/FontRendererAccessor.java b/src/main/java/com/cleanroommc/modularui/core/mixin/FontRendererAccessor.java new file mode 100644 index 00000000..2f0e0a29 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/mixin/FontRendererAccessor.java @@ -0,0 +1,12 @@ +package com.cleanroommc.modularui.core.mixin; + +import net.minecraft.client.gui.FontRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(FontRenderer.class) +public interface FontRendererAccessor { + + @Invoker + int invokeSizeStringToWidth(String str, int wrapWidth); +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/DelegateIcon.java b/src/main/java/com/cleanroommc/modularui/drawable/DelegateIcon.java new file mode 100644 index 00000000..ee65df53 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/DelegateIcon.java @@ -0,0 +1,56 @@ +package com.cleanroommc.modularui.drawable; + +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widget.sizer.Box; + +public class DelegateIcon implements IIcon { + + private IIcon icon; + + public DelegateIcon(IIcon icon) { + this.icon = icon; + } + + @Override + public int getWidth() { + return this.icon.getWidth(); + } + + @Override + public int getHeight() { + return this.icon.getHeight(); + } + + @Override + public Box getMargin() { + return this.icon.getMargin(); + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + this.icon.draw(context, x, y, width, height, widgetTheme); + } + + public IIcon getDelegate() { + return icon; + } + + public IIcon findRootDelegate() { + IIcon icon = this; + while (icon instanceof DelegateIcon di) { + icon = di.getDelegate(); + } + return icon; + } + + protected void setDelegate(IIcon icon) { + this.icon = icon; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.icon + ")"; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java b/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java index 61492c65..8df30530 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/GuiDraw.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.drawable; +import com.cleanroommc.modularui.drawable.text.TextRenderer; import com.cleanroommc.modularui.utils.Color; import net.minecraft.client.Minecraft; @@ -341,10 +342,11 @@ public static void drawItem(ItemStack item, int x, int y, float width, float hei GlStateManager.pushMatrix(); RenderHelper.enableGUIStandardItemLighting(); GlStateManager.enableDepth(); + GlStateManager.translate(x, y, 0); GlStateManager.scale(width / 16f, height / 16f, 1); RenderItem renderItem = Minecraft.getMinecraft().getRenderItem(); renderItem.zLevel = 200; - renderItem.renderItemAndEffectIntoGUI(Minecraft.getMinecraft().player, item, x, y); + renderItem.renderItemAndEffectIntoGUI(Minecraft.getMinecraft().player, item, 0, 0); renderItem.zLevel = 0; GlStateManager.disableDepth(); RenderHelper.enableStandardItemLighting(); diff --git a/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java b/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java new file mode 100644 index 00000000..dcb88549 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/HoverableIcon.java @@ -0,0 +1,53 @@ +package com.cleanroommc.modularui.drawable; + +import com.cleanroommc.modularui.api.drawable.IHoverable; +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.widget.ITooltip; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widget.sizer.Box; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class HoverableIcon extends DelegateIcon implements IHoverable, ITooltip { + + private final Area area = new Area(); + private RichTooltip tooltip; + + public HoverableIcon(IIcon icon) { + super(icon); + setRenderedAt(0, 0); + } + + @Override + @Nullable + public RichTooltip getTooltip() { + return tooltip; + } + + @Override + public void setRenderedAt(int x, int y) { + this.area.set(x, y, getWidth(), getHeight()); + } + + @Override + public Area getRenderedArea() { + this.area.setSize(getWidth(), getHeight()); + return this.area; + } + + @Override + public @NotNull RichTooltip tooltip() { + if (this.tooltip == null) this.tooltip = new RichTooltip(area -> area.set(getRenderedArea())); + return tooltip; + } + + @Override + public HoverableIcon tooltip(RichTooltip tooltip) { + this.tooltip = tooltip; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/Icon.java b/src/main/java/com/cleanroommc/modularui/drawable/Icon.java index 609ed0f6..0034013f 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/Icon.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/Icon.java @@ -158,4 +158,9 @@ public void loadFromJson(JsonObject json) { public static Icon ofJson(JsonObject json) { return JsonHelper.deserialize(json, IDrawable.class, IDrawable.EMPTY, "drawable", "icon").asIcon(); } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.drawable.getClass().getSimpleName() + ")"; + } } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/IconRenderer.java b/src/main/java/com/cleanroommc/modularui/drawable/IconRenderer.java index 1e17e0ea..309469cf 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/IconRenderer.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/IconRenderer.java @@ -3,6 +3,9 @@ import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.drawable.IIcon; import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.text.StyledText; +import com.cleanroommc.modularui.drawable.text.TextIcon; +import com.cleanroommc.modularui.drawable.text.TextRenderer; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; diff --git a/src/main/java/com/cleanroommc/modularui/drawable/InteractableIcon.java b/src/main/java/com/cleanroommc/modularui/drawable/InteractableIcon.java new file mode 100644 index 00000000..f658af28 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/InteractableIcon.java @@ -0,0 +1,115 @@ +package com.cleanroommc.modularui.drawable; + +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.widget.IGuiAction; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.screen.ModularScreen; + +import org.jetbrains.annotations.NotNull; + +public class InteractableIcon extends DelegateIcon implements Interactable { + + private IGuiAction.MousePressed mousePressed; + private IGuiAction.MouseReleased mouseReleased; + private IGuiAction.MousePressed mouseTapped; + private IGuiAction.MouseScroll mouseScroll; + private IGuiAction.KeyPressed keyPressed; + private IGuiAction.KeyReleased keyReleased; + private IGuiAction.KeyPressed keyTapped; + + public InteractableIcon(IIcon icon) { + super(icon); + } + + public void playClickSound() { + //if (this.playClickSound) { + Interactable.playButtonClickSound(); + //} + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + if (this.mousePressed != null && this.mousePressed.press(mouseButton)) { + playClickSound(); + return Result.SUCCESS; + } + return Result.ACCEPT; + } + + @Override + public boolean onMouseRelease(int mouseButton) { + return this.mouseReleased != null && this.mouseReleased.release(mouseButton); + } + + @NotNull + @Override + public Result onMouseTapped(int mouseButton) { + if (this.mouseTapped != null && this.mouseTapped.press(mouseButton)) { + playClickSound(); + return Result.SUCCESS; + } + return Result.IGNORE; + } + + @Override + public @NotNull Result onKeyPressed(char typedChar, int keyCode) { + if (this.keyPressed != null && this.keyPressed.press(typedChar, keyCode)) { + return Result.SUCCESS; + } + return Result.ACCEPT; + } + + @Override + public boolean onKeyRelease(char typedChar, int keyCode) { + return this.keyReleased != null && this.keyReleased.release(typedChar, keyCode); + } + + @NotNull + @Override + public Result onKeyTapped(char typedChar, int keyCode) { + if (this.keyTapped != null && this.keyTapped.press(typedChar, keyCode)) { + return Result.SUCCESS; + } + return Result.IGNORE; + } + + @Override + public boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) { + return this.mouseScroll != null && this.mouseScroll.scroll(scrollDirection, amount); + } + + public InteractableIcon onMousePressed(IGuiAction.MousePressed mousePressed) { + this.mousePressed = mousePressed; + return this; + } + + public InteractableIcon onMouseReleased(IGuiAction.MouseReleased mouseReleased) { + this.mouseReleased = mouseReleased; + return this; + } + + public InteractableIcon onMouseTapped(IGuiAction.MousePressed mouseTapped) { + this.mouseTapped = mouseTapped; + return this; + } + + public InteractableIcon onMouseScrolled(IGuiAction.MouseScroll mouseScroll) { + this.mouseScroll = mouseScroll; + return this; + } + + public InteractableIcon onKeyPressed(IGuiAction.KeyPressed keyPressed) { + this.keyPressed = keyPressed; + return this; + } + + public InteractableIcon onKeyReleased(IGuiAction.KeyReleased keyReleased) { + this.keyReleased = keyReleased; + return this; + } + + public InteractableIcon onKeyTapped(IGuiAction.KeyPressed keyTapped) { + this.keyTapped = keyTapped; + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/ItemDrawable.java b/src/main/java/com/cleanroommc/modularui/drawable/ItemDrawable.java index 9abbb867..ebedfadc 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/ItemDrawable.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/ItemDrawable.java @@ -7,10 +7,6 @@ import com.cleanroommc.modularui.utils.JsonHelper; import com.cleanroommc.modularui.widget.Widget; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.RenderHelper; -import net.minecraft.client.renderer.RenderItem; import net.minecraft.item.ItemStack; import net.minecraft.nbt.JsonToNBT; import net.minecraft.nbt.NBTException; diff --git a/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java b/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java index 30a59942..0984b72e 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/Rectangle.java @@ -1,6 +1,5 @@ package com.cleanroommc.modularui.drawable; -import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; diff --git a/src/main/java/com/cleanroommc/modularui/drawable/Stencil.java b/src/main/java/com/cleanroommc/modularui/drawable/Stencil.java index e44abec8..23f6c386 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/Stencil.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/Stencil.java @@ -13,6 +13,8 @@ import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; +import java.awt.Rectangle; + public class Stencil { // Stores a stack of areas that are transformed, so it represents the actual area @@ -37,15 +39,15 @@ public static void reset() { GL11.glStencilMask(0x00); } - public static void apply(Area area, @Nullable GuiContext context) { + public static void apply(Rectangle area, @Nullable GuiContext context) { apply(area.x, area.y, area.width, area.height, context); } - public static void applyAtZero(Area area, @Nullable GuiContext context) { + public static void applyAtZero(Rectangle area, @Nullable GuiContext context) { apply(0, 0, area.width, area.height, context); } - public static void applyTransformed(Area area) { + public static void applyTransformed(Rectangle area) { applyTransformed(area.x, area.y, area.width, area.height); } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/AnimatedText.java b/src/main/java/com/cleanroommc/modularui/drawable/text/AnimatedText.java similarity index 94% rename from src/main/java/com/cleanroommc/modularui/drawable/AnimatedText.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/AnimatedText.java index 7e09ad7b..96d19ec3 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/AnimatedText.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/AnimatedText.java @@ -1,4 +1,4 @@ -package com.cleanroommc.modularui.drawable; +package com.cleanroommc.modularui.drawable.text; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.screen.viewport.GuiContext; @@ -6,6 +6,7 @@ import com.cleanroommc.modularui.utils.Alignment; import net.minecraft.client.Minecraft; +import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -99,6 +100,11 @@ public AnimatedText forward(boolean forward) { return this; } + @Override + public AnimatedText format(TextFormatting formatting) { + return (AnimatedText) super.format(formatting); + } + @Override public AnimatedText alignment(Alignment alignment) { return (AnimatedText) super.alignment(alignment); diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/BaseKey.java b/src/main/java/com/cleanroommc/modularui/drawable/text/BaseKey.java new file mode 100644 index 00000000..e3d3fc7a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/BaseKey.java @@ -0,0 +1,51 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.drawable.IKey; + +import net.minecraft.util.text.TextFormatting; + +import org.apache.commons.lang3.NotImplementedException; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseKey implements IKey { + + private TextFormatting[] formatting; + + @Override + public String getFormatted() { + if (this.formatting == null) return get(); + if (FontRenderHelper.isReset(this.formatting)) return TextFormatting.RESET + get(); + return FontRenderHelper.getFormatting(this.formatting, new StringBuilder()).append(get()).append(TextFormatting.RESET).toString(); + } + + @Override + public BaseKey format(TextFormatting formatting) { + if (this.formatting == null) { + this.formatting = FontRenderHelper.createFormattingState(); + } + FontRenderHelper.addAfter(this.formatting, formatting); + return this; + } + + @Nullable + public TextFormatting[] getFormatting() { + return formatting; + } + + @Override + public String toString() { + return getFormatted(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof IKey key)) return false; + return getFormatted().equals(key.getFormatted()); + } + + @Override + public int hashCode() { + throw new NotImplementedException("Implement hashCode() in subclasses"); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/ComposedLine.java b/src/main/java/com/cleanroommc/modularui/drawable/text/ComposedLine.java new file mode 100644 index 00000000..faf5f99d --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/ComposedLine.java @@ -0,0 +1,88 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.drawable.IHoverable; +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.drawable.ITextLine; +import com.cleanroommc.modularui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.FontRenderer; + +import java.util.List; + +public class ComposedLine implements ITextLine { + + private final List elements; + private final int width; + private final int height; + + private float lastX, lastY; + + public ComposedLine(List elements, int width, int height) { + this.elements = elements; + this.width = width; + this.height = height; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight(FontRenderer fr) { + return height == fr.FONT_HEIGHT ? height : height + 1; + } + + @Override + public void draw(GuiContext context, FontRenderer fr, float x, float y, int color, boolean shadow) { + this.lastX = x; + this.lastY = y; + for (Object o : this.elements) { + if (o instanceof String s) { + float drawY = getHeight(fr) / 2f - fr.FONT_HEIGHT / 2f; + fr.drawString(s, x, (int) (y + drawY), color, shadow); + x += fr.getStringWidth(s); + } else if (o instanceof IIcon icon) { + float drawY = getHeight(fr) / 2f - icon.getHeight() / 2f; + icon.draw(context, (int) x, (int) (y + drawY), icon.getWidth(), icon.getHeight(), IThemeApi.get().getDefaultTheme().getFallback()); + if (icon instanceof IHoverable hoverable) { + hoverable.setRenderedAt((int) x, (int) (y + drawY)); + } + x += icon.getWidth(); + } + } + } + + @Override + public Object getHoveringElement(FontRenderer fr, int x, int y) { + int h0 = getHeight(fr); + if (y < lastY || y > lastY + h0) return null; // is not hovering vertically + if (x < lastX || x > lastX + getWidth()) return Boolean.FALSE; // is not hovering horizontally + float x0 = x - this.lastX; // origin to 0 + float x1 = 0; + float y0 = y - this.lastY; // origin to 0 + for (Object o : this.elements) { + float w, h; + if (o instanceof String s) { + w = fr.getStringWidth(s); + h = fr.FONT_HEIGHT; + } else if (o instanceof IIcon icon) { + w = icon.getWidth(); + h = icon.getWidth(); + } else continue; + if (x0 > x1 && x0 < x1 + w) { + // is inside horizontally + if (h < h0) { + // is smaller than line height + int lower = (int) (h0 / 2f - h / 2); + int upper = (int) (h0 / 2f + h / 2 - 1f); + if (y0 < lower || y0 > upper) return Boolean.FALSE; // is outside vertically + } + return o; // found hovering + } + x1 += w; // move to next element + } + return null; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/keys/CompoundKey.java b/src/main/java/com/cleanroommc/modularui/drawable/text/CompoundKey.java similarity index 66% rename from src/main/java/com/cleanroommc/modularui/drawable/keys/CompoundKey.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/CompoundKey.java index a3e27e42..6b8fae1e 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/keys/CompoundKey.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/CompoundKey.java @@ -1,9 +1,9 @@ -package com.cleanroommc.modularui.drawable.keys; +package com.cleanroommc.modularui.drawable.text; import com.cleanroommc.modularui.ClientEventHandler; import com.cleanroommc.modularui.api.drawable.IKey; -public class CompoundKey implements IKey { +public class CompoundKey extends BaseKey { private static final IKey[] EMPTY = new IKey[0]; @@ -31,18 +31,4 @@ public String get() { public IKey[] getKeys() { return this.keys; } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj instanceof CompoundKey compoundKey) { - return this.get().equals(compoundKey.get()); - } - return false; - } - - @Override - public String toString() { - return this.get(); - } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/drawable/keys/DynamicKey.java b/src/main/java/com/cleanroommc/modularui/drawable/text/DynamicKey.java similarity index 75% rename from src/main/java/com/cleanroommc/modularui/drawable/keys/DynamicKey.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/DynamicKey.java index ea4a649c..84407330 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/keys/DynamicKey.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/DynamicKey.java @@ -1,10 +1,8 @@ -package com.cleanroommc.modularui.drawable.keys; - -import com.cleanroommc.modularui.api.drawable.IKey; +package com.cleanroommc.modularui.drawable.text; import java.util.function.Supplier; -public class DynamicKey implements IKey { +public class DynamicKey extends BaseKey { private final Supplier supplier; diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/FontRenderHelper.java b/src/main/java/com/cleanroommc/modularui/drawable/text/FontRenderHelper.java new file mode 100644 index 00000000..e6eceb8d --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/FontRenderHelper.java @@ -0,0 +1,117 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.MCHelper; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.util.text.TextFormatting; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; + +public class FontRenderHelper { + + private static final int min = '0', max = 'r'; // min = 48, max = 114 + // array to access text formatting by character fast + private static final TextFormatting[] formattingMap = new TextFormatting[max - min + 1]; + + static { + for (TextFormatting formatting : TextFormatting.values()) { + char c = formatting.toString().charAt(1); + formattingMap[c - min] = formatting; + if (Character.isLetter(c)) { + formattingMap[Character.toUpperCase(c) - min] = formatting; + } + } + } + + /** + * Returns the formatting for a character with a fast array lookup. + * + * @param c formatting character + * @return formatting for character or null + */ + @Nullable + public static TextFormatting getForCharacter(char c) { + if (c < min || c > max) return null; + return formattingMap[c - min]; + } + + // a formatting state keeps track of a color format and each of the fancy style options + // 0: color, 1 - 5: fancy style (random, bolt, italic, underline, strikethrough), 6: reset + public static TextFormatting[] createFormattingState() { + return new TextFormatting[7]; + } + + public static void addAfter(TextFormatting[] state, TextFormatting formatting) { + if (formatting == TextFormatting.RESET) { + Arrays.fill(state, null); + state[6] = formatting; + return; + } + // remove reset + state[6] = null; + if (formatting.isFancyStyling()) { + state[formatting.ordinal() - 15] = formatting; + return; + } + // color + state[0] = formatting; + } + + public static void parseFormattingState(TextFormatting[] state, String text) { + int i = -2; + while ((i = text.indexOf(167, i + 2)) >= 0 && i < text.length() - 1) { + TextFormatting formatting = getForCharacter(text.charAt(i + 1)); + if (formatting != null) addAfter(state, formatting); + } + } + + public static String getFormatting(TextFormatting[] state) { + if (isReset(state)) return TextFormatting.RESET.toString(); + StringBuilder builder = getFormatting(state, new StringBuilder()); + return builder.length() == 0 ? StringUtils.EMPTY : builder.toString(); + } + + public static StringBuilder getFormatting(TextFormatting[] state, StringBuilder builder) { + for (int i = 0, n = 6; i < n; i++) { + TextFormatting formatting = state[i]; + if (formatting != null) builder.append(formatting); + } + return builder; + } + + public static boolean isReset(TextFormatting[] state) { + return state[6] != null; + } + + public static int getDefaultTextHeight() { + FontRenderer fr = MCHelper.getFontRenderer(); + return fr != null ? fr.FONT_HEIGHT : 9; + } + + /** + * Calculates how many formatting characters there are at the given position of the string. + * + * @param s string + * @param start starting index + * @return amount of formatting characters at index + */ + public static int getFormatLength(String s, int start) { + int i = Math.max(0, start); + int l = 0; + for (; i < s.length(); i++) { + char c = s.charAt(i); + if (c == 167) { + if (i + 1 >= s.length()) return l; + if (getForCharacter(c) == null) return l; + l += 2; + i++; + } else { + return l; + } + } + return l; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/KeyIcon.java b/src/main/java/com/cleanroommc/modularui/drawable/text/KeyIcon.java new file mode 100644 index 00000000..17407da8 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/KeyIcon.java @@ -0,0 +1,97 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.MCHelper; +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widget.sizer.Box; + +import net.minecraft.client.gui.FontRenderer; + +/** + * An icon which represents a {@link IKey} object. + * Note: This class assumes the string will be a single line! + */ +public class KeyIcon implements IIcon { + + private final IKey key; + private FontRenderer overrideFontRenderer; + private final Box margin = new Box(); + + public KeyIcon(IKey key) { + this.key = key; + } + + public FontRenderer getFontRenderer() { + return this.overrideFontRenderer != null ? this.overrideFontRenderer : MCHelper.getFontRenderer(); + } + + @Override + public int getWidth() { + return getFontRenderer().getStringWidth(key.get()) + this.margin.horizontal(); + } + + @Override + public int getHeight() { + return getFontRenderer().FONT_HEIGHT + this.margin.vertical(); + } + + @Override + public Box getMargin() { + return null; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + int w = getWidth(), h = getHeight(); + x += (int) (width / 2f - w / 2f); + y += (int) (height / 2f - h / 2f); + this.key.draw(context, x, y, width, height, widgetTheme); + } + + public KeyIcon margin(int left, int right, int top, int bottom) { + this.margin.all(left, right, top, bottom); + return this; + } + + public KeyIcon margin(int horizontal, int vertical) { + this.margin.all(horizontal, vertical); + return this; + } + + public KeyIcon margin(int all) { + this.margin.all(all); + return this; + } + + public KeyIcon marginLeft(int val) { + this.margin.left(val); + return this; + } + + public KeyIcon marginRight(int val) { + this.margin.right(val); + return this; + } + + public KeyIcon marginTop(int val) { + this.margin.top(val); + return this; + } + + public KeyIcon marginBottom(int val) { + this.margin.bottom(val); + return this; + } + + public KeyIcon fontRenderer(FontRenderer fr) { + this.overrideFontRenderer = fr; + return this; + } + + @Override + public String toString() { + return "KeyIcon(" + this.key.get() + ")"; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/keys/LangKey.java b/src/main/java/com/cleanroommc/modularui/drawable/text/LangKey.java similarity index 76% rename from src/main/java/com/cleanroommc/modularui/drawable/keys/LangKey.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/LangKey.java index 962f1aa5..c8ab8f15 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/keys/LangKey.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/LangKey.java @@ -1,7 +1,6 @@ -package com.cleanroommc.modularui.drawable.keys; +package com.cleanroommc.modularui.drawable.text; import com.cleanroommc.modularui.ClientEventHandler; -import com.cleanroommc.modularui.api.drawable.IKey; import net.minecraft.client.resources.I18n; @@ -11,7 +10,7 @@ import java.util.Objects; import java.util.function.Supplier; -public class LangKey implements IKey { +public class LangKey extends BaseKey { private final Supplier keySupplier; private final Supplier argsSupplier; @@ -53,21 +52,7 @@ public String get() { return this.string; } this.time = ClientEventHandler.getTicks(); - this.string = I18n.format(Objects.requireNonNull(this.keySupplier.get()), this.argsSupplier.get()); + this.string = I18n.format(Objects.requireNonNull(this.keySupplier.get()), this.argsSupplier.get()).replaceAll("\\\\n", "\n"); return string; } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj instanceof LangKey langKey) { - return this.get().equals(langKey.get()); - } - return false; - } - - @Override - public String toString() { - return this.get(); - } } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java b/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java new file mode 100644 index 00000000..7abaa4b0 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/RichText.java @@ -0,0 +1,190 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.drawable.*; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.Alignment; + +import net.minecraft.client.gui.FontRenderer; + +import java.util.ArrayList; +import java.util.List; + +public class RichText implements IDrawable, IRichTextBuilder { + + private static final TextRenderer renderer = new TextRenderer(); + + private final List elements = new ArrayList<>(); + private Alignment alignment = Alignment.CenterLeft; + private float scale = 1f; + private Integer color = null; + private Boolean shadow = null; + + private List cachedText; + + public boolean isEmpty() { + return this.elements.isEmpty(); + } + + public List getStringRepresentation() { + List list = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + for (Object o : this.elements) { + if (o == IKey.LINE_FEED) { + list.add(builder.toString()); + builder.delete(0, builder.length()); + continue; + } + String s = null; + if (o instanceof IKey key) { + s = key.get(); + } else if (o instanceof String s1) { + s = s1; + } else if (o instanceof TextIcon ti) { + s = ti.getText(); + } + if (s != null) { + for (String part : s.split("\n")) { + builder.append(part); + list.add(builder.toString()); + builder.delete(0, builder.length()); + } + } + } + if (!list.isEmpty() && list.get(list.size() - 1).isEmpty()) { + list.remove(list.size() - 1); + } + return list; + } + + public int getMinWidth() { + int minWidth = 12; + for (Object o : this.elements) { + if (o instanceof IIcon icon) { + minWidth = Math.max(minWidth, icon.getWidth()); + } + } + return minWidth; + } + + public Alignment getAlignment() { + return alignment; + } + + public Boolean getShadow() { + return shadow; + } + + public Integer getColor() { + return color; + } + + public float getScale() { + return scale; + } + + @Override + public RichText getThis() { + return this; + } + + @Override + public IRichTextBuilder getRichText() { + return this; + } + + public RichText add(String s) { + this.elements.add(s); + return this; + } + + @Override + public RichText add(IDrawable drawable) { + Object o = drawable; + if (!(o instanceof IKey) && !(o instanceof IIcon)) o = drawable.asIcon(); + this.elements.add(o); + return this; + } + + @Override + public RichText addLine(ITextLine line) { + this.elements.add(line); + return this; + } + + @Override + public RichText clearText() { + this.elements.clear(); + return this; + } + + @Override + public RichText alignment(Alignment alignment) { + this.alignment = alignment; + return this; + } + + @Override + public RichText textColor(int color) { + this.color = color; + return this; + } + + @Override + public RichText scale(float scale) { + this.scale = scale; + return this; + } + + @Override + public RichText textShadow(boolean shadow) { + this.shadow = shadow; + return this; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + draw(context, x, y, width, height, widgetTheme.getTextColor(), widgetTheme.getTextShadow()); + /*int mx = context.unTransformX(context.getAbsMouseX(), context.getAbsMouseY()); + int my = context.unTransformY(context.getAbsMouseX(), context.getAbsMouseY()); + Object hovering = getHoveringElement(TextRenderer.getFontRenderer(), mx, my); + if (hovering != null) ModularUI.LOGGER.info(hovering);*/ + } + + public void draw(GuiContext context, int x, int y, int width, int height, int color, boolean shadow) { + renderer.setSimulate(false); + setupRenderer(renderer, x, y, width, height, color, shadow); + this.cachedText = renderer.compileAndDraw(context, this.elements); + } + + public void setupRenderer(TextRenderer renderer, int x, int y, float width, float height, int color, boolean shadow) { + renderer.setPos(x, y); + renderer.setScale(this.scale); + renderer.setColor(this.color != null ? this.color : color); + renderer.setShadow(this.shadow != null ? this.shadow : shadow); + renderer.setAlignment(this.alignment, width, height); + } + + public List compileAndDraw(TextRenderer renderer, GuiContext context, boolean simulate) { + renderer.setSimulate(simulate); + this.cachedText = renderer.compileAndDraw(context, this.elements); + renderer.setSimulate(false); + return this.cachedText; + } + + public Object getHoveringElement(GuiContext context) { + return getHoveringElement(context.getFontRenderer(), context.getMouseX(), context.getMouseY()); + } + + public Object getHoveringElement(FontRenderer fr, int x, int y) { + if (this.cachedText == null) return null; + + for (ITextLine line : this.cachedText) { + Object o = line.getHoveringElement(fr, x, y); + if (o == null) continue; + if (o == Boolean.FALSE) return null; + return o; + } + return null; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/RichTextCompiler.java b/src/main/java/com/cleanroommc/modularui/drawable/text/RichTextCompiler.java new file mode 100644 index 00000000..9cbe0635 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/RichTextCompiler.java @@ -0,0 +1,224 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.drawable.IDrawable; +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.drawable.ITextLine; +import com.cleanroommc.modularui.core.mixin.FontRendererAccessor; +import com.cleanroommc.modularui.drawable.DelegateIcon; +import com.cleanroommc.modularui.drawable.Icon; + +import com.cleanroommc.modularui.screen.viewport.GuiContext; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.util.text.TextFormatting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class compiles a list of objects into renderable text. The objects can be strings or any drawable. + * The compiler will try to inline the drawables into the text according to the given maximum width. + * Recommended usage is via {@link TextRenderer#compileAndDraw(GuiContext, List)}. + */ +public class RichTextCompiler { + + public static final RichTextCompiler INSTANCE = new RichTextCompiler(); + + private FontRenderer fr; + private int maxWidth; + + private List lines; + private List currentLine; + private int x, h; + private final TextFormatting[] formatting = FontRenderHelper.createFormattingState(); + + public List compileLines(FontRenderer fr, List raw, int maxWidth, float scale) { + reset(fr, (int) (maxWidth / scale)); + compile(raw); + return lines; + } + + public void reset(FontRenderer fr, int maxWidth) { + this.fr = fr != null ? fr : Minecraft.getMinecraft().fontRenderer; + this.maxWidth = maxWidth > 0 ? maxWidth : Integer.MAX_VALUE; + this.lines = new ArrayList<>(); + this.currentLine = new ArrayList<>(); + this.x = 0; + this.h = 0; + Arrays.fill(this.formatting, null); + } + + private void compile(List raw) { + for (Object o : raw) { + if (o instanceof ITextLine line) { + newLine(); + lines.add(line); + continue; + } + String text = null; + if (o instanceof IKey key) { + if (key == IKey.EMPTY) continue; + if (key == IKey.SPACE) { + addLineElement(key.get()); + continue; + } + if (key == IKey.LINE_FEED) { + newLine(); + continue; + } + text = key.getFormatted(); + } else if (!(o instanceof IDrawable)) { + text = String.valueOf(o); + } + if (text != null) { + compileString(text); + continue; + } + if (!(o instanceof IIcon)) { + o = ((IDrawable) o).asIcon();//.size(fr.FONT_HEIGHT); + } + IIcon icon = (IIcon) o; + IIcon delegate = icon; + if (icon instanceof DelegateIcon di) { + delegate = di.findRootDelegate(); + } + if (delegate instanceof Icon icon1) { + if (icon1.getWidth() <= 0) icon1.width(fr.FONT_HEIGHT); + if (icon1.getHeight() <= 0) icon1.height(fr.FONT_HEIGHT); + } + if (icon.getWidth() > maxWidth) { + ModularUI.LOGGER.warn("Icon is wider than max width"); + } + checkNewLine(icon.getWidth()); + addLineElement(icon); + h = Math.max(h, icon.getHeight()); + x += icon.getWidth(); + } + newLine(); + } + + private void compileString(String text) { + int l = text.indexOf('\n'); + int k = 0; + do { + // essentially splits text at \n and compiles it + if (l < 0) l = text.length(); // no line feed, use rest of string + String subText = text.substring(k, l); + k = l + 1; // start next sub string here + while (!subText.isEmpty()) { + // how many chars fit + int i = ((FontRendererAccessor) fr).invokeSizeStringToWidth(subText, maxWidth - this.x); + if (i == 0) { + // doesn't fit at the end of the line, try new line + if (this.x > 0) i = ((FontRendererAccessor) fr).invokeSizeStringToWidth(subText, maxWidth); + if (i == 0) throw new IllegalStateException("No space for string '" + subText + "'"); + newLine(); + } else if (i < subText.length()) { + // the whole string doesn't fit + char c = subText.charAt(i); + if (c != ' ' && this.x > 0) { + // line was split in the middle of a word, try new line + int j = ((FontRendererAccessor) fr).invokeSizeStringToWidth(subText, maxWidth); + if (j < subText.length()) { + c = subText.charAt(j); + if (j > i && c == ' ') { + // line was split properly on a new line + newLine(); + } + } else { + // the end of the line is reached + newLine(); + } + } + } + // get fitting string + String current = subText.length() <= i ? subText : trimRight(subText.substring(0, i)); + int width = fr.getStringWidth(current); + addLineElement(current); // add string + h = Math.max(h, fr.FONT_HEIGHT); + x += width; + if (subText.length() <= i) break; // sub text reached the end + newLine(); // string was split -> line is full + char c = subText.charAt(i); + if (c == ' ') i++; // if was split at space then don't include it in next sub text + subText = subText.substring(i); // set sub text to part after split + } + if (l < text.length() && text.charAt(l) == '\n') { + // was split at line feed -> new line + newLine(); + } + } while ((l = text.indexOf('\n', k)) >= 0 || k < text.length()); // if no line feed found, check if we are at the end of the text + } + + private void newLine() { + int i = currentLine.size() - 1; + if (!currentLine.isEmpty() && currentLine.get(i) instanceof String s) { + if (s.equals(" ")) {currentLine.remove(i);} else currentLine.set(i, trimRight(s)); + } + if (currentLine.isEmpty()) { + //lines.add(null); + } else if (currentLine.size() == 1 && currentLine.get(0) instanceof String) { + lines.add(new TextLine((String) currentLine.get(0), x)); + currentLine.clear(); + } else { + lines.add(new ComposedLine(currentLine, x, h)); + currentLine = new ArrayList<>(); + } + x = 0; + h = 0; + } + + private void addLineElement(Object o) { + if (o instanceof String s2) { + if (this.currentLine.size() == 1 && this.currentLine.get(0) instanceof String s1) { + // if there is already one string in the line, merge them + this.currentLine.set(0, s1 + s2); + return; + } + if (this.currentLine.isEmpty()) { + // if there is currently no string, remove all whitespace from the start, + // but don't remove any formatting before + int l = FontRenderHelper.getFormatLength(s2, 0); + if (l + 1 < s2.length()) { + o = trimAt(s2, l); + } + } + o = FontRenderHelper.getFormatting(this.formatting) + o; // add formatting from previous string + FontRenderHelper.parseFormattingState(this.formatting, s2); // parse formatting from current string + } + this.currentLine.add(o); + } + + private void checkNewLine(int width) { + if (x > 0 && x + width > maxWidth) { + newLine(); + } + } + + public static String trimRight(String s) { + int i = s.length() - 1; + for (; i >= 0; i--) { + if (!Character.isWhitespace(s.charAt(i))) break; + } + if (i < s.length() - 1) s = s.substring(0, i); + return s; + } + + public static String trimAt(String s, int start) { + int l = 0; + for (int i = Math.max(0, start), n = s.length(); i < n; i++) { + if (Character.isWhitespace(s.charAt(i))) { + l++; + } else { + break; + } + } + if (l == 0) return s; + if (start <= 0) return s.substring(l); + return s.substring(0, start) + s.substring(start + l); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/Spacer.java b/src/main/java/com/cleanroommc/modularui/drawable/text/Spacer.java new file mode 100644 index 00000000..4311f0c7 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/Spacer.java @@ -0,0 +1,42 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.drawable.ITextLine; +import com.cleanroommc.modularui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.FontRenderer; + +public class Spacer implements ITextLine { + + public static final Spacer SPACER_2PX = new Spacer(2); + public static final Spacer LINE_SPACER = new Spacer(FontRenderHelper.getDefaultTextHeight()); + + public static Spacer of(int space) { + if (space == 2) return SPACER_2PX; + if (space == LINE_SPACER.space) return LINE_SPACER; + return new Spacer(space); + } + + private final int space; + + protected Spacer(int space) { + this.space = space; + } + + @Override + public int getWidth() { + return 1; + } + + @Override + public int getHeight(FontRenderer fr) { + return this.space; + } + + @Override + public void draw(GuiContext context, FontRenderer fr, float x, float y, int color, boolean shadow) {} + + @Override + public Object getHoveringElement(FontRenderer fr, int x, int y) { + return null; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/keys/StringKey.java b/src/main/java/com/cleanroommc/modularui/drawable/text/StringKey.java similarity index 53% rename from src/main/java/com/cleanroommc/modularui/drawable/keys/StringKey.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/StringKey.java index 19e8ef1b..66ef78bc 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/keys/StringKey.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/StringKey.java @@ -1,12 +1,10 @@ -package com.cleanroommc.modularui.drawable.keys; - -import com.cleanroommc.modularui.api.drawable.IKey; +package com.cleanroommc.modularui.drawable.text; import org.jetbrains.annotations.Nullable; import java.util.Objects; -public class StringKey implements IKey { +public class StringKey extends BaseKey { private final String string; private final Object[] args; @@ -24,18 +22,4 @@ public StringKey(String string, @Nullable Object[] args) { public String get() { return this.args == null ? this.string : String.format(this.string, this.args); } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj instanceof StringKey stringKey) { - return this.string.equals(stringKey.string); - } - return false; - } - - @Override - public String toString() { - return this.string; - } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/drawable/StyledText.java b/src/main/java/com/cleanroommc/modularui/drawable/text/StyledText.java similarity index 70% rename from src/main/java/com/cleanroommc/modularui/drawable/StyledText.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/StyledText.java index f35a982a..457a5c8e 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/StyledText.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/StyledText.java @@ -1,4 +1,4 @@ -package com.cleanroommc.modularui.drawable; +package com.cleanroommc.modularui.drawable.text; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.screen.viewport.GuiContext; @@ -6,19 +6,18 @@ import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.widgets.TextWidget; +import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -public class StyledText implements IKey { +public class StyledText extends BaseKey { private final IKey key; private Alignment alignment = Alignment.Center; - private int color = 0x404040; - private boolean shadow = false; + private Integer color = null; + private Boolean shadow = null; private float scale = 1f; - protected boolean colorChanged = false, shadowChanged = false; - public StyledText(IKey key) { this.key = key; } @@ -28,15 +27,20 @@ public String get() { return this.key.get(); } + @Override + public String getFormatted() { + return this.key.getFormatted(); + } + @SideOnly(Side.CLIENT) @Override public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { renderer.setAlignment(this.alignment, width, height); - renderer.setColor(this.colorChanged ? this.color : widgetTheme.getColor()); + renderer.setColor(this.color != null ? this.color : widgetTheme.getColor()); renderer.setScale(this.scale); renderer.setPos(x, y); - renderer.setShadow(this.shadowChanged ? this.shadow : widgetTheme.getTextShadow()); - renderer.draw(get()); + renderer.setShadow(this.shadow != null ? this.shadow : widgetTheme.getTextShadow()); + renderer.draw(getFormatted()); } public Alignment getAlignment() { @@ -55,6 +59,12 @@ public boolean isShadow() { return this.shadow; } + @Override + public BaseKey format(TextFormatting formatting) { + this.key.format(formatting); + return this; + } + @Override public StyledText alignment(Alignment alignment) { this.alignment = alignment; @@ -64,7 +74,6 @@ public StyledText alignment(Alignment alignment) { @Override public StyledText color(int color) { this.color = color; - this.colorChanged = true; return this; } @@ -77,31 +86,24 @@ public StyledText scale(float scale) { @Override public StyledText shadow(boolean shadow) { this.shadow = shadow; - this.shadowChanged = true; return this; } @Override public TextWidget asWidget() { - TextWidget textWidget = new TextWidget(this.key) + return new TextWidget(this.key) .alignment(this.alignment) .color(this.color) .scale(this.scale) .shadow(this.shadow); - textWidget.colorChanged = this.colorChanged; - textWidget.shadowChanged = this.shadowChanged; - return textWidget; } @Override public AnimatedText withAnimation() { - AnimatedText animatedText = new AnimatedText(this) + return new AnimatedText(this.key) .alignment(this.alignment) .color(this.color) .scale(this.scale) .shadow(this.shadow); - animatedText.colorChanged = this.colorChanged; - animatedText.shadowChanged = this.shadowChanged; - return animatedText; } } diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/TextDrawParams.java b/src/main/java/com/cleanroommc/modularui/drawable/text/TextDrawParams.java new file mode 100644 index 00000000..99158293 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/TextDrawParams.java @@ -0,0 +1,6 @@ +package com.cleanroommc.modularui.drawable.text; + +public class TextDrawParams { + + private int line; +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/TextIcon.java b/src/main/java/com/cleanroommc/modularui/drawable/text/TextIcon.java similarity index 96% rename from src/main/java/com/cleanroommc/modularui/drawable/TextIcon.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/TextIcon.java index e310f782..4c0e064f 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/TextIcon.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/TextIcon.java @@ -1,4 +1,4 @@ -package com.cleanroommc.modularui.drawable; +package com.cleanroommc.modularui.drawable.text; import com.cleanroommc.modularui.api.drawable.IIcon; import com.cleanroommc.modularui.screen.viewport.GuiContext; diff --git a/src/main/java/com/cleanroommc/modularui/drawable/text/TextLine.java b/src/main/java/com/cleanroommc/modularui/drawable/text/TextLine.java new file mode 100644 index 00000000..778d7e4c --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/TextLine.java @@ -0,0 +1,43 @@ +package com.cleanroommc.modularui.drawable.text; + +import com.cleanroommc.modularui.api.drawable.ITextLine; +import com.cleanroommc.modularui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.FontRenderer; + +public class TextLine implements ITextLine { + + private final String text; + private final int width; + + private float lastX, lastY; + + public TextLine(String text, int width) { + this.text = text; + this.width = width; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight(FontRenderer fr) { + return fr.FONT_HEIGHT; + } + + @Override + public void draw(GuiContext context, FontRenderer fr, float x, float y, int color, boolean shadow) { + fr.drawString(this.text, x, y, color, shadow); + this.lastX = x; + this.lastY = y; + } + + @Override + public Object getHoveringElement(FontRenderer fr, int x, int y) { + if (y < lastY || y > lastY + getHeight(fr)) return null; + if (x < lastX || x > lastX + getWidth()) return Boolean.FALSE; // not hovering, but we know that nothing else is hovered either + return this.text; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java b/src/main/java/com/cleanroommc/modularui/drawable/text/TextRenderer.java similarity index 77% rename from src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java rename to src/main/java/com/cleanroommc/modularui/drawable/text/TextRenderer.java index 9d0daf31..a8d4215f 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/TextRenderer.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/text/TextRenderer.java @@ -1,5 +1,7 @@ -package com.cleanroommc.modularui.drawable; +package com.cleanroommc.modularui.drawable.text; +import com.cleanroommc.modularui.api.drawable.ITextLine; +import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.widget.sizer.Area; @@ -26,6 +28,7 @@ public class TextRenderer { protected int color = 0;//Theme.INSTANCE.getText(); protected boolean simulate; protected float lastWidth = 0, lastHeight = 0; + protected float lastX = 0, lastY = 0; protected boolean scrollOnOverflow = false; public void setAlignment(Alignment alignment, float maxWidth) { @@ -73,7 +76,7 @@ public void draw(List lines) { protected void drawMeasuredLines(List measuredLines) { float maxW = 0; - int y0 = getStartY(measuredLines.size()); + int y0 = getStartYOfLines(measuredLines.size()); for (Line measuredLine : measuredLines) { int x0 = getStartX(measuredLine.width); maxW = Math.max(maxW, measuredLine.width); @@ -88,7 +91,7 @@ protected void drawMeasuredLines(List measuredLines) { public void drawSimple(String text) { float w = getFontRenderer().getStringWidth(text) * this.scale; - int y = getStartY(1), x = getStartX(w); + int y = getStartYOfLines(1), x = getStartX(w); draw(text, x, y); this.lastWidth = w; this.lastHeight = getFontHeight(); @@ -106,6 +109,40 @@ public List measureLines(List lines) { return measuredLines; } + public List compile(List rawText) { + return RichTextCompiler.INSTANCE.compileLines(getFontRenderer(), rawText, (int) this.maxWidth, this.scale); + } + + public List compileAndDraw(GuiContext context, List raw) { + List lines = compile(raw); + drawCompiled(context, lines); + return lines; + } + + public void drawCompiled(GuiContext context, List lines) { + int height = 0, width = 0; + for (ITextLine line : lines) { + height += line.getHeight(getFontRenderer()); + width = Math.max(width, line.getWidth()); + } + if (!this.simulate) { + GlStateManager.pushMatrix(); + GlStateManager.scale(this.scale, this.scale, 1f); + } + int y0 = getStartY(height * this.scale); + this.lastY = y0; + for (ITextLine line : lines) { + int x0 = getStartX(line.getWidth() * this.scale); + if (!simulate) line.draw(context, getFontRenderer(), x0, y0, this.color, this.shadow); + y0 += line.getHeight(getFontRenderer()); + } + if (!this.simulate) GlStateManager.popMatrix(); + this.lastWidth = this.maxWidth > 0 ? Math.min(width * this.scale, this.maxWidth) : width * this.scale; + this.lastHeight = height * this.scale; + this.lastWidth = Math.max(0, this.lastWidth - this.scale); + this.lastHeight = Math.max(0, this.lastHeight - this.scale); + } + public void drawCut(String text) { if (text.contains("\n")) { throw new IllegalArgumentException("Scrolling text can't wrap!"); @@ -167,9 +204,12 @@ public int getMaxWidth(List lines) { return (int) Math.ceil(w); } - protected int getStartY(int lines) { + protected int getStartYOfLines(int lines) { + return getStartY(lines * getFontHeight() - this.scale); + } + + protected int getStartY(float height) { if (this.alignment.y > 0 && this.maxHeight > 0) { - float height = lines * getFontHeight() - this.scale; return (int) (this.y + (this.maxHeight * this.alignment.y) - height * this.alignment.y); } return this.y; @@ -182,17 +222,14 @@ protected int getStartX(float lineWidth) { return this.x; } - protected float draw(String text, float x, float y) { - if (this.simulate) { - return getFontRenderer().getStringWidth(text); - } + protected void draw(String text, float x, float y) { + if (this.simulate) return; GlStateManager.disableBlend(); GlStateManager.pushMatrix(); GlStateManager.scale(this.scale, this.scale, 0f); - int width = getFontRenderer().drawString(text, x / this.scale, y / this.scale, this.color, this.shadow); + getFontRenderer().drawString(text, x / this.scale, y / this.scale, this.color, this.shadow); GlStateManager.popMatrix(); GlStateManager.enableBlend(); - return width * this.scale; } public float getFontHeight() { diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java index d7a39f61..0b257a41 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java @@ -35,12 +35,14 @@ public static void foreach(Consumer function, boolean topToBottom public static boolean interact(Predicate function, boolean topToBottom) { if (topToBottom) { for (int i = overlay.size() - 1; i >= 0; i--) { + overlay.get(i).getContext().updateEventState(); if (function.test(overlay.get(i))) { return true; } } } else { for (ModularScreen screen : overlay) { + screen.getContext().updateEventState(); if (function.test(screen)) { return true; } @@ -53,8 +55,7 @@ public static void draw(int mouseX, int mouseY, float partialTicks) { ModularScreen hovered = null; ModularScreen fallback = null; for (ModularScreen screen : overlay) { - GlStateManager.disableDepth(); - GlStateManager.disableLighting(); + screen.getContext().updateState(mouseX, mouseY, partialTicks); GlStateManager.enableBlend(); GlStateManager.color(1f, 1f, 1f, 1f); screen.drawScreen(mouseX, mouseY, partialTicks); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index cfc98c65..506929b8 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -3,6 +3,7 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.IMuiScreen; +import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.core.mixin.GuiAccessor; @@ -13,11 +14,13 @@ import com.cleanroommc.modularui.overlay.OverlayStack; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.screen.viewport.LocatedWidget; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.Animator; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.utils.FpsCounter; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widgets.ItemSlot; +import com.cleanroommc.modularui.widgets.RichTextWidget; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.slot.SlotGroup; @@ -57,12 +60,15 @@ @SideOnly(Side.CLIENT) public class ClientScreenHandler { + private static final GuiContext defaultContext = new GuiContext(); + private static ModularScreen currentScreen = null; private static Character lastChar = null; private static final FpsCounter fpsCounter = new FpsCounter(); @SubscribeEvent public static void onGuiOpen(GuiOpenEvent event) { + defaultContext.reset(); if (event.getGui() instanceof IMuiScreen muiScreen) { Objects.requireNonNull(muiScreen.getScreen(), "ModularScreen must not be null!"); if (currentScreen != muiScreen.getScreen()) { @@ -83,6 +89,7 @@ public static void onGuiOpen(GuiOpenEvent event) { @SubscribeEvent public static void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + defaultContext.updateScreenArea(event.getGui().width, event.getGui().height); if (checkGui(event.getGui())) { currentScreen.onResize(event.getGui().width, event.getGui().height); } @@ -91,7 +98,8 @@ public static void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { @SubscribeEvent(priority = EventPriority.LOW) public static void onGuiInputLow(GuiScreenEvent.KeyboardInputEvent.Pre event) throws IOException { - checkGui(event.getGui()); + defaultContext.updateEventState(); + if (checkGui(event.getGui())) currentScreen.getContext().updateEventState(); if (handleKeyboardInput(currentScreen, event.getGui())) { event.setCanceled(true); } @@ -99,16 +107,12 @@ public static void onGuiInputLow(GuiScreenEvent.KeyboardInputEvent.Pre event) th @SubscribeEvent(priority = EventPriority.LOW) public static void onGuiInputLow(GuiScreenEvent.MouseInputEvent.Pre event) throws IOException { - if (checkGui(event.getGui())) { - currentScreen.getContext().updateEventState(); - } + defaultContext.updateEventState(); + if (checkGui(event.getGui())) currentScreen.getContext().updateEventState(); if (handleMouseInput(Mouse.getEventButton(), currentScreen, event.getGui())) { event.setCanceled(true); + return; } - } - - @SubscribeEvent - public static void onScroll(GuiScreenEvent.MouseInputEvent.Pre event) { int w = Mouse.getEventDWheel(); if (w == 0) return; ModularScreen.UpOrDown upOrDown = w > 0 ? ModularScreen.UpOrDown.UP : ModularScreen.UpOrDown.DOWN; @@ -120,8 +124,13 @@ public static void onScroll(GuiScreenEvent.MouseInputEvent.Pre event) { @SubscribeEvent(priority = EventPriority.LOW) public static void onGuiDraw(GuiScreenEvent.DrawScreenEvent.Pre event) { + int mx = event.getMouseX(), my = event.getMouseY(); + float pt = event.getRenderPartialTicks(); + defaultContext.updateState(mx, my, pt); + defaultContext.reset(); if (checkGui(event.getGui())) { - drawScreen(currentScreen, currentScreen.getScreenWrapper().getGuiScreen(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks()); + currentScreen.getContext().updateState(mx, my, pt); + drawScreen(currentScreen, currentScreen.getScreenWrapper().getGuiScreen(), mx, my, pt); event.setCanceled(true); } } @@ -135,6 +144,7 @@ public static void onGuiDraw(GuiScreenEvent.DrawScreenEvent.Post event) { public static void onTick(TickEvent.ClientTickEvent event) { if (event.phase == TickEvent.Phase.START) { OverlayStack.onTick(); + defaultContext.tick(); if (checkGui()) { currentScreen.onUpdate(); } @@ -234,14 +244,14 @@ private static boolean keyTyped(GuiScreen screen, char typedChar, int keyCode) t public static void dragSlot(long timeSinceLastClick) { if (hasScreen() && getMCScreen() instanceof GuiScreenAccessor container) { - GuiContext ctx = currentScreen.getContext(); + ModularGuiContext ctx = currentScreen.getContext(); container.invokeMouseClickMove(ctx.getAbsMouseX(), ctx.getAbsMouseY(), ctx.getMouseButton(), timeSinceLastClick); } } public static void clickSlot() { if (hasScreen() && getMCScreen() instanceof GuiScreenAccessor screen) { - GuiContext ctx = currentScreen.getContext(); + ModularGuiContext ctx = currentScreen.getContext(); try { screen.invokeMouseClicked(ctx.getAbsMouseX(), ctx.getMouseY(), ctx.getMouseButton()); } catch (IOException e) { @@ -252,7 +262,7 @@ public static void clickSlot() { public static void releaseSlot() { if (hasScreen() && getMCScreen() instanceof GuiScreenAccessor screen) { - GuiContext ctx = currentScreen.getContext(); + ModularGuiContext ctx = currentScreen.getContext(); screen.invokeMouseReleased(ctx.getAbsMouseX(), ctx.getAbsMouseY(), ctx.getMouseButton()); } } @@ -300,7 +310,6 @@ public static void drawScreenInternal(ModularScreen muiScreen, GuiScreen mcScree } public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, int mouseX, int mouseY, float partialTicks) { - fpsCounter.onDraw(); GuiContainerAccessor acc = (GuiContainerAccessor) mcScreen; Stencil.reset(); @@ -408,6 +417,7 @@ private static void drawVanillaElements(GuiScreen mcScreen, int mouseX, int mous } public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable ModularScreen fallback) { + fpsCounter.onDraw(); if (!ModularUIConfig.guiDebugMode) return; if (muiScreen == null) { if (checkGui()) { @@ -421,10 +431,10 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable GlStateManager.disableLighting(); GlStateManager.enableBlend(); - GuiContext context = muiScreen.getContext(); + ModularGuiContext context = muiScreen.getContext(); int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); int screenH = muiScreen.getScreenArea().height; - int color = Color.rgb(180, 40, 115); + int color = Color.argb(180, 40, 115, 220); int lineY = screenH - 13; Minecraft.getMinecraft().fontRenderer.drawStringWithShadow("Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, color); lineY -= 11; @@ -474,6 +484,11 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable boolean allowShiftTransfer = slotGroup != null && slotGroup.allowShiftTransfer(); GuiDraw.drawText("Shift-Click Priority: " + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), 5, lineY, 1, color, false); } + } else if(hovered instanceof RichTextWidget richTextWidget) { + drawSegmentLine(lineY -= 4, color); + lineY -= 10; + Object hoveredElement = richTextWidget.getHoveredElement(); + GuiDraw.drawText("Hovered: " + hoveredElement, 5, lineY, 1, color, false); } } // dot at mouse pos @@ -499,7 +514,7 @@ public static boolean hasScreen() { @Nullable public static GuiScreen getMCScreen() { - return Minecraft.getMinecraft().currentScreen; + return MCHelper.getCurrentScreen(); } @Nullable @@ -508,16 +523,28 @@ public static ModularScreen getMuiScreen() { } private static boolean checkGui() { - return checkGui(Minecraft.getMinecraft().currentScreen); + return MCHelper.hasMc() && checkGui(Minecraft.getMinecraft().currentScreen); } private static boolean checkGui(GuiScreen screen) { - if (currentScreen == null || !(screen instanceof IMuiScreen muiScreen)) return false; + if (!MCHelper.hasMc() || currentScreen == null || !(screen instanceof IMuiScreen muiScreen)) return false; if (screen != Minecraft.getMinecraft().currentScreen || muiScreen.getScreen() != currentScreen) { + defaultContext.reset(); currentScreen = null; lastChar = null; return false; } return true; } + + public static GuiContext getDefaultContext() { + return defaultContext; + } + + public static GuiContext getBestContext() { + if (checkGui()) { + return currentScreen.getContext(); + } + return defaultContext; + } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java index 5d0746ba..647f9347 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java @@ -1,7 +1,7 @@ package com.cleanroommc.modularui.screen; import com.cleanroommc.modularui.ModularUI; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -39,5 +39,5 @@ public CustomModularScreen(@NotNull String owner) { */ @NotNull @ApiStatus.OverrideOnly - public abstract ModularPanel buildUI(GuiContext context); + public abstract ModularPanel buildUI(ModularGuiContext context); } diff --git a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java index 7a7ea9fe..c95c98dd 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java +++ b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java @@ -2,7 +2,7 @@ import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IDraggable; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.widget.WidgetTree; import com.cleanroommc.modularui.widget.sizer.Area; @@ -21,7 +21,7 @@ public DraggablePanelWrapper(ModularPanel panel) { } @Override - public void drawMovingState(GuiContext context, float partialTicks) { + public void drawMovingState(ModularGuiContext context, float partialTicks) { context.pushMatrix(); transform(context); WidgetTree.drawTree(this.panel, context, true); @@ -31,7 +31,7 @@ public void drawMovingState(GuiContext context, float partialTicks) { @Override public boolean onDragStart(int button) { if (button == 0) { - GuiContext context = this.panel.getContext(); + ModularGuiContext context = this.panel.getContext(); this.movingArea.x = context.transformX(0, 0); this.movingArea.y = context.transformY(0, 0); this.relativeClickX = context.getAbsMouseX() - this.movingArea.x; diff --git a/src/main/java/com/cleanroommc/modularui/screen/JeiSettingsImpl.java b/src/main/java/com/cleanroommc/modularui/screen/JeiSettingsImpl.java index e778395f..58ccd1c6 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/JeiSettingsImpl.java +++ b/src/main/java/com/cleanroommc/modularui/screen/JeiSettingsImpl.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.stream.Collectors; /** * Keeps track of everything related to JEI in a Modular GUI. @@ -160,10 +159,16 @@ public List> getJeiGhostIngredientSlots() { public List getAllJeiExclusionAreas() { this.jeiExclusionWidgets.removeIf(widget -> !widget.isValid()); List areas = new ArrayList<>(this.jeiExclusionAreas); - areas.addAll(this.jeiExclusionWidgets.stream() - .filter(IWidget::isEnabled) - .map(IWidget::getArea) - .collect(Collectors.toList())); + for (Iterator iterator = this.jeiExclusionWidgets.iterator(); iterator.hasNext(); ) { + IWidget widget = iterator.next(); + if (!widget.isValid()) { + iterator.remove(); + continue; + } + if (widget.isEnabled()) { + areas.add(widget.getArea()); + } + } return areas; } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index 5eb9ca47..011fae61 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -8,7 +8,7 @@ import com.cleanroommc.modularui.api.widget.IFocusedWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.api.widget.Interactable; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.screen.viewport.GuiViewportStack; import com.cleanroommc.modularui.screen.viewport.LocatedWidget; import com.cleanroommc.modularui.theme.WidgetTheme; @@ -70,7 +70,7 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int hei public ModularPanel(@NotNull String name) { this.name = Objects.requireNonNull(name, "A panels name must not be null and should be unique!"); - align(Alignment.Center); + center(); } @Override @@ -596,7 +596,7 @@ public int getDefaultWidth() { return 176; } - final void setPanelGuiContext(@NotNull GuiContext context) { + final void setPanelGuiContext(@NotNull ModularGuiContext context) { setContext(context); if (!context.getScreen().isOverlay()) { context.getJeiSettings().addJeiExclusionArea(this); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index 59453d7d..dcff9eb5 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -9,7 +9,7 @@ import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.overlay.ScreenWrapper; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.value.sync.ModularSyncManager; import com.cleanroommc.modularui.widget.WidgetTree; @@ -66,8 +66,7 @@ public static ModularScreen getCurrent() { private final String owner; private final String name; private final PanelManager panelManager; - private final GuiContext context = new GuiContext(this); - private final Area screenArea = new Area(); + private final ModularGuiContext context = new ModularGuiContext(this); private final Map, List> guiActionListeners = new Object2ObjectOpenHashMap<>(); private final Object2ObjectArrayMap frameUpdates = new Object2ObjectArrayMap<>(); private boolean pausesGame = false; @@ -101,11 +100,11 @@ public ModularScreen(@NotNull String owner, @NotNull ModularPanel mainPanel) { * @param owner owner of this screen (usually a mod id) * @param mainPanelCreator function which creates the main panel of this screen */ - public ModularScreen(@NotNull String owner, @NotNull Function mainPanelCreator) { + public ModularScreen(@NotNull String owner, @NotNull Function mainPanelCreator) { this(owner, Objects.requireNonNull(mainPanelCreator, "The main panel function must not be null!"), false); } - private ModularScreen(@NotNull String owner, @Nullable Function mainPanelCreator, boolean ignored) { + private ModularScreen(@NotNull String owner, @Nullable Function mainPanelCreator, boolean ignored) { Objects.requireNonNull(owner, "The owner must not be null!"); this.owner = owner; ModularPanel mainPanel = mainPanelCreator != null ? mainPanelCreator.apply(this.context) : buildUI(this.context); @@ -125,7 +124,7 @@ private ModularScreen(@NotNull String owner, @Nullable Function { + + private static final Area HOLDER = new Area(); + + private final Consumer parent; + private final RichText text = new RichText(); + private Pos pos = null; + private Consumer tooltipBuilder; + private int showUpTimer = 0; + private boolean autoUpdate = false; + + private int x = 0, y = 0; + private int maxWidth = Integer.MAX_VALUE; + + private boolean dirty; + + public RichTooltip(IWidget parent) { + this(area -> { + area.setSize(parent.getArea()); + area.setPos(0, 0); + }); + } + + public RichTooltip(Area parent) { + this(area -> area.set(parent)); + } + + public RichTooltip(Supplier parent) { + this(area -> area.set(parent.get())); + } + + public RichTooltip(Consumer parent) { + this.parent = parent; + } + + public void buildTooltip() { + this.dirty = false; + this.text.clearText(); + if (this.tooltipBuilder != null) { + this.tooltipBuilder.accept(this); + } + } + + public void draw(GuiContext context) { + draw(context, ItemStack.EMPTY); + } + + public void draw(GuiContext context, @Nullable ItemStack stack) { + if (this.autoUpdate) markDirty(); + if (isEmpty()) return; + + if (this.maxWidth <= 0) { + this.maxWidth = Integer.MAX_VALUE; + } + if (stack == null) stack = ItemStack.EMPTY; + Area screen = context.getScreenArea(); + this.maxWidth = Math.min(this.maxWidth, screen.width); + int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); + TextRenderer renderer = TextRenderer.SHARED; + // this only turns the text and not any drawables into strings + List textLines = this.text.getStringRepresentation(); + RenderTooltipEvent.Pre event = new RenderTooltipEvent.Pre(stack, textLines, mouseX, mouseY, screen.width, screen.height, this.maxWidth, TextRenderer.getFontRenderer()); + if (MinecraftForge.EVENT_BUS.post(event)) return; // canceled + // we are supposed to now use the strings of the event, but we can't properly determine where to put them + mouseX = event.getX(); + mouseY = event.getY(); + int screenWidth = event.getScreenWidth(), screenHeight = event.getScreenHeight(); + this.maxWidth = event.getMaxWidth(); + + // simulate to figure how big this tooltip is without any restrictions + this.text.setupRenderer(renderer, 0, 0, this.maxWidth, -1, Color.WHITE.main, false); + this.text.compileAndDraw(renderer, context, true); + + Rectangle area = determineTooltipArea(context, renderer, screenWidth, screenHeight, mouseX, mouseY); + + GlStateManager.disableRescaleNormal(); + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + GlStateManager.disableBlend(); + + GuiDraw.drawTooltipBackground(stack, textLines, area.x, area.y, area.width, area.height); + + MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostBackground(stack, textLines, area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height)); + + GlStateManager.color(1f, 1f, 1f, 1f); + + renderer.setPos(area.x, area.y); + this.text.compileAndDraw(renderer, context, false); + + MinecraftForge.EVENT_BUS.post(new RenderTooltipEvent.PostText(stack, textLines, area.x, area.y, TextRenderer.getFontRenderer(), area.width, area.height)); + } + + public Rectangle determineTooltipArea(GuiContext context, TextRenderer renderer, int screenWidth, int screenHeight, int mouseX, int mouseY) { + int width = (int) renderer.getLastWidth(); + int height = (int) renderer.getLastHeight(); + + Pos pos = this.pos; + if (pos == null) { + pos = context.isMuiContext() ? context.getMuiContext().getScreen().getCurrentTheme().getTooltipPosOverride() : null; + if (pos == null) pos = ModularUIConfig.tooltipPos; + } + if (pos == Pos.FIXED) { + return new Rectangle(this.x, this.y, width, height); + } + + if (pos == Pos.NEXT_TO_MOUSE) { + // vanilla style, tooltip floats next to mouse + // note that this behaves slightly different from vanilla (better imo) + final int padding = 8; + // magic number to place tooltip nicer. Look at GuiScreen#L237 + final int mouseOffset = 12; + int x = mouseX + mouseOffset, y = mouseY - mouseOffset; + if (x < padding) { + x = padding; // this cant happen mathematically since mouse is always positive + } else if (x + width + padding > screenWidth) { + // doesn't fit on the right side of the screen + if (screenWidth - mouseX < mouseX) { // check if left side has more space + x -= mouseOffset * 2 + width; // flip side of cursor if other side has more space + if (x < padding) { + x = padding; // went of screen + } + width = mouseX - 12 - x; // max space on left side + } else { + width = screenWidth - padding - x; // max space on right side + } + // recalculate with and height + renderer.setPos(x, y); + renderer.setAlignment(this.text.getAlignment(), width, -1); + this.text.compileAndDraw(renderer, context, true); + width = (int) renderer.getLastWidth(); + height = (int) renderer.getLastHeight(); + } + y = MathHelper.clamp(y, padding, screenHeight - padding - height); + return new Rectangle(x, y, width, height); + } + + // the rest of the cases will put the tooltip next a given area + if (this.parent == null) { + throw new IllegalStateException("Tooltip pos is " + pos.name() + ", but no widget parent is set!"); + } + + int minWidth = this.text.getMinWidth(); + + int shiftAmount = 10; + int padding = 7; + + Area area = HOLDER; + this.parent.accept(area); + area.transformAndRectanglerize(context); + int x = 0, y = 0; + if (pos.axis.isVertical()) { // above or below + if (width < area.width) { + x = area.x + shiftAmount; + } else { + x = area.x - shiftAmount; + if (x < padding) { + x = padding; + } else if (x + width > screenWidth - padding) { + int maxWidth = Math.max(minWidth, screenWidth - x - padding); + renderer.setAlignment(this.text.getAlignment(), maxWidth); + this.text.compileAndDraw(renderer, context, true); + width = (int) renderer.getLastWidth(); + height = (int) renderer.getLastHeight(); + } + } + + if (pos == Pos.VERTICAL) { + int bottomSpace = screenHeight - area.ey(); + pos = bottomSpace < height + padding && bottomSpace < area.y ? Pos.ABOVE : Pos.BELOW; + } + + if (pos == Pos.BELOW) { + y = area.ey() + padding; + } else if (pos == Pos.ABOVE) { + y = area.y - height - padding; + } + } else if (pos.axis.isHorizontal()) { + boolean usedMoreSpaceSide = false; + Pos oPos = pos; + if (oPos == Pos.HORIZONTAL) { + if (area.x > screenWidth - area.ex()) { + pos = Pos.LEFT; + // x = 0; + } else { + pos = Pos.RIGHT; + x = screenWidth - area.ex() + padding; + } + } + + if (height < area.height) { + y = area.y + shiftAmount; + } else { + y = area.y - shiftAmount; + if (y < padding) { + y = padding; + } + } + + if (x + width > screenWidth - padding) { + int maxWidth; + if (pos == Pos.LEFT) { + maxWidth = Math.max(minWidth, area.x - padding * 2); + } else { + maxWidth = Math.max(minWidth, screenWidth - area.ex() - padding * 2); + } + usedMoreSpaceSide = true; + renderer.setAlignment(this.text.getAlignment(), maxWidth); + this.text.compileAndDraw(renderer, context, true); + width = (int) renderer.getLastWidth(); + height = (int) renderer.getLastHeight(); + } + + if (oPos == Pos.HORIZONTAL && !usedMoreSpaceSide) { + int rightSpace = screenWidth - area.ex(); + pos = rightSpace < width + padding && rightSpace < area.x ? Pos.LEFT : Pos.RIGHT; + } + + if (pos == Pos.RIGHT) { + x = area.ex() + padding; + } else if (pos == Pos.LEFT) { + x = area.x - width - padding; + } + } + return new Rectangle(x, y, width, height); + } + + public boolean isEmpty() { + if (this.dirty) buildTooltip(); + return this.text.isEmpty(); + } + + public void markDirty() { + this.dirty = true; + } + + public int getShowUpTimer() { + return this.showUpTimer; + } + + public boolean isAutoUpdate() { + return this.autoUpdate; + } + + public RichTooltip pos(Pos pos) { + this.pos = pos; + return this; + } + + public RichTooltip pos(int x, int y) { + this.pos = Pos.FIXED; + this.x = x; + this.y = y; + return this; + } + + @Override + public RichTooltip getThis() { + return this; + } + + @Override + public IRichTextBuilder getRichText() { + return text; + } + + public RichTooltip showUpTimer(int showUpTimer) { + this.showUpTimer = showUpTimer; + return this; + } + + public RichTooltip tooltipBuilder(Consumer tooltipBuilder) { + Consumer existingBuilder = this.tooltipBuilder; + if (existingBuilder != null) { + this.tooltipBuilder = tooltip -> { + existingBuilder.accept(this); + tooltipBuilder.accept(this); + }; + } else { + this.tooltipBuilder = tooltipBuilder; + } + return this; + } + + public RichTooltip setAutoUpdate(boolean update) { + this.autoUpdate = update; + return this; + } + + public RichTooltip addFromItem(ItemStack item) { + List lines = MCHelper.getItemToolTip(item); + add(lines.get(0)).spaceLine(2); + for (int i = 1, n = lines.size(); i < n; i++) { + add(lines.get(i)).newLine(); + } + return this; + } + + public enum Pos { + + ABOVE(GuiAxis.Y), + BELOW(GuiAxis.Y), + LEFT(GuiAxis.X), + RIGHT(GuiAxis.X), + VERTICAL(GuiAxis.Y), + HORIZONTAL(GuiAxis.X), + NEXT_TO_MOUSE(null), + FIXED(null); + + public final GuiAxis axis; + + Pos(GuiAxis axis) { + this.axis = axis; + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java b/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java index 0d6ad48c..0c751cbc 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java +++ b/src/main/java/com/cleanroommc/modularui/screen/Tooltip.java @@ -5,7 +5,11 @@ import com.cleanroommc.modularui.api.drawable.IIcon; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.drawable.*; +import com.cleanroommc.modularui.drawable.GuiDraw; +import com.cleanroommc.modularui.drawable.Icon; +import com.cleanroommc.modularui.drawable.IconRenderer; +import com.cleanroommc.modularui.drawable.text.TextIcon; +import com.cleanroommc.modularui.drawable.text.TextRenderer; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.Color; @@ -20,19 +24,19 @@ import org.jetbrains.annotations.Nullable; -import java.awt.Rectangle; +import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; +@Deprecated public class Tooltip { private final IWidget parent; private final List lines = new ArrayList<>(); private List additionalLines = new ArrayList<>(); - private Pos pos = ModularUIConfig.tooltipPos; - private boolean customPos = false; + private RichTooltip.Pos pos = null; private Consumer tooltipBuilder; private int showUpTimer = 0; @@ -81,7 +85,7 @@ public void draw(GuiContext context, @Nullable ItemStack stack) { this.maxWidth = Integer.MAX_VALUE; } if (stack == null) stack = ItemStack.EMPTY; - Area screen = context.getScreen().getScreenArea(); + Area screen = context.getScreenArea(); int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); IconRenderer renderer = IconRenderer.SHARED; List textLines = lines.stream().filter(drawable -> drawable instanceof IKey).map(key -> ((IKey) key).get()).collect(Collectors.toList()); @@ -132,15 +136,16 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, int width = (int) renderer.getLastWidth(); int height = (int) renderer.getLastHeight(); - if (!this.customPos) { - this.pos = context.getScreen().getCurrentTheme().getTooltipPosOverride(); + RichTooltip.Pos pos = this.pos; + if (pos == null) { + pos = context.isMuiContext() ? context.getMuiContext().getScreen().getCurrentTheme().getTooltipPosOverride() : null; + if (pos == null) pos = ModularUIConfig.tooltipPos; } - - if (this.pos == null) { + if (pos == RichTooltip.Pos.FIXED) { return new Rectangle(this.x, this.y, width, height); } - if (this.pos == Pos.NEXT_TO_MOUSE) { + if (pos == RichTooltip.Pos.NEXT_TO_MOUSE) { final int padding = 8; // magic number to place tooltip nicer. Look at GuiScreen#L237 final int mouseOffset = 12; @@ -178,7 +183,7 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, area.setPos(0, 0); // context is transformed to this widget area.transformAndRectanglerize(context); int x = 0, y = 0; - if (this.pos.vertical) { + if (pos.axis.isVertical()) { if (width < area.width) { x = area.x + shiftAmount; } else { @@ -194,26 +199,26 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, } } - Pos pos = this.pos; - if (this.pos == Pos.VERTICAL) { + RichTooltip.Pos pos1 = pos; + if (pos == RichTooltip.Pos.VERTICAL) { int bottomSpace = screenHeight - area.ey(); - pos = bottomSpace < height + padding && bottomSpace < area.y ? Pos.ABOVE : Pos.BELOW; + pos1 = bottomSpace < height + padding && bottomSpace < area.y ? RichTooltip.Pos.ABOVE : RichTooltip.Pos.BELOW; } - if (pos == Pos.BELOW) { + if (pos1 == RichTooltip.Pos.BELOW) { y = area.ey() + padding; - } else if (pos == Pos.ABOVE) { + } else if (pos1 == RichTooltip.Pos.ABOVE) { y = area.y - height - padding; } - } else if (this.pos.horizontal) { + } else if (pos.axis.isHorizontal()) { boolean usedMoreSpaceSide = false; - Pos pos = this.pos; - if (this.pos == Pos.HORIZONTAL) { + RichTooltip.Pos pos1 = pos; + if (pos == RichTooltip.Pos.HORIZONTAL) { if (area.x > screenWidth - area.ex()) { - pos = Pos.LEFT; + pos1 = RichTooltip.Pos.LEFT; // x = 0; } else { - pos = Pos.RIGHT; + pos1 = RichTooltip.Pos.RIGHT; x = screenWidth - area.ex() + padding; } } @@ -229,7 +234,7 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, if (x + width > screenWidth - padding) { int maxWidth; - if (pos == Pos.LEFT) { + if (pos1 == RichTooltip.Pos.LEFT) { maxWidth = Math.max(minWidth, area.x - padding * 2); } else { maxWidth = Math.max(minWidth, screenWidth - area.ex() - padding * 2); @@ -241,14 +246,14 @@ public Rectangle determineTooltipArea(GuiContext context, List lines, height = (int) renderer.getLastHeight(); } - if (this.pos == Pos.HORIZONTAL && !usedMoreSpaceSide) { + if (pos == RichTooltip.Pos.HORIZONTAL && !usedMoreSpaceSide) { int rightSpace = screenWidth - area.ex(); - pos = rightSpace < width + padding && rightSpace < area.x ? Pos.LEFT : Pos.RIGHT; + pos1 = rightSpace < width + padding && rightSpace < area.x ? RichTooltip.Pos.LEFT : RichTooltip.Pos.RIGHT; } - if (pos == Pos.RIGHT) { + if (pos1 == RichTooltip.Pos.RIGHT) { x = area.ex() + padding; - } else if (pos == Pos.LEFT) { + } else if (pos1 == RichTooltip.Pos.LEFT) { x = area.x - width - padding; } } @@ -283,15 +288,13 @@ public boolean hasTitleMargin() { return this.hasTitleMargin; } - public Tooltip pos(Pos pos) { - this.customPos = true; + public Tooltip pos(RichTooltip.Pos pos) { this.pos = pos; return this; } public Tooltip pos(int x, int y) { - this.customPos = true; - this.pos = null; + this.pos = RichTooltip.Pos.FIXED; this.x = x; this.y = y; return this; @@ -375,22 +378,4 @@ public Tooltip addStringLines(Iterable lines) { } return this; } - - public enum Pos { - - ABOVE(false, true), - BELOW(false, true), - LEFT(true, false), - RIGHT(true, false), - VERTICAL(false, true), - HORIZONTAL(true, false), - NEXT_TO_MOUSE(false, false); - - public final boolean horizontal, vertical; - - Pos(boolean horizontal, boolean vertical) { - this.horizontal = horizontal; - this.vertical = vertical; - } - } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java index dd14df79..b66cca73 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java @@ -1,45 +1,35 @@ package com.cleanroommc.modularui.screen.viewport; -import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.GuiAxis; -import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.MCHelper; -import com.cleanroommc.modularui.api.widget.*; -import com.cleanroommc.modularui.screen.*; +import com.cleanroommc.modularui.api.drawable.IDrawable; +import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.screen.ClientScreenHandler; +import com.cleanroommc.modularui.widget.sizer.Area; import net.minecraft.client.Minecraft; -import net.minecraft.client.entity.EntityPlayerSP; +import net.minecraft.client.gui.FontRenderer; + +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Consumer; - /** - * This class keeps track of the gui state like hovered widget, focused widget, pressed buttons, draggables, mouse pos - * jei settings and themes. + * A gui context contains various properties like screen size, mouse position, last clicked button etc. + * It also is a matrix/pose stack. + * A default instance can be obtained using {@link #getDefault()}, which can be used in {@link IDrawable IDrawables} for example. + * That instance is automatically updated at all times (except when no UI is currently open). */ public class GuiContext extends GuiViewportStack { - /* GUI elements */ - @Deprecated - public final ModularScreen screen; - private LocatedWidget focusedWidget = LocatedWidget.EMPTY; - @Nullable - private IGuiElement hovered; - private int timeHovered = 0; - private final HoveredIterable hoveredWidgets; + public static GuiContext getDefault() { + return ClientScreenHandler.getBestContext(); + } - private LocatedElement draggable; - private int lastButton = -1; - private long lastClickTime = 0; - private int lastDragX, lastDragY; + private final Area screenArea = new Area(); /* Mouse states */ private int mouseX; @@ -53,282 +43,24 @@ public class GuiContext extends GuiViewportStack { /* Render states */ private float partialTicks; - private long tick; - - public List> postRenderCallbacks = new ArrayList<>(); - - private JeiSettingsImpl jeiSettings; - - public GuiContext(ModularScreen screen) { - this.screen = screen; - this.hoveredWidgets = new HoveredIterable(this.screen.getPanelManager());; - } + private long tick = 0; - public ModularScreen getScreen() { - return screen; - } - - /** - * @return true the mouse is anywhere above the widget - */ public boolean isAbove(IGuiElement widget) { - return widget.getArea().isInside(this.mouseX, this.mouseY); - } - - /** - * @return true if any widget is being hovered - */ - public boolean isHovered() { - return this.hovered != null; - } - - /** - * @return true if the widget is directly below the mouse - */ - public boolean isHovered(IGuiElement guiElement) { - return isHovered() && this.hovered == guiElement; - } - - /** - * Checks if a widget is hovered for a certain amount of ticks - * - * @param guiElement widget - * @param ticks time hovered - * @return true if the widget is hovered for at least a certain number of ticks - */ - public boolean isHoveredFor(IGuiElement guiElement, int ticks) { - return isHovered(guiElement) && this.timeHovered / 3 >= ticks; - } - - /** - * @return the hovered widget (widget directly below the mouse) - */ - @Nullable - public IGuiElement getHovered() { - return this.hovered; + return isMouseAbove(widget.getArea()); } /** - * @return all widgets which are below the mouse ({@link #isAbove(IGuiElement)} is true) - */ - public Iterable getAllBelowMouse() { - return this.hoveredWidgets; - } - - /** - * @return true if there is any focused widget - */ - public boolean isFocused() { - return this.focusedWidget.getElement() != null; - } - - /* Element focusing */ - - /** - * @return true if there is any focused widget - */ - public boolean isFocused(IFocusedWidget widget) { - return this.focusedWidget.getElement() == widget; - } - - public LocatedWidget getFocusedWidget() { - return this.focusedWidget; - } - - /** - * Tries to focus the given widget - * - * @param widget widget to focus - */ - public void focus(IFocusedWidget widget) { - focus(LocatedWidget.of((IWidget) widget)); - } - - /** - * Tries to focus the given widget - * - * @param widget widget to focus - */ - public void focus(@NotNull LocatedWidget widget) { - if (this.focusedWidget.getElement() == widget.getElement()) { - return; - } - - if (widget.getElement() != null && !(widget.getElement() instanceof IFocusedWidget)) { - throw new IllegalArgumentException(); - } - - if (this.focusedWidget.getElement() != null) { - IFocusedWidget focusedWidget = (IFocusedWidget) this.focusedWidget.getElement(); - focusedWidget.onRemoveFocus(this); - this.screen.setFocused(false); - } - - this.focusedWidget = widget; - - if (this.focusedWidget.getElement() != null) { - IFocusedWidget focusedWidget = (IFocusedWidget) this.focusedWidget.getElement(); - focusedWidget.onFocus(this); - this.screen.setFocused(true); - } - } - - /** - * Removes focus from any widget - */ - public void removeFocus() { - focus(LocatedWidget.EMPTY); - } - - /** - * Tries to find the next focusable widget. - * - * @param parent focusable context - * @return true if successful - */ - public boolean focusNext(IWidget parent) { - return focus(parent, -1, 1); - } - - /** - * Tries to find the previous focusable widget. - * - * @param parent focusable context - * @return true if successful + * @return true the mouse is anywhere above the widget */ - public boolean focusPrevious(IWidget parent) { - return focus(parent, -1, -1); - } - - public boolean focus(IWidget parent, int index, int factor) { - return focus(parent, index, factor, false); + public boolean isMouseAbove(IGuiElement widget) { + return isMouseAbove(widget.getArea()); } /** - * Focus next focusable GUI element + * @return true the mouse is anywhere above the area */ - public boolean focus(IWidget widget, int index, int factor, boolean stop) { - List children = widget.getChildren(); - - factor = factor >= 0 ? 1 : -1; - index += factor; - - for (; index >= 0 && index < children.size(); index += factor) { - IWidget child = children.get(index); - - if (!child.isEnabled()) { - continue; - } - - if (child instanceof IFocusedWidget focusedWidget1) { - focus(focusedWidget1); - - return true; - } else { - int start = factor > 0 ? -1 : child.getChildren().size(); - - if (focus(child, start, factor, true)) { - return true; - } - } - } - - IWidget grandparent = widget.getParent(); - boolean isRoot = grandparent instanceof ModularPanel; - - if (!stop && (isRoot || grandparent.canBeSeen(this))) { - List siblings = grandparent.getChildren(); - if (focus(grandparent, siblings.indexOf(widget), factor)) { - return true; - } - if (isRoot) { - return focus(grandparent, factor > 0 ? -1 : siblings.size() - 1, factor); - } - } - - return false; - } - - /* draggable */ - - public boolean hasDraggable() { - return this.draggable != null; - } - - public boolean isMouseItemEmpty() { - EntityPlayerSP player = MCHelper.getPlayer(); - return player == null || player.inventory.getItemStack().isEmpty(); - } - - @ApiStatus.Internal - public boolean onMousePressed(int button) { - if ((button == 0 || button == 1) && isMouseItemEmpty() && hasDraggable()) { - dropDraggable(); - return true; - } - return false; - } - - @ApiStatus.Internal - public boolean onMouseReleased(int button) { - if (button == this.lastButton && isMouseItemEmpty() && hasDraggable()) { - long time = Minecraft.getSystemTime(); - if (time - this.lastClickTime < 200) return false; - dropDraggable(); - return true; - } - return false; - } - - @ApiStatus.Internal - public void dropDraggable() { - this.draggable.applyMatrix(this); - this.draggable.getElement().onDragEnd(this.draggable.getElement().canDropHere(getAbsMouseX(), getAbsMouseY(), this.hovered)); - this.draggable.getElement().setMoving(false); - this.draggable.unapplyMatrix(this); - this.draggable = null; - this.lastButton = -1; - this.lastClickTime = 0; - } - - @ApiStatus.Internal - public boolean onHoveredClick(int button, LocatedWidget hovered) { - if ((button == 0 || button == 1) && isMouseItemEmpty() && !hasDraggable()) { - IWidget widget = hovered.getElement(); - LocatedElement draggable; - if (widget instanceof IDraggable iDraggable) { - draggable = new LocatedElement<>(iDraggable, hovered.getTransformationMatrix()); - } else if (widget instanceof ModularPanel panel) { - if (panel.isDraggable()) { - if (!panel.flex().hasFixedSize()) { - throw new IllegalStateException("Panel must have a fixed size. It can't specify left AND right or top AND bottom!"); - } - draggable = new LocatedElement<>(new DraggablePanelWrapper(panel), TransformationMatrix.EMPTY); - } else { - return false; - } - } else { - return false; - } - if (draggable.getElement().onDragStart(button)) { - draggable.getElement().setMoving(true); - - this.draggable = draggable; - this.lastButton = button; - this.lastClickTime = Minecraft.getSystemTime(); - return true; - } - } - return false; - } - - @ApiStatus.Internal - public void drawDraggable() { - if (hasDraggable()) { - this.draggable.applyMatrix(this); - this.draggable.getElement().drawMovingState(this, this.partialTicks); - this.draggable.unapplyMatrix(this); - } + public boolean isMouseAbove(Area area) { + return area.isInside(this.mouseX, this.mouseY); } @ApiStatus.Internal @@ -347,39 +79,30 @@ public void updateEventState() { } @ApiStatus.Internal - public void onFrameUpdate() { - updateEventState(); - IGuiElement hovered = this.screen.getPanelManager().getTopWidget(); - if (hasDraggable() && (this.lastDragX != this.mouseX || this.lastDragY != this.mouseY)) { - this.lastDragX = this.mouseX; - this.lastDragY = this.mouseY; - this.draggable.applyMatrix(this); - this.draggable.getElement().onDrag(this.lastButton, this.lastClickTime); - this.draggable.unapplyMatrix(this); - } - if (this.hovered != hovered) { - if (this.hovered != null) { - this.hovered.onMouseEndHover(); - } - this.hovered = hovered; - this.timeHovered = 0; - if (this.hovered != null) { - this.hovered.onMouseStartHover(); - if (this.hovered instanceof IVanillaSlot vanillaSlot) { - this.screen.getScreenWrapper().setHoveredSlot(vanillaSlot.getVanillaSlot()); - } else { - this.screen.getScreenWrapper().setHoveredSlot(null); - } - } - } else { - this.timeHovered++; - } + public void updateScreenArea(int w, int h) { + this.screenArea.set(0, 0, w, h); + this.screenArea.rx = 0; + this.screenArea.ry = 0; + } + + @SideOnly(Side.CLIENT) + public Minecraft getMC() { + return Minecraft.getMinecraft(); + } + + @SideOnly(Side.CLIENT) + public FontRenderer getFontRenderer() { + return MCHelper.getFontRenderer(); } public void tick() { this.tick += 1; } + public Area getScreenArea() { + return screenArea; + } + public long getTick() { return this.tick; } @@ -387,11 +110,11 @@ public long getTick() { /* Viewport */ public int getMouseX() { - return transformX(this.mouseX, this.mouseY); + return unTransformX(this.mouseX, this.mouseY); } public int getMouseY() { - return transformY(this.mouseX, this.mouseY); + return unTransformY(this.mouseX, this.mouseY); } public int getMouse(GuiAxis axis) { @@ -418,14 +141,6 @@ public int getAbsMouse(GuiAxis axis) { return axis.isHorizontal() ? getAbsMouseX() : getAbsMouseY(); } - public int unTransformMouseX() { - return unTransformX(getAbsMouseX(), getAbsMouseY()); - } - - public int unTransformMouseY() { - return unTransformY(getAbsMouseX(), getAbsMouseY()); - } - public int getMouseButton() { return this.mouseButton; } @@ -446,63 +161,11 @@ public float getPartialTicks() { return this.partialTicks; } - public ITheme getTheme() { - return this.screen.getCurrentTheme(); - } - - public JeiSettingsImpl getJeiSettings() { - if (this.screen.isOverlay()) { - throw new IllegalStateException("Overlays don't have JEI settings!"); - } - if (this.jeiSettings == null) { - throw new IllegalStateException("The screen is not yet initialised!"); - } - return this.jeiSettings; - } - - @ApiStatus.Internal - public void setJeiSettings(JeiSettingsImpl jeiSettings) { - if (this.jeiSettings != null) { - throw new IllegalStateException("Tried to set jei settings twice"); - } - this.jeiSettings = jeiSettings; + public boolean isMuiContext() { + return false; } - private static class HoveredIterable implements Iterable { - - private final PanelManager panelManager; - - private HoveredIterable(PanelManager panelManager) { - this.panelManager = panelManager; - } - - @NotNull - @Override - public Iterator iterator() { - return new Iterator() { - - private final Iterator panelIt = HoveredIterable.this.panelManager.getOpenPanels().iterator(); - private Iterator widgetIt; - - @Override - public boolean hasNext() { - if (this.widgetIt == null) { - if (!this.panelIt.hasNext()) { - return false; - } - this.widgetIt = this.panelIt.next().getHovering().iterator(); - } - return this.widgetIt.hasNext(); - } - - @Override - public IGuiElement next() { - if (this.widgetIt == null || !this.widgetIt.hasNext()) { - this.widgetIt = this.panelIt.next().getHovering().iterator(); - } - return this.widgetIt.next().getElement(); - } - }; - } + public ModularGuiContext getMuiContext() { + throw new UnsupportedOperationException("This is not a MuiContext"); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java index 1be5ff15..02ef330e 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiViewportStack.java @@ -15,10 +15,9 @@ import java.util.ListIterator; /** - * Viewport stack - *

- * This class is responsible for calculating and keeping track of - * embedded (into each other) scrolling areas + * This class is a matrix stack aka pose stack. It keeps track of widget transformations (including position) + * and can apply these transformations to OpenGL for rendering. + * This is mainly used, but not limited to properly displacing widgets in a scroll area. */ public class GuiViewportStack implements IViewportStack { diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java new file mode 100644 index 00000000..70ebc5a7 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java @@ -0,0 +1,409 @@ +package com.cleanroommc.modularui.screen.viewport; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.MCHelper; +import com.cleanroommc.modularui.api.widget.*; +import com.cleanroommc.modularui.screen.*; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityPlayerSP; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; + +/** + * This class contains all the info from {@link GuiContext} and additional MUI specific info like the current {@link ModularScreen}, + * current hovered widget, current dragged widget, current focused widget and JEI settings. + * An instance can only be obtained from {@link ModularScreen#getContext()}. One instance is created every time a {@link ModularScreen} + * is created. + */ +public class ModularGuiContext extends GuiContext { + + /* GUI elements */ + @Deprecated + public final ModularScreen screen; + private LocatedWidget focusedWidget = LocatedWidget.EMPTY; + @Nullable + private IGuiElement hovered; + private int timeHovered = 0; + private final HoveredIterable hoveredWidgets; + + private LocatedElement draggable; + private int lastButton = -1; + private long lastClickTime = 0; + private int lastDragX, lastDragY; + + public List> postRenderCallbacks = new ArrayList<>(); + + private JeiSettingsImpl jeiSettings; + + public ModularGuiContext(ModularScreen screen) { + this.screen = screen; + this.hoveredWidgets = new HoveredIterable(this.screen.getPanelManager()); + } + + public ModularScreen getScreen() { + return screen; + } + + /** + * @return true if any widget is being hovered + */ + public boolean isHovered() { + return this.hovered != null; + } + + /** + * @return true if the widget is directly below the mouse + */ + public boolean isHovered(IGuiElement guiElement) { + return isHovered() && this.hovered == guiElement; + } + + /** + * Checks if a widget is hovered for a certain amount of ticks + * + * @param guiElement widget + * @param ticks time hovered + * @return true if the widget is hovered for at least a certain number of ticks + */ + public boolean isHoveredFor(IGuiElement guiElement, int ticks) { + return isHovered(guiElement) && this.timeHovered / 3 >= ticks; + } + + /** + * @return the hovered widget (widget directly below the mouse) + */ + @Nullable + public IGuiElement getHovered() { + return this.hovered; + } + + /** + * @return all widgets which are below the mouse ({@link GuiContext#isAbove(IGuiElement)} is true) + */ + public Iterable getAllBelowMouse() { + return this.hoveredWidgets; + } + + /** + * @return true if there is any focused widget + */ + public boolean isFocused() { + return this.focusedWidget.getElement() != null; + } + + /* Element focusing */ + + /** + * @return true if there is any focused widget + */ + public boolean isFocused(IFocusedWidget widget) { + return this.focusedWidget.getElement() == widget; + } + + public LocatedWidget getFocusedWidget() { + return this.focusedWidget; + } + + /** + * Tries to focus the given widget + * + * @param widget widget to focus + */ + public void focus(IFocusedWidget widget) { + focus(LocatedWidget.of((IWidget) widget)); + } + + /** + * Tries to focus the given widget + * + * @param widget widget to focus + */ + public void focus(@NotNull LocatedWidget widget) { + if (this.focusedWidget.getElement() == widget.getElement()) { + return; + } + + if (widget.getElement() != null && !(widget.getElement() instanceof IFocusedWidget)) { + throw new IllegalArgumentException(); + } + + if (this.focusedWidget.getElement() != null) { + IFocusedWidget focusedWidget = (IFocusedWidget) this.focusedWidget.getElement(); + focusedWidget.onRemoveFocus(this); + this.screen.setFocused(false); + } + + this.focusedWidget = widget; + + if (this.focusedWidget.getElement() != null) { + IFocusedWidget focusedWidget = (IFocusedWidget) this.focusedWidget.getElement(); + focusedWidget.onFocus(this); + this.screen.setFocused(true); + } + } + + /** + * Removes focus from any widget + */ + public void removeFocus() { + focus(LocatedWidget.EMPTY); + } + + /** + * Tries to find the next focusable widget. + * + * @param parent focusable context + * @return true if successful + */ + public boolean focusNext(IWidget parent) { + return focus(parent, -1, 1); + } + + /** + * Tries to find the previous focusable widget. + * + * @param parent focusable context + * @return true if successful + */ + public boolean focusPrevious(IWidget parent) { + return focus(parent, -1, -1); + } + + public boolean focus(IWidget parent, int index, int factor) { + return focus(parent, index, factor, false); + } + + /** + * Focus next focusable GUI element + */ + public boolean focus(IWidget widget, int index, int factor, boolean stop) { + List children = widget.getChildren(); + + factor = factor >= 0 ? 1 : -1; + index += factor; + + for (; index >= 0 && index < children.size(); index += factor) { + IWidget child = children.get(index); + + if (!child.isEnabled()) { + continue; + } + + if (child instanceof IFocusedWidget focusedWidget1) { + focus(focusedWidget1); + + return true; + } else { + int start = factor > 0 ? -1 : child.getChildren().size(); + + if (focus(child, start, factor, true)) { + return true; + } + } + } + + IWidget grandparent = widget.getParent(); + boolean isRoot = grandparent instanceof ModularPanel; + + if (!stop && (isRoot || grandparent.canBeSeen(this))) { + List siblings = grandparent.getChildren(); + if (focus(grandparent, siblings.indexOf(widget), factor)) { + return true; + } + if (isRoot) { + return focus(grandparent, factor > 0 ? -1 : siblings.size() - 1, factor); + } + } + + return false; + } + + /* draggable */ + + public boolean hasDraggable() { + return this.draggable != null; + } + + public boolean isMouseItemEmpty() { + EntityPlayerSP player = MCHelper.getPlayer(); + return player == null || player.inventory.getItemStack().isEmpty(); + } + + @ApiStatus.Internal + public boolean onMousePressed(int button) { + if ((button == 0 || button == 1) && isMouseItemEmpty() && hasDraggable()) { + dropDraggable(); + return true; + } + return false; + } + + @ApiStatus.Internal + public boolean onMouseReleased(int button) { + if (button == this.lastButton && isMouseItemEmpty() && hasDraggable()) { + long time = Minecraft.getSystemTime(); + if (time - this.lastClickTime < 200) return false; + dropDraggable(); + return true; + } + return false; + } + + @ApiStatus.Internal + public void dropDraggable() { + this.draggable.applyMatrix(this); + this.draggable.getElement().onDragEnd(this.draggable.getElement().canDropHere(getAbsMouseX(), getAbsMouseY(), this.hovered)); + this.draggable.getElement().setMoving(false); + this.draggable.unapplyMatrix(this); + this.draggable = null; + this.lastButton = -1; + this.lastClickTime = 0; + } + + @ApiStatus.Internal + public boolean onHoveredClick(int button, LocatedWidget hovered) { + if ((button == 0 || button == 1) && isMouseItemEmpty() && !hasDraggable()) { + IWidget widget = hovered.getElement(); + LocatedElement draggable; + if (widget instanceof IDraggable iDraggable) { + draggable = new LocatedElement<>(iDraggable, hovered.getTransformationMatrix()); + } else if (widget instanceof ModularPanel panel) { + if (panel.isDraggable()) { + if (!panel.flex().hasFixedSize()) { + throw new IllegalStateException("Panel must have a fixed size. It can't specify left AND right or top AND bottom!"); + } + draggable = new LocatedElement<>(new DraggablePanelWrapper(panel), TransformationMatrix.EMPTY); + } else { + return false; + } + } else { + return false; + } + if (draggable.getElement().onDragStart(button)) { + draggable.getElement().setMoving(true); + + this.draggable = draggable; + this.lastButton = button; + this.lastClickTime = Minecraft.getSystemTime(); + return true; + } + } + return false; + } + + @ApiStatus.Internal + public void drawDraggable() { + if (hasDraggable()) { + this.draggable.applyMatrix(this); + this.draggable.getElement().drawMovingState(this, getPartialTicks()); + this.draggable.unapplyMatrix(this); + } + } + + @ApiStatus.Internal + public void onFrameUpdate() { + IGuiElement hovered = this.screen.getPanelManager().getTopWidget(); + if (hasDraggable() && (this.lastDragX != getAbsMouseX() || this.lastDragY != getAbsMouseY())) { + this.lastDragX = getAbsMouseX(); + this.lastDragY = getAbsMouseY(); + this.draggable.applyMatrix(this); + this.draggable.getElement().onDrag(this.lastButton, this.lastClickTime); + this.draggable.unapplyMatrix(this); + } + if (this.hovered != hovered) { + if (this.hovered != null) { + this.hovered.onMouseEndHover(); + } + this.hovered = hovered; + this.timeHovered = 0; + if (this.hovered != null) { + this.hovered.onMouseStartHover(); + if (this.hovered instanceof IVanillaSlot vanillaSlot) { + this.screen.getScreenWrapper().setHoveredSlot(vanillaSlot.getVanillaSlot()); + } else { + this.screen.getScreenWrapper().setHoveredSlot(null); + } + } + } else { + this.timeHovered++; + } + } + + public ITheme getTheme() { + return this.screen.getCurrentTheme(); + } + + @Override + public boolean isMuiContext() { + return true; + } + + @Override + public ModularGuiContext getMuiContext() { + return this; + } + + public JeiSettingsImpl getJeiSettings() { + if (this.screen.isOverlay()) { + throw new IllegalStateException("Overlays don't have JEI settings!"); + } + if (this.jeiSettings == null) { + throw new IllegalStateException("The screen is not yet initialised!"); + } + return this.jeiSettings; + } + + @ApiStatus.Internal + public void setJeiSettings(JeiSettingsImpl jeiSettings) { + if (this.jeiSettings != null) { + throw new IllegalStateException("Tried to set jei settings twice"); + } + this.jeiSettings = jeiSettings; + } + + private static class HoveredIterable implements Iterable { + + private final PanelManager panelManager; + + private HoveredIterable(PanelManager panelManager) { + this.panelManager = panelManager; + } + + @NotNull + @Override + public Iterator iterator() { + return new Iterator<>() { + + private final Iterator panelIt = HoveredIterable.this.panelManager.getOpenPanels().iterator(); + private Iterator widgetIt; + + @Override + public boolean hasNext() { + if (this.widgetIt == null) { + if (!this.panelIt.hasNext()) { + return false; + } + this.widgetIt = this.panelIt.next().getHovering().iterator(); + } + return this.widgetIt.hasNext(); + } + + @Override + public IGuiElement next() { + if (this.widgetIt == null || !this.widgetIt.hasNext()) { + this.widgetIt = this.panelIt.next().getHovering().iterator(); + } + return this.widgetIt.next().getElement(); + } + }; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java index 55def5c3..cf2473c6 100644 --- a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java +++ b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java @@ -8,7 +8,7 @@ import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.widgets.ButtonWidget; import com.cleanroommc.modularui.widgets.TextWidget; @@ -52,7 +52,7 @@ public static void init() { return new CustomModularScreen() { @Override - public @NotNull ModularPanel buildUI(GuiContext context) { + public @NotNull ModularPanel buildUI(ModularGuiContext context) { return ModularPanel.defaultPanel("watermark_overlay", gui.getXSize(), gui.getYSize()) .pos(gui.getGuiLeft(), gui.getGuiTop()) .background(IDrawable.EMPTY) diff --git a/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java b/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java index 73702fd3..0631a5b5 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java +++ b/src/main/java/com/cleanroommc/modularui/test/ResizerTest.java @@ -1,18 +1,24 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.ItemDrawable; import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.viewport.GuiContext; -import com.cleanroommc.modularui.utils.fakeworld.ArraySchema; -import com.cleanroommc.modularui.utils.fakeworld.ISchema; -import com.cleanroommc.modularui.widgets.SchemaWidget; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.widgets.RichTextWidget; + +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.TextFormatting; import org.jetbrains.annotations.NotNull; public class ResizerTest extends CustomModularScreen { @Override - public @NotNull ModularPanel buildUI(GuiContext context) { + public @NotNull ModularPanel buildUI(ModularGuiContext context) { /*TextureAtlasSprite sprite = SpriteHelper.getSpriteOfBlockState(GameObjectHelper.getBlockState("minecraft", "command_block"), EnumFacing.UP); //SpriteHelper.getSpriteOfItem(new ItemStack(Items.DIAMOND)); return ModularPanel.defaultPanel("main") @@ -46,7 +52,7 @@ public class ResizerTest extends CustomModularScreen { .add(new BlockPos(0, 3, 0), Blocks.BEACON.getDefaultState()) .build();*/ - ISchema schema = ArraySchema.builder() + /*ISchema schema = ArraySchema.builder() .layer("D D", " ", " ", " ") .layer(" DDD ", " E E ", " ", " ") .layer(" DDD ", " E ", " G ", " B ") @@ -64,7 +70,37 @@ public class ResizerTest extends CustomModularScreen { .child(new SchemaWidget.LayerButton(schema, 0, 3) .bottom(1) .left(1) - .size(16)); - return panel; + .size(16));*/ + + return new ModularPanel("main") + .size(176, 166) + .child(new RichTextWidget() + .sizeRel(1f).margin(7) + .autoUpdate(true) + .textBuilder(text -> text.add("Hello ") + .add(new ItemDrawable(new ItemStack(Blocks.GRASS)) + .asIcon() + .asHoverable() + .tooltip(richTooltip -> richTooltip.addFromItem(new ItemStack(Blocks.GRASS)))) + .add(", nice to ") + .add(new ItemDrawable(new ItemStack(Items.PORKCHOP)) + .asIcon() + .asInteractable() + .onMousePressed(button -> { + ModularUI.LOGGER.info("Pressed Pork"); + return true; + })) + .add(" you. ") + .add(TextFormatting.GREEN + "This is a long ") + .add(IKey.str("string").format(TextFormatting.DARK_PURPLE) + .asTextIcon() + .asHoverable() + .addTooltipLine("Text Tooltip")) + .add(" of characters" + TextFormatting.RESET) + .add(" and not numbers as some might think...") + .newLine() + .add("") + .textShadow(false) + )); } } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGui.java b/src/main/java/com/cleanroommc/modularui/test/TestGui.java index c1fca147..b1aa6b65 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGui.java @@ -7,7 +7,7 @@ import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widgets.ButtonWidget; import com.cleanroommc.modularui.widgets.SortableListWidget; @@ -39,7 +39,7 @@ public void onClose() { } @Override - public @NotNull ModularPanel buildUI(GuiContext context) { + public @NotNull ModularPanel buildUI(ModularGuiContext context) { if (this.lines == null) { this.lines = IntStream.range(0, 20).mapToObj(i -> "Option " + (i + 1)).collect(Collectors.toList()); this.configuredOptions = this.lines; diff --git a/src/main/java/com/cleanroommc/modularui/test/TestItem.java b/src/main/java/com/cleanroommc/modularui/test/TestItem.java index 2b720743..e61f93aa 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestItem.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestItem.java @@ -3,7 +3,6 @@ import com.cleanroommc.modularui.api.IGuiHolder; import com.cleanroommc.modularui.factory.GuiFactories; import com.cleanroommc.modularui.factory.HandGuiData; -import com.cleanroommc.modularui.factory.ItemGuiFactory; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.ItemCapabilityProvider; @@ -15,8 +14,8 @@ import com.cleanroommc.modularui.widgets.SlotGroupWidget; import com.cleanroommc.modularui.widgets.layout.Column; +import net.minecraft.client.util.ITooltipFlag; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -32,6 +31,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; import javax.annotation.Nonnull; public class TestItem extends Item implements IGuiHolder { @@ -81,4 +81,10 @@ public ICapabilityProvider initCapabilities(@NotNull ItemStack stack, @Nullable } }; } + + @Override + public void addInformation(ItemStack stack, @Nullable World worldIn, List tooltip, ITooltipFlag flagIn) { + super.addInformation(stack, worldIn, tooltip, flagIn); + tooltip.add("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."); + } } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index c4007a24..bdb37d87 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -3,11 +3,15 @@ import com.cleanroommc.modularui.api.IGuiHolder; import com.cleanroommc.modularui.api.IPanelHandler; import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.drawable.*; +import com.cleanroommc.modularui.drawable.Circle; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.drawable.ItemDrawable; +import com.cleanroommc.modularui.drawable.Rectangle; +import com.cleanroommc.modularui.drawable.text.AnimatedText; import com.cleanroommc.modularui.factory.PosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.Tooltip; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.Color; @@ -140,7 +144,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager guiSyncManager) .asIcon() .size(20)) .addLine(new ItemDrawable(new ItemStack(Items.DIAMOND)).asIcon()) - .pos(Tooltip.Pos.LEFT); + .pos(RichTooltip.Pos.LEFT); }) .onMousePressed(mouseButton -> { //panel.getScreen().close(true); @@ -417,7 +421,7 @@ private SpecialButton(AnimatedText animatedKey) { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { this.animatedKey.draw(context, 0, 0, getArea().w(), getArea().h(), widgetTheme); } diff --git a/src/main/java/com/cleanroommc/modularui/test/TransformationTestGui.java b/src/main/java/com/cleanroommc/modularui/test/TransformationTestGui.java index e5d76706..9372acd2 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TransformationTestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TransformationTestGui.java @@ -4,7 +4,7 @@ import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.Interpolation; import com.cleanroommc.modularui.widget.Widget; @@ -16,7 +16,7 @@ public class TransformationTestGui extends CustomModularScreen { @Override - public @NotNull ModularPanel buildUI(GuiContext context) { + public @NotNull ModularPanel buildUI(ModularGuiContext context) { return new TestPanel("test") .child(new Widget<>() .align(Alignment.Center) diff --git a/src/main/java/com/cleanroommc/modularui/theme/AbstractDefaultTheme.java b/src/main/java/com/cleanroommc/modularui/theme/AbstractDefaultTheme.java index f0b820f5..af7283fd 100644 --- a/src/main/java/com/cleanroommc/modularui/theme/AbstractDefaultTheme.java +++ b/src/main/java/com/cleanroommc/modularui/theme/AbstractDefaultTheme.java @@ -2,7 +2,7 @@ import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.ITheme; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; public abstract class AbstractDefaultTheme implements ITheme { @@ -77,7 +77,7 @@ public boolean getSmoothProgressBarOverride() { } @Override - public Tooltip.Pos getTooltipPosOverride() { + public RichTooltip.Pos getTooltipPosOverride() { return ModularUIConfig.tooltipPos; } } diff --git a/src/main/java/com/cleanroommc/modularui/theme/Theme.java b/src/main/java/com/cleanroommc/modularui/theme/Theme.java index 70180609..19a2f86f 100644 --- a/src/main/java/com/cleanroommc/modularui/theme/Theme.java +++ b/src/main/java/com/cleanroommc/modularui/theme/Theme.java @@ -3,7 +3,7 @@ import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.IThemeApi; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -33,7 +33,7 @@ public class Theme implements ITheme { private int openCloseAnimationOverride = -1; private Boolean smoothProgressBarOverride = null; - private Tooltip.Pos tooltipPosOverride = null; + private RichTooltip.Pos tooltipPosOverride = null; Theme(String id, ITheme parent, Map widgetThemes) { this.id = id; @@ -72,7 +72,7 @@ void setSmoothProgressBarOverride(boolean smooth) { this.smoothProgressBarOverride = smooth; } - void setTooltipPosOverride(Tooltip.Pos pos) { + void setTooltipPosOverride(RichTooltip.Pos pos) { this.tooltipPosOverride = pos; } @@ -139,7 +139,7 @@ public boolean getSmoothProgressBarOverride() { } @Override - public Tooltip.Pos getTooltipPosOverride() { + public RichTooltip.Pos getTooltipPosOverride() { if (this.tooltipPosOverride != null) { return this.tooltipPosOverride; } diff --git a/src/main/java/com/cleanroommc/modularui/theme/ThemeManager.java b/src/main/java/com/cleanroommc/modularui/theme/ThemeManager.java index 5dfef5f5..6ed21d39 100644 --- a/src/main/java/com/cleanroommc/modularui/theme/ThemeManager.java +++ b/src/main/java/com/cleanroommc/modularui/theme/ThemeManager.java @@ -2,7 +2,8 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.ITheme; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.utils.ObjectList; import com.cleanroommc.modularui.utils.*; import net.minecraft.client.resources.IResource; @@ -18,11 +19,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectIterator; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.objects.*; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -303,7 +300,7 @@ private Theme deserialize() { } if (jsonBuilder.getJson().has("tooltipPos")) { String posName = jsonBuilder.getJson().get("tooltipPos").getAsString(); - theme.setTooltipPosOverride(Tooltip.Pos.valueOf(posName)); + theme.setTooltipPosOverride(RichTooltip.Pos.valueOf(posName)); } return theme; } diff --git a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java index 25c4aac8..8aa1ff78 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java +++ b/src/main/java/com/cleanroommc/modularui/utils/math/MathBuilder.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Math builder @@ -41,6 +42,8 @@ */ public class MathBuilder { + public static final Pattern DECIMAL_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?([eE]-?\\d+)?"); + public static final MathBuilder INSTANCE = new MathBuilder(); /** @@ -565,6 +568,6 @@ protected boolean isOperator(String s) { * number) */ protected boolean isDecimal(String s) { - return s.matches("^-?\\d+(\\.\\d+)?$"); + return DECIMAL_PATTERN.matcher(s).matches(); } } \ No newline at end of file diff --git a/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java b/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java index d1308a12..47be349a 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java @@ -6,7 +6,7 @@ import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.DraggablePanelWrapper; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.HoveredWidgetList; import com.cleanroommc.modularui.widget.sizer.Area; @@ -32,7 +32,7 @@ public void onInit() { } @Override - public void drawMovingState(GuiContext context, float partialTicks) { + public void drawMovingState(ModularGuiContext context, float partialTicks) { if (this.parentDraggable != null) { this.parentDraggable.drawMovingState(context, partialTicks); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java index 4426e91d..bcadebb9 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java @@ -3,7 +3,7 @@ import com.cleanroommc.modularui.api.layout.IViewport; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IDraggable; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.HoveredWidgetList; import com.cleanroommc.modularui.widget.sizer.Area; @@ -25,7 +25,7 @@ public DraggableWidget() { } @Override - public void drawMovingState(GuiContext context, float partialTicks) { + public void drawMovingState(ModularGuiContext context, float partialTicks) { WidgetTree.drawTree(this, context, true); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java index 7e65daab..64a88e8f 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java @@ -5,7 +5,7 @@ import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Flex; @@ -40,19 +40,19 @@ public boolean isValid() { } @Override - public void drawBackground(GuiContext context, WidgetTheme widgetTheme) { + public void drawBackground(ModularGuiContext context, WidgetTheme widgetTheme) { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { } @Override - public void drawOverlay(GuiContext context, WidgetTheme widgetTheme) { + public void drawOverlay(ModularGuiContext context, WidgetTheme widgetTheme) { } @Override - public void drawForeground(GuiContext context) { + public void drawForeground(ModularGuiContext context) { } @Override @@ -93,7 +93,7 @@ public void markTooltipDirty() { } @Override - public GuiContext getContext() { + public ModularGuiContext getContext() { return this.parent.getContext(); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java b/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java index 7e13a3b6..2647c3a3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/ScrollWidget.java @@ -6,7 +6,7 @@ import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.HoveredWidgetList; import com.cleanroommc.modularui.widget.scroll.HorizontalScrollData; import com.cleanroommc.modularui.widget.scroll.ScrollArea; @@ -87,7 +87,7 @@ public boolean canHover() { @Override public @NotNull Result onMousePressed(int mouseButton) { - GuiContext context = getContext(); + ModularGuiContext context = getContext(); if (this.scroll.mouseClicked(context)) { return Result.STOP; } @@ -112,14 +112,14 @@ public void onUpdate() { } @Override - public void preDraw(GuiContext context, boolean transformed) { + public void preDraw(ModularGuiContext context, boolean transformed) { if (!transformed) { Stencil.applyAtZero(this.scroll, context); } } @Override - public void postDraw(GuiContext context, boolean transformed) { + public void postDraw(ModularGuiContext context, boolean transformed) { if (!transformed) { Stencil.remove(); this.scroll.drawScrollbar(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 73d3bd60..086432aa 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -9,8 +9,8 @@ import com.cleanroommc.modularui.factory.GuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.Tooltip; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.value.sync.ModularSyncManager; import com.cleanroommc.modularui.value.sync.SyncHandler; @@ -39,7 +39,7 @@ public class Widget> implements IWidget, IPositioned, ITo private boolean valid = false; private IWidget parent = null; private ModularPanel panel = null; - private GuiContext context = null; + private ModularGuiContext context = null; // sizing private final Area area = new Area(); private final Flex flex = new Flex(this); @@ -53,7 +53,7 @@ public class Widget> implements IWidget, IPositioned, ITo @Nullable private IDrawable overlay = null; @Nullable private IDrawable hoverBackground = null; @Nullable private IDrawable hoverOverlay = null; - @Nullable private Tooltip tooltip; + @Nullable private RichTooltip tooltip; @Nullable private String widgetThemeOverride = null; // listener @Nullable private List guiActionListeners; @@ -144,7 +144,7 @@ public void dispose() { // ----------------- @Override - public void drawBackground(GuiContext context, WidgetTheme widgetTheme) { + public void drawBackground(ModularGuiContext context, WidgetTheme widgetTheme) { IDrawable bg = getCurrentBackground(context.getTheme(), widgetTheme); if (bg != null) { bg.drawAtZero(context, getArea(), widgetTheme); @@ -152,10 +152,10 @@ public void drawBackground(GuiContext context, WidgetTheme widgetTheme) { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) {} + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) {} @Override - public void drawOverlay(GuiContext context, WidgetTheme widgetTheme) { + public void drawOverlay(ModularGuiContext context, WidgetTheme widgetTheme) { IDrawable bg = getCurrentOverlay(context.getTheme(), widgetTheme); if (bg != null) { Box padding = getArea().getPadding(); @@ -164,10 +164,10 @@ public void drawOverlay(GuiContext context, WidgetTheme widgetTheme) { } @Override - public void drawForeground(GuiContext context) { - Tooltip tooltip = getTooltip(); + public void drawForeground(ModularGuiContext context) { + RichTooltip tooltip = getTooltip(); if (tooltip != null && isHoveringFor(tooltip.getShowUpTimer())) { - tooltip.draw(getContext()); + tooltip.draw(context); } } @@ -204,18 +204,24 @@ public IDrawable getCurrentOverlay(ITheme theme, WidgetTheme widgetTheme) { @Nullable @Override - public Tooltip getTooltip() { + public RichTooltip getTooltip() { return this.tooltip; } @Override - public @NotNull Tooltip tooltip() { + public @NotNull RichTooltip tooltip() { if (this.tooltip == null) { - this.tooltip = new Tooltip(this); + this.tooltip = new RichTooltip(this); } return this.tooltip; } + @Override + public W tooltip(RichTooltip tooltip) { + this.tooltip = tooltip; + return getThis(); + } + @Override public void markTooltipDirty() { if (this.tooltip != null) { @@ -383,7 +389,7 @@ public ModularScreen getScreen() { } @Override - public GuiContext getContext() { + public ModularGuiContext getContext() { if (!isValid()) { throw new IllegalStateException(getClass().getSimpleName() + " is not in a valid state!"); } @@ -391,7 +397,7 @@ public GuiContext getContext() { } @ApiStatus.Internal - protected final void setContext(GuiContext context) { + protected final void setContext(ModularGuiContext context) { this.context = context; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index dd6ca3d4..012c4645 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -9,7 +9,7 @@ import com.cleanroommc.modularui.api.widget.ISynced; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.ObjectList; import com.cleanroommc.modularui.value.sync.ModularSyncManager; @@ -116,11 +116,11 @@ public static boolean foreachChildReverse(IWidget parent, Predicate con return !includeSelf || consumer.test(parent); } - public static void drawTree(IWidget parent, GuiContext context) { + public static void drawTree(IWidget parent, ModularGuiContext context) { drawTree(parent, context, false); } - public static void drawTree(IWidget parent, GuiContext context, boolean ignoreEnabled) { + public static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEnabled) { if (!parent.isEnabled() && !ignoreEnabled) return; float alpha = parent.getPanel().getAlpha(); @@ -201,7 +201,7 @@ public static void drawTree(IWidget parent, GuiContext context, boolean ignoreEn context.popMatrix(); } - public static void drawTreeForeground(IWidget parent, GuiContext context) { + public static void drawTreeForeground(IWidget parent, ModularGuiContext context) { IViewport viewport = parent instanceof IViewport viewport1 ? viewport1 : null; context.pushMatrix(); parent.transform(context); diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java index a40010a9..aebc2246 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -228,61 +228,136 @@ public void clamp(Area area) { } /** - * Expand the area either inwards or outwards on each side + * Increases or decreases the size of this area. The position will change so that the center of the new + * area is in the same place. + * The size will change with double of the given value. The position will change with the negative of the given value. + *
+ * In short, it will push or pull all four edges by the given amount. + * + * @param expand amount to expand area by (no restrictions) */ - public void expand(int offset) { - this.expandX(offset); - this.expandY(offset); + public void expand(int expand) { + this.expandX(expand); + this.expandY(expand); } /** - * Expand the area either inwards or outwards (horizontally) + * Increases or decreases the size of this area. The position will change so that the center of the new + * area is in the same place. + * The size will change with double of the given value. The position will change with the negative of the given value. + *
+ * In short, it will push or pull all four edges by the given amount. + * + * @param expandX amount to expand x-axis by (no restrictions) + * @param expandY amount to expand y-axis by (no restrictions) */ - public void expandX(int offset) { - offsetX(-offset); - growW(offset * 2); + public void expand(int expandX, int expandY) { + this.expandX(expandX); + this.expandY(expandY); } /** - * Expand the area either inwards or outwards (horizontally) + * Increases or decreases the width of this area. The x position will change so that the center of the new + * area is in the same place. + * The width will change with double of the given value. The x position will change with the negative of the given value. + *
+ * In short, it will push or pull the left and right edges by the given amount. + * + * @param expand amount to expand x-axis by (no restrictions) */ - public void expandY(int offset) { - offsetY(-offset); - growH(offset * 2); + public void expandX(int expand) { + offsetX(-expand); + growW(expand * 2); } + /** + * Increases or decreases the height of this area. The y position will change so that the center of the new + * area is in the same place. + * The height will change with double of the given value. The y position will change with the negative of the given value. + *
+ * In short, it will push or pull the top and bottom edges by the given amount. + * + * @param expand amount to expand y-axis by (no restrictions) + */ + public void expandY(int expand) { + offsetY(-expand); + growH(expand * 2); + } + + /** + * Increases or decreases the position of the area by the given amount, but doesn't change its size. + * + * @param offset amount to change position by (no restrictions) + */ public void offset(int offset) { offsetX(offset); offsetY(offset); } + /** + * Increases or decreases the position of the area by the given amount, but doesn't change its size. + * + * @param offsetX amount to change x position by (no restrictions) + * @param offsetY amount to change y position by (no restrictions) + */ public void offset(int offsetX, int offsetY) { offsetX(offsetX); offsetY(offsetY); } + /** + * Increases or decreases the x position of the area by the given amount, but doesn't change its size. + * + * @param offset amount to change x position by (no restrictions) + */ public void offsetX(int offset) { this.x += offset; } + /** + * Increases or decreases the y position of the area by the given amount, but doesn't change its size. + * + * @param offset amount to change y position by (no restrictions) + */ public void offsetY(int offset) { this.y += offset; } + /** + * Increases or decreases the size of the area by the given amount, but doesn't change its position. + * + * @param grow amount to change size by (no restrictions) + */ public void grow(int grow) { growW(grow); growH(grow); } + /** + * Increases or decreases the size of the area by the given amount, but doesn't change its position. + * + * @param growW amount to change width by (no restrictions) + * @param growH amount to change height by (no restrictions) + */ public void grow(int growW, int growH) { growW(growW); growH(growH); } + /** + * Increases or decreases the width of the area by the given amount, but doesn't change its position. + * + * @param grow amount to change width by (no restrictions) + */ public void growW(int grow) { this.width += grow; } + /** + * Increases or decreases the height of the area by the given amount, but doesn't change its position. + * + * @param grow amount to change height by (no restrictions) + */ public void growH(int grow) { this.height += grow; } @@ -311,6 +386,22 @@ public void setSize(int w, int h) { this.height = h; } + public void setPos(Rectangle rectangle) { + setPos(rectangle.x, rectangle.y); + } + + public void setSize(Rectangle rectangle) { + setSize(rectangle.width, rectangle.height); + } + + /** + * Sets position and size by specifying top left and bottom right corner position. + * + * @param sx x position of top left corner + * @param sy y position of top left corner + * @param ex x position of bottom right corner + * @param ey y position of bottom right corner + */ public void setPos(int sx, int sy, int ex, int ey) { int x0 = Math.min(sx, ex); int y0 = Math.min(sy, ey); @@ -331,7 +422,12 @@ public void set(Rectangle area) { setBounds(area.x, area.y, area.width, area.height); } - // transforms each corner vertex and then puts a min fit rectangle around all vertices + /** + * Transforms the four corners of this rectangle with the given pose stack. The new rectangle can be rotated. + * Then a min fit rectangle, which is not rotated and aligned with the screen, is put around the corner. + * + * @param stack pose stack + */ public void transformAndRectanglerize(IViewportStack stack) { int xTL = stack.transformX(this.x, this.y), xTR = stack.transformX(ex(), this.y), xBL = stack.transformX(this.x, ey()), xBR = stack.transformX(ex(), ey()); int yTL = stack.transformY(this.x, this.y), yTR = stack.transformY(ex(), this.y), yBL = stack.transformY(this.x, ey()), yBR = stack.transformY(ex(), ey()); @@ -361,6 +457,11 @@ public Area getArea() { return this; } + /** + * This creates a copy, but it only copies position and size. + * + * @return copy + */ public Area createCopy() { return new Area(this); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java index 96ed56eb..bb2df0d5 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java @@ -8,7 +8,7 @@ import com.cleanroommc.modularui.api.value.IIntValue; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.UITexture; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.value.IntValue; import com.cleanroommc.modularui.value.sync.SyncHandler; @@ -21,7 +21,6 @@ import java.util.Arrays; import java.util.List; import java.util.function.Consumer; -import java.util.function.IntFunction; public class AbstractCycleButtonWidget> extends Widget implements Interactable { @@ -32,7 +31,7 @@ public class AbstractCycleButtonWidget> e protected IDrawable[] hoverBackground = null; protected IDrawable[] overlay = null; protected IDrawable[] hoverOverlay = null; - private final List stateTooltip = new ArrayList<>(); + private final List stateTooltip = new ArrayList<>(); @Override public void onInit() { @@ -128,15 +127,15 @@ public boolean hasTooltip() { @Override public void markTooltipDirty() { super.markTooltipDirty(); - for (Tooltip tooltip : this.stateTooltip) { + for (RichTooltip tooltip : this.stateTooltip) { tooltip.markDirty(); } getState(); } @Override - public @Nullable Tooltip getTooltip() { - Tooltip tooltip = super.getTooltip(); + public @Nullable RichTooltip getTooltip() { + RichTooltip tooltip = super.getTooltip(); if (tooltip == null || tooltip.isEmpty()) { return this.stateTooltip.get(getState()); } @@ -224,7 +223,7 @@ protected W stateCount(int stateCount) { this.stateCount = stateCount; // adjust tooltip buffer size while (this.stateTooltip.size() < this.stateCount) { - this.stateTooltip.add(new Tooltip(this)); + this.stateTooltip.add(new RichTooltip(this)); } while (this.stateTooltip.size() > this.stateCount) { this.stateTooltip.remove(this.stateTooltip.size() - 1); @@ -265,12 +264,12 @@ protected static void splitTexture(UITexture texture, IDrawable[] dest) { } } - protected W tooltip(int index, Consumer builder) { + protected W tooltip(int index, Consumer builder) { builder.accept(this.stateTooltip.get(index)); return getThis(); } - protected W tooltipBuilder(int index, Consumer builder) { + protected W tooltipBuilder(int index, Consumer builder) { this.stateTooltip.get(index).tooltipBuilder(builder); return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java index 61002f9d..908c6942 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java @@ -5,7 +5,7 @@ import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.GuiTextures; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.widget.ParentWidget; @@ -25,7 +25,7 @@ public class CategoryList extends ParentWidget implements Interact private IDrawable collapsedOverlay; @Override - public void drawOverlay(GuiContext context, WidgetTheme widgetTheme) { + public void drawOverlay(ModularGuiContext context, WidgetTheme widgetTheme) { super.drawOverlay(context, widgetTheme); if (this.expanded) { this.expandedOverlay.drawAtZero(context, getArea(), widgetTheme); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/CycleButtonWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/CycleButtonWidget.java index fb638a54..fbc1c9db 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/CycleButtonWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/CycleButtonWidget.java @@ -2,7 +2,7 @@ import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.value.IIntValue; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; import java.util.function.Consumer; @@ -85,12 +85,12 @@ public CycleButtonWidget stateCount(int stateCount) { } @Override - public CycleButtonWidget tooltip(int index, Consumer builder) { + public CycleButtonWidget tooltip(int index, Consumer builder) { return super.tooltip(index, builder); } @Override - public CycleButtonWidget tooltipBuilder(int index, Consumer builder) { + public CycleButtonWidget tooltipBuilder(int index, Consumer builder) { return super.tooltipBuilder(index, builder); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/FluidSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/FluidSlot.java index a397ef76..6d7db009 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/FluidSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/FluidSlot.java @@ -5,12 +5,12 @@ import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.GuiDraw; -import com.cleanroommc.modularui.drawable.TextRenderer; +import com.cleanroommc.modularui.drawable.text.TextRenderer; import com.cleanroommc.modularui.integration.jei.JeiGhostIngredientSlot; import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.Tooltip; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetSlotTheme; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; @@ -51,7 +51,7 @@ public class FluidSlot extends Widget implements Interactable, JeiGho public FluidSlot() { size(DEFAULT_SIZE); - tooltip().setAutoUpdate(true).setHasTitleMargin(true); + tooltip().setAutoUpdate(true);//.setHasTitleMargin(true); tooltipBuilder(tooltip -> { IFluidTank fluidTank = getFluidTank(); FluidStack fluid = this.syncHandler.getValue(); @@ -93,8 +93,7 @@ public FluidSlot() { }); } - public void addAdditionalFluidInfo(Tooltip tooltip, FluidStack fluidStack) { - } + public void addAdditionalFluidInfo(RichTooltip tooltip, FluidStack fluidStack) {} public String formatFluidAmount(double amount) { NumberFormat.FORMAT.setMaximumFractionDigits(3); @@ -124,7 +123,7 @@ public boolean isValidSyncHandler(SyncHandler syncHandler) { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { IFluidTank fluidTank = getFluidTank(); FluidStack content = this.syncHandler.getValue(); if (content != null) { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java index ad16d64b..e6e41443 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ItemSlot.java @@ -1,6 +1,5 @@ package com.cleanroommc.modularui.widgets; -import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.api.widget.Interactable; @@ -8,12 +7,13 @@ import com.cleanroommc.modularui.core.mixin.GuiContainerAccessor; import com.cleanroommc.modularui.core.mixin.GuiScreenAccessor; import com.cleanroommc.modularui.drawable.GuiDraw; -import com.cleanroommc.modularui.drawable.TextRenderer; +import com.cleanroommc.modularui.drawable.text.TextRenderer; import com.cleanroommc.modularui.integration.jei.JeiGhostIngredientSlot; import com.cleanroommc.modularui.integration.jei.JeiIngredientProvider; +import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.Tooltip; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetSlotTheme; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; @@ -41,8 +41,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; - public class ItemSlot extends Widget implements IVanillaSlot, Interactable, JeiGhostIngredientSlot, JeiIngredientProvider { public static final int SIZE = 18; @@ -51,12 +49,12 @@ public class ItemSlot extends Widget implements IVanillaSlot, Interact private ItemSlotSH syncHandler; public ItemSlot() { - tooltip().setAutoUpdate(true).setHasTitleMargin(true); + tooltip().setAutoUpdate(true);//.setHasTitleMargin(true); tooltipBuilder(tooltip -> { if (!isSynced()) return; ItemStack stack = getSlot().getStack(); if (stack.isEmpty()) return; - tooltip.addStringLines(getItemTooltip(stack)); + tooltip.addFromItem(stack); }); } @@ -65,7 +63,7 @@ public void onInit() { if (getScreen().isOverlay()) { throw new IllegalStateException("Overlays can't have slots!"); } - size(SIZE, SIZE); + size(SIZE); getContext().getJeiSettings().addJeiGhostIngredientSlot(this); } @@ -85,7 +83,7 @@ public void onUpdate() { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { if (this.syncHandler == null) return; RenderHelper.enableGUIStandardItemLighting(); drawSlot(getSlot()); @@ -99,8 +97,8 @@ public void draw(GuiContext context, WidgetTheme widgetTheme) { } @Override - public void drawForeground(GuiContext context) { - Tooltip tooltip = getTooltip(); + public void drawForeground(ModularGuiContext context) { + RichTooltip tooltip = getTooltip(); if (tooltip != null && isHoveringFor(tooltip.getShowUpTimer())) { tooltip.draw(getContext(), getSlot().getStack()); } @@ -173,12 +171,6 @@ public Slot getVanillaSlot() { return this.syncHandler; } - protected List getItemTooltip(ItemStack stack) { - // todo: JEI seems to be getting tooltip from IngredientRenderer#getTooltip - return getScreen().getScreenWrapper().getGuiScreen().getItemToolTip(stack); - } - - public ItemSlot slot(ModularSlot slot) { this.syncHandler = new ItemSlotSH(slot); setSyncHandler(this.syncHandler); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java index f13a2248..62c67eff 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ProgressWidget.java @@ -2,7 +2,7 @@ import com.cleanroommc.modularui.api.value.IDoubleValue; import com.cleanroommc.modularui.drawable.UITexture; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.value.DoubleValue; @@ -54,7 +54,7 @@ public float getCurrentProgress() { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { if (this.emptyTexture != null) { this.emptyTexture.draw(context, 0, 0, getArea().w(), getArea().h(), widgetTheme); Color.setGlColorOpaque(Color.WHITE.main); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/RichTextWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/RichTextWidget.java new file mode 100644 index 00000000..8f8bba6c --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/RichTextWidget.java @@ -0,0 +1,164 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.api.drawable.IHoverable; +import com.cleanroommc.modularui.api.drawable.IRichTextBuilder; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.drawable.text.RichText; +import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.RichTooltip; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.widget.Widget; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public class RichTextWidget extends Widget implements IRichTextBuilder, Interactable { + + private final RichText text = new RichText(); + private Consumer builder; + private boolean dirty = false; + private boolean autoUpdate = false; + + public void markDirty() { + this.dirty = true; + } + + @Override + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { + super.draw(context, widgetTheme); + if (this.autoUpdate || this.dirty) { + this.text.clearText(); + if (this.builder != null) { + this.builder.accept(this.text); + } + } + this.text.drawAtZero(context, getArea(), widgetTheme); + } + + @Override + public void drawForeground(ModularGuiContext context) { + super.drawForeground(context); + Object o = this.text.getHoveringElement(context.getFontRenderer(), context.getMouseX(), context.getMouseY()); + //ModularUI.LOGGER.info("Mouse {}, {}", context.getMouseX(), context.getMouseY()); + if (o instanceof IHoverable hoverable) { + hoverable.onHover(); + RichTooltip tooltip = hoverable.getTooltip(); + if (tooltip != null) { + tooltip.draw(context); + } + } + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onMousePressed(mouseButton); + } + return Result.ACCEPT; + } + + @Override + public boolean onMouseRelease(int mouseButton) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onMouseRelease(mouseButton); + } + return false; + } + + @Override + public @NotNull Result onMouseTapped(int mouseButton) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onMouseTapped(mouseButton); + } + return Result.IGNORE; + } + + @Override + public @NotNull Result onKeyPressed(char typedChar, int keyCode) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onKeyPressed(typedChar, keyCode); + } + return Result.ACCEPT; + } + + @Override + public boolean onKeyRelease(char typedChar, int keyCode) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onKeyRelease(typedChar, keyCode); + } + return false; + } + + @Override + public @NotNull Result onKeyTapped(char typedChar, int keyCode) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onKeyTapped(typedChar, keyCode); + } + return Result.ACCEPT; + } + + @Override + public boolean onMouseScroll(ModularScreen.UpOrDown scrollDirection, int amount) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + return interactable.onMouseScroll(scrollDirection, amount); + } + return false; + } + + @Override + public void onMouseDrag(int mouseButton, long timeSinceClick) { + Object o = this.text.getHoveringElement(getContext().getFontRenderer(), getContext().getMouseX(), getContext().getMouseY()); + if (o instanceof Interactable interactable) { + interactable.onMouseDrag(mouseButton, timeSinceClick); + } + } + + @Nullable + public Object getHoveredElement() { + if (!isHovering()) return null; + getContext().pushMatrix(); + getContext().translate(getArea().x, getArea().y); + Object o = this.text.getHoveringElement(getContext()); + getContext().popMatrix(); + return o; + } + + @Override + public IRichTextBuilder getRichText() { + return text; + } + + /** + * Sets the auto update property. If auto update is true the text will be deleted each time it is drawn. + * If {@link #builder} is not null, it will then be called. + * + * @param autoUpdate auto update + * @return this + */ + public RichTextWidget autoUpdate(boolean autoUpdate) { + this.autoUpdate = autoUpdate; + return this; + } + + /** + * A builder which is called every time before drawing when {@link #dirty} is true. + * + * @param builder text builder + * @return this + */ + public RichTextWidget textBuilder(Consumer builder) { + this.builder = builder; + markDirty(); + return this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ScrollingTextWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ScrollingTextWidget.java index 1f224176..7a8d7a49 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ScrollingTextWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ScrollingTextWidget.java @@ -1,8 +1,8 @@ package com.cleanroommc.modularui.widgets; import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.drawable.TextRenderer; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.drawable.text.TextRenderer; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; @@ -54,7 +54,7 @@ public void onUpdate() { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { checkString(); TextRenderer renderer = TextRenderer.SHARED; renderer.setColor(getColor()); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java index 150496ef..b9be8a22 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java @@ -1,14 +1,13 @@ package com.cleanroommc.modularui.widgets; import com.cleanroommc.modularui.api.GuiAxis; -import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.value.IDoubleValue; import com.cleanroommc.modularui.api.widget.IGuiAction; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.drawable.Rectangle; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.value.DoubleValue; @@ -69,7 +68,7 @@ public boolean isValidSyncHandler(SyncHandler syncHandler) { } @Override - public void drawBackground(GuiContext context, WidgetTheme widgetTheme) { + public void drawBackground(ModularGuiContext context, WidgetTheme widgetTheme) { super.drawBackground(context, widgetTheme); if (this.stopper != null && this.stopperDrawable != null && this.stopperWidth > 0 && this.stopperHeight > 0) { for (double stop : this.stopper) { @@ -88,7 +87,7 @@ public void drawBackground(GuiContext context, WidgetTheme widgetTheme) { } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { if (this.handleDrawable != null) { this.handleDrawable.draw(context, this.sliderArea, context.getTheme().getButtonTheme()); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java index f233e57b..9f3bf82e 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java @@ -1,37 +1,37 @@ package com.cleanroommc.modularui.widgets; import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.drawable.TextRenderer; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.drawable.text.TextRenderer; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widget.sizer.Box; +import net.minecraft.util.text.TextFormatting; + public class TextWidget extends Widget { private final IKey key; private Alignment alignment = Alignment.CenterLeft; - private int color = 0x404040; - private boolean shadow = false; + private Integer color = null; + private Boolean shadow = null; private float scale = 1f; - public boolean colorChanged = false, shadowChanged = false; - public TextWidget(IKey key) { this.key = key; } @Override - public void draw(GuiContext context, WidgetTheme widgetTheme) { + public void draw(ModularGuiContext context, WidgetTheme widgetTheme) { TextRenderer renderer = TextRenderer.SHARED; - renderer.setColor(this.colorChanged ? this.color : widgetTheme.getTextColor()); + renderer.setColor(this.color != null ? this.color : widgetTheme.getTextColor()); renderer.setAlignment(this.alignment, getArea().w() + this.scale, getArea().h()); - renderer.setShadow(this.shadowChanged ? this.shadow : widgetTheme.getTextShadow()); + renderer.setShadow(this.shadow != null ? this.shadow : widgetTheme.getTextShadow()); renderer.setPos(getArea().getPadding().left, getArea().getPadding().top); renderer.setScale(this.scale); renderer.setSimulate(false); - renderer.draw(this.key.get()); + renderer.draw(this.key.getFormatted()); } private TextRenderer simulate(float maxWidth) { @@ -41,7 +41,7 @@ private TextRenderer simulate(float maxWidth) { renderer.setPos(padding.left, padding.top); renderer.setScale(this.scale); renderer.setSimulate(true); - renderer.draw(this.key.get()); + renderer.draw(this.key.getFormatted()); return renderer; } @@ -97,7 +97,6 @@ public TextWidget alignment(Alignment alignment) { } public TextWidget color(int color) { - this.colorChanged = true; this.color = color; return this; } @@ -108,8 +107,12 @@ public TextWidget scale(float scale) { } public TextWidget shadow(boolean shadow) { - this.shadowChanged = true; this.shadow = shadow; return this; } + + public TextWidget format(TextFormatting formatting) { + this.key.format(formatting); + return this; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java b/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java index 5e19a499..49bf9fd0 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java @@ -3,7 +3,7 @@ import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.value.IBoolValue; -import com.cleanroommc.modularui.screen.Tooltip; +import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.theme.WidgetThemeSelectable; @@ -65,11 +65,11 @@ public ToggleButton addTooltip(boolean selected, IDrawable tooltip) { return super.addTooltip(selected ? 1 : 0, tooltip); } - public ToggleButton tooltip(boolean selected, Consumer builder) { + public ToggleButton tooltip(boolean selected, Consumer builder) { return super.tooltip(selected ? 1 : 0, builder); } - public ToggleButton tooltipBuilder(boolean selected, Consumer builder) { + public ToggleButton tooltipBuilder(boolean selected, Consumer builder) { return super.tooltipBuilder(selected ? 1 : 0, builder); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 72d87d14..796fe7e1 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -5,7 +5,7 @@ import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.Stencil; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.widget.ScrollWidget; @@ -83,7 +83,7 @@ public void onUpdate() { } @Override - public void preDraw(GuiContext context, boolean transformed) { + public void preDraw(ModularGuiContext context, boolean transformed) { if (transformed) { drawText(context); } else { @@ -91,7 +91,7 @@ public void preDraw(GuiContext context, boolean transformed) { } } - public void drawText(GuiContext context) { + public void drawText(ModularGuiContext context) { this.renderer.setSimulate(false); this.renderer.setScale(this.scale); this.renderer.setAlignment(this.textAlignment, -2, getArea().height); @@ -110,13 +110,13 @@ public boolean isFocused() { } @Override - public void onFocus(GuiContext context) { + public void onFocus(ModularGuiContext context) { this.cursorTimer = 0; this.renderer.setCursor(true); } @Override - public void onRemoveFocus(GuiContext context) { + public void onRemoveFocus(ModularGuiContext context) { this.renderer.setCursor(false); this.cursorTimer = 0; this.scrollOffset = 0; @@ -132,8 +132,8 @@ public void onRemoveFocus(GuiContext context) { if (!isHovering()) { return Result.IGNORE; } - int x = getContext().unTransformMouseX() + getScrollX(); - int y = getContext().unTransformMouseY() + getScrollY(); + int x = getContext().getMouseX() + getScrollX(); + int y = getContext().getMouseY() + getScrollY(); this.handler.setCursor(this.renderer.getCursorPos(this.handler.getText(), x, y), true); return Result.SUCCESS; } @@ -141,8 +141,8 @@ public void onRemoveFocus(GuiContext context) { @Override public void onMouseDrag(int mouseButton, long timeSinceClick) { if (isFocused()) { - int x = getContext().unTransformMouseX() + getScrollX(); - int y = getContext().unTransformMouseY() + getScrollY(); + int x = getContext().getMouseX() + getScrollX(); + int y = getContext().getMouseY() + getScrollY(); this.handler.setMainCursor(this.renderer.getCursorPos(this.handler.getText(), x, y), true); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java index 6cb50b22..8f9e8859 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldRenderer.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.widgets.textfield; -import com.cleanroommc.modularui.drawable.TextRenderer; +import com.cleanroommc.modularui.drawable.text.TextRenderer; import com.cleanroommc.modularui.utils.Color; import net.minecraft.client.renderer.BufferBuilder; @@ -117,11 +117,11 @@ public Point getCursorPos(List lines, int x, int y) { public Point2D.Float getPosOf(List measuredLines, Point cursorPos) { if (measuredLines.isEmpty()) { - return new Point2D.Float(getStartX(0), getStartY(1)); + return new Point2D.Float(getStartX(0), getStartYOfLines(1)); } Line line = measuredLines.get(cursorPos.y); String sub = line.getText().substring(0, Math.min(line.getText().length(), cursorPos.x)); - return new Point2D.Float(getStartX(line.getWidth()) + getFontRenderer().getStringWidth(sub) * this.scale, getStartY(measuredLines.size()) + cursorPos.y * getFontHeight()); + return new Point2D.Float(getStartX(line.getWidth()) + getFontRenderer().getStringWidth(sub) * this.scale, getStartYOfLines(measuredLines.size()) + cursorPos.y * getFontHeight()); } @SideOnly(Side.CLIENT) diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java index 2b158ea5..c49ff2e5 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/TextFieldWidget.java @@ -5,7 +5,7 @@ import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.api.value.IStringValue; -import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetTextFieldTheme; import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.math.Constant; @@ -37,6 +37,7 @@ public static IMathValue parse(String num) { try { return MathBuilder.INSTANCE.parse(num); } catch (Exception e) { + ModularUI.LOGGER.error("Failed to parse {} in TextFieldWidget", num); ModularUI.LOGGER.catching(e); } return new Constant(0); @@ -90,7 +91,7 @@ public void onUpdate() { } @Override - public void drawText(GuiContext context) { + public void drawText(ModularGuiContext context) { this.renderer.setSimulate(false); this.renderer.setPos(getArea().getPadding().left, 0); this.renderer.setScale(this.scale); @@ -100,7 +101,7 @@ public void drawText(GuiContext context) { } @Override - public void drawForeground(GuiContext context) { + public void drawForeground(ModularGuiContext context) { if (hasTooltip() && getScrollData().isScrollBarActive(getScrollArea()) && isHoveringFor(getTooltip().getShowUpTimer())) { getTooltip().draw(getContext()); } @@ -126,7 +127,7 @@ public void setText(@NotNull String text) { } @Override - public void onFocus(GuiContext context) { + public void onFocus(ModularGuiContext context) { super.onFocus(context); Point main = this.handler.getMainCursor(); if (main.x == 0) { @@ -135,7 +136,7 @@ public void onFocus(GuiContext context) { } @Override - public void onRemoveFocus(GuiContext context) { + public void onRemoveFocus(ModularGuiContext context) { super.onRemoveFocus(context); if (this.handler.getText().isEmpty()) { this.handler.getText().add(this.validator.apply("")); diff --git a/src/main/resources/mixin.modularui.json b/src/main/resources/mixin.modularui.json index 22bab54c..19d5d843 100644 --- a/src/main/resources/mixin.modularui.json +++ b/src/main/resources/mixin.modularui.json @@ -14,6 +14,7 @@ "MinecraftMixin" ], "mixins": [ - "ContainerAccessor" + "ContainerAccessor", + "FontRendererAccessor" ] } \ No newline at end of file