From 305cf6e6356194f7dc5d17d0d51ff4782871b59d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 1 Nov 2025 11:49:58 +0100 Subject: [PATCH 1/2] context menu things --- .../cleanroommc/modularui/ClientProxy.java | 2 + .../cleanroommc/modularui/api/IThemeApi.java | 8 + .../api/widget/IDelegatingWidget.java | 18 ++ .../modularui/overlay/DebugOptions.java | 30 +++ .../modularui/overlay/DebugOverlay.java | 99 +++++++++ .../modularui/screen/ClientScreenHandler.java | 9 +- .../cleanroommc/modularui/test/TestGuis.java | 51 ++++- .../widget/DelegatingSingleChildWidget.java | 20 +- .../cleanroommc/modularui/widget/Widget.java | 13 ++ .../modularui/widget/sizer/Bounds.java | 50 +++++ .../widgets/AbstractCycleButtonWidget.java | 47 +++- .../modularui/widgets/ToggleButton.java | 5 + .../widgets/menu/ContextMenuButton.java | 210 ++++++++++++++++++ .../widgets/menu/ContextMenuList.java | 81 +++++++ .../widgets/menu/ContextMenuOption.java | 31 +++ .../widgets/menu/IContextMenuOption.java | 22 ++ .../modularui/widgets/menu/MenuPanel.java | 36 +++ .../textures/gui/background/menu.png | Bin 0 -> 602 bytes 18 files changed, 717 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java create mode 100644 src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java create mode 100644 src/main/resources/assets/modularui/textures/gui/background/menu.png diff --git a/src/main/java/com/cleanroommc/modularui/ClientProxy.java b/src/main/java/com/cleanroommc/modularui/ClientProxy.java index f9153df52..1a895d309 100644 --- a/src/main/java/com/cleanroommc/modularui/ClientProxy.java +++ b/src/main/java/com/cleanroommc/modularui/ClientProxy.java @@ -8,6 +8,7 @@ import com.cleanroommc.modularui.holoui.HoloScreenEntity; import com.cleanroommc.modularui.holoui.ScreenEntityRender; import com.cleanroommc.modularui.keybind.KeyBindHandler; +import com.cleanroommc.modularui.overlay.DebugOverlay; import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.test.EventHandler; import com.cleanroommc.modularui.test.OverlayTest; @@ -68,6 +69,7 @@ void preInit(FMLPreInitializationEvent event) { testKey = new KeyBinding("key.test", KeyConflictContext.IN_GAME, Keyboard.KEY_NUMPAD4, "key.categories.modularui"); ClientRegistry.registerKeyBinding(testKey); } + DebugOverlay.register(); if (ModularUIConfig.enableTestOverlays) { OverlayTest.init(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java b/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java index b9fa69ab4..a8a363f5f 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java +++ b/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java @@ -70,6 +70,14 @@ public interface IThemeApi { .defaultHoverTheme(SelectableTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON_HOVERED, IDrawable.NONE)) .register(); + WidgetThemeKey CONTEXT_MENU = get().widgetThemeKeyBuilder("menu", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(80, 100, GuiTextures.MENU_BACKGROUND)) + .register(); + + WidgetThemeKey MENU_OPTION = get().widgetThemeKeyBuilder("menuOption", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(80, 12, IDrawable.EMPTY)) + .register(); + // sub widget themes WidgetThemeKey ITEM_SLOT_PLAYER = ITEM_SLOT.createSubKey("player"); WidgetThemeKey ITEM_SLOT_PLAYER_HOTBAR = ITEM_SLOT_PLAYER.createSubKey("playerHotbar"); diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java new file mode 100644 index 000000000..b8132f336 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java @@ -0,0 +1,18 @@ +package com.cleanroommc.modularui.api.widget; + +import net.minecraft.inventory.Slot; + +public interface IDelegatingWidget extends IWidget, IVanillaSlot { + + IWidget getDelegate(); + + @Override + default Slot getVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot ? vanillaSlot.getVanillaSlot() : null; + } + + @Override + default boolean handleAsVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java new file mode 100644 index 000000000..b4badcf98 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java @@ -0,0 +1,30 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.utils.Color; + +public class DebugOptions { + + public static final DebugOptions INSTANCE = new DebugOptions(); + + public boolean showHovered = true; + public boolean showName = true; + public boolean showPos = true; + public boolean showSize = true; + public boolean showRelPos = true; + public boolean showWidgetTheme = true; + public boolean showOutline = true; + + public boolean showParent = true; + public boolean showParentName = true; + public boolean showParentPos = true; + public boolean showParentSize = true; + public boolean showParentRelPos = false; + public boolean showParentWidgetTheme = false; + public boolean showParentOutline = true; + + public int textColor = Color.argb(180, 40, 115, 220); + public int outlineColor = textColor; + public int cursorColor = Color.GREEN.main; + public float scale = 0.8f; + +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java new file mode 100644 index 000000000..0fef8cf05 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -0,0 +1,99 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.ModularUIConfig; +import com.cleanroommc.modularui.api.IMuiScreen; +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.value.IBoolValue; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.drawable.NamedDrawableRow; +import com.cleanroommc.modularui.drawable.Rectangle; +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.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.value.BoolValue; +import com.cleanroommc.modularui.widget.WidgetTree; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ToggleButton; +import com.cleanroommc.modularui.widgets.menu.ContextMenuButton; +import com.cleanroommc.modularui.widgets.menu.ContextMenuList; +import com.cleanroommc.modularui.widgets.menu.ContextMenuOption; + +import org.jetbrains.annotations.NotNull; + +public class DebugOverlay extends CustomModularScreen { + + public static void register() { + OverlayManager.register(new OverlayHandler(screen -> ModularUIConfig.guiDebugMode && screen instanceof IMuiScreen, screen -> new DebugOverlay((IMuiScreen) screen))); + } + + private static final IIcon CHECKMARK = GuiTextures.CHECKMARK.asIcon().size(8); + + private final IMuiScreen parent; + + public DebugOverlay(IMuiScreen screen) { + this.parent = screen; + } + + @Override + public @NotNull ModularPanel buildUI(ModularGuiContext context) { + return new ModularPanel("debug") + .fullScreenInvisible() + .child(new ContextMenuButton<>() + .horizontalCenter() + .bottom(0) + .height(12) + .width(100) + .background(new Rectangle().setColor(Color.withAlpha(Color.WHITE.main, 0.2f)).setCornerRadius(4)) + .overlay(IKey.str("Debug Options")) + .openUp() + .menuList(new ContextMenuList<>("debug_options") + .maxSize(100) + .widthRel(1f) + .child(new ContextMenuOption<>() + .child(new ButtonWidget<>() + .invisible() + .overlay(IKey.str("Print widget trees")) + .onMousePressed(this::logWidgetTrees))) + .child(new ContextMenuButton<>() + .height(10) + .overlay(IKey.str("Widget hover info")) + .openRightUp() + .menuList(new ContextMenuList<>("hover_info") + .maxSize(100) + .child(toggleOption("Name", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showName, v -> DebugOptions.INSTANCE.showName = v))) + .child(toggleOption("Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showPos, v -> DebugOptions.INSTANCE.showPos = v))) + .child(toggleOption("Size", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showSize, v -> DebugOptions.INSTANCE.showSize = v))) + .child(toggleOption("Rel Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showRelPos, v -> DebugOptions.INSTANCE.showRelPos = v))) + .child(toggleOption("Widget Theme", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showWidgetTheme, v -> DebugOptions.INSTANCE.showWidgetTheme = v))) + .child(toggleOption("Outline", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showOutline, v -> DebugOptions.INSTANCE.showOutline = v))) + )))); + } + + public static ContextMenuOption toggleOption(String name, IBoolValue boolValue) { + return new ContextMenuOption<>() + .child(new ToggleButton() + .name("menu toggle " + name) + .invisible() + .value(boolValue) + .overlay(true, new NamedDrawableRow() + .name(IKey.str(name)) + .drawable(CHECKMARK)) + .overlay(false, new NamedDrawableRow() + .name(IKey.str(name)))); + } + + private void drawDebug(GuiContext context, int x, int y, int w, int h, WidgetTheme widgetTheme) { + + } + + private boolean logWidgetTrees(int b) { + for (ModularPanel panel : parent.getScreen().getPanelManager().getOpenPanels()) { + WidgetTree.printTree(panel); + } + return true; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index da6a8c82d..069fed627 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -14,6 +14,7 @@ import com.cleanroommc.modularui.core.mixins.early.minecraft.GuiScreenAccessor; import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.drawable.Stencil; +import com.cleanroommc.modularui.overlay.DebugOptions; import com.cleanroommc.modularui.overlay.OverlayManager; import com.cleanroommc.modularui.overlay.OverlayStack; import com.cleanroommc.modularui.screen.viewport.GuiContext; @@ -296,7 +297,7 @@ private static boolean handleKeyboardInput(@Nullable ModularScreen muiScreen, Gu } else { // releasing a key // for some reason when you press E after joining a world the button will not trigger the press event, - // but ony the release event, causing this to be null + // but only the release event, causing this to be null if (lastChar == null) return false; // when the key is released, the event char is empty if (inputPhase.isEarly() && doAction(muiScreen, ms -> ms.onKeyRelease(lastChar, key))) { @@ -570,8 +571,10 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable locatedHovered.unapplyMatrix(context); GuiDraw.drawText("Widget Theme: " + hovered.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, lineY, scale, color, true); lineY -= shift; - GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, scale, color, true); - lineY -= shift; + if (DebugOptions.INSTANCE.showSize) { + GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, scale, color, true); + lineY -= shift; + } GuiDraw.drawText("Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, lineY, scale, color, true); lineY -= shift; GuiDraw.drawText("Class: " + hovered, 5, lineY, scale, color, true); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index d26e20c74..ca765554c 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -50,6 +50,8 @@ import com.cleanroommc.modularui.widgets.layout.Flow; import com.cleanroommc.modularui.widgets.layout.Grid; import com.cleanroommc.modularui.widgets.layout.Row; +import com.cleanroommc.modularui.widgets.menu.ContextMenuButton; +import com.cleanroommc.modularui.widgets.menu.ContextMenuList; import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.client.Minecraft; @@ -74,6 +76,7 @@ import java.util.Comparator; import java.util.List; import java.util.Random; +import java.util.stream.Collectors; import java.util.stream.IntStream; public class TestGuis extends CustomModularScreen { @@ -503,7 +506,7 @@ public static ModularPanel buildCollapseDisabledChildrenUI() { } public static @NotNull ModularPanel buildViewportTransformUI() { - return new TestPanel("test") + return new TestPanel("viewport_transform") .child(new Widget<>() .align(Alignment.Center) .size(50, 50) @@ -511,6 +514,52 @@ public static ModularPanel buildCollapseDisabledChildrenUI() { .hoverBackground(GuiTextures.MC_BUTTON_HOVERED)); } + public static ModularPanel buildContextMenu() { + List options1 = IntStream.range(0, 5).mapToObj(i -> "Option " + (i + 1)).collect(Collectors.toList()); + List options2 = IntStream.range(0, 5).mapToObj(i -> "Sub Option " + (i + 1)).collect(Collectors.toList()); + return new ModularPanel("context_menu_test") + .size(150) + .child(new ListWidget<>() + .height(100) + .left(25) + .coverChildrenWidth() + //.right(25) + .top(40) + .child(new ToggleButton() + .coverChildrenHeight() + .widthRel(1f) + .child(true, new Row() + .coverChildrenHeight() + .widthRel(1f) + .mainAxisAlignment(Alignment.MainAxis.SPACE_BETWEEN) + .child(IKey.str("Text1").asWidget()) + .child(new ItemDrawable(Items.PORKCHOP).asWidget())) + .child(false, new Row() + .coverChildrenHeight() + .widthRel(1f) + .mainAxisAlignment(Alignment.MainAxis.SPACE_BETWEEN) + .child(IKey.str("Text2").asWidget()) + .child(new ItemDrawable(Items.MAGMA_CREAM).asWidget())))) + .child(new ContextMenuButton<>() + .top(7) + .width(100) + .horizontalCenter() + .height(16) + .overlay(IKey.str("Menu")) + .menuList(new ContextMenuList<>("menu1") + .widthRel(1f) + .maxSize(80) + .children(options1, s -> IKey.str(s).asWidget()) + .child(new ContextMenuButton<>() + .overlay(IKey.str("Sub Menu")) + .openRightDown() + .menuList(new ContextMenuList<>("menu2") + .coverChildrenWidth() + //.width(90) + .maxSize(80) + .children(options2, s -> IKey.str(s).asWidget()))))); + } + private static class TestPanel extends ModularPanel { public TestPanel(String name) { diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java index b766224af..44eb79ce3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -43,15 +43,17 @@ public void postResize() { super.postResize(); if (getDelegate() != null) getDelegate().postResize(); this.currentlyResizing = false; - Area childArea = getChild().getArea(); - Area area = super.getArea(); - area.set(childArea); - area.rx = childArea.rx; - area.ry = childArea.ry; - childArea.x = 0; - childArea.y = 0; - childArea.rx = 0; - childArea.ry = 0; + if (getDelegate() != null) { + Area childArea = getChild().getArea(); + Area area = super.getArea(); + area.set(childArea); + area.rx = childArea.rx; + area.ry = childArea.ry; + childArea.x = 0; + childArea.y = 0; + childArea.rx = 0; + childArea.ry = 0; + } } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 43dd716f0..fbabb11a4 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -24,6 +24,7 @@ import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widget.sizer.Bounds; import com.cleanroommc.modularui.widget.sizer.Flex; import com.cleanroommc.modularui.widget.sizer.IUnResizeable; @@ -72,6 +73,7 @@ public class Widget> implements IWidget, IPositioned, ITo @Nullable private String syncKey; @Nullable private SyncHandler syncHandler; // rendering + @Nullable private IDrawable shadow = null; @Nullable private IDrawable background = null; @Nullable private IDrawable overlay = null; @Nullable private IDrawable hoverBackground = null; @@ -207,6 +209,9 @@ public void dispose() { */ @Override public void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + if (this.shadow != null) { + this.shadow.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); + } IDrawable bg = getCurrentBackground(context.getTheme(), widgetTheme); if (bg != null) { bg.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); @@ -391,6 +396,10 @@ public final WidgetThemeEntry getWidgetTheme(ITheme theme) { return getWidgetThemeInternal(theme); } + public final @Nullable WidgetThemeKey getWidgetThemeOverride() { + return widgetThemeOverride; + } + /** * Returns the actual used widget theme. Uses {@link #widgetTheme(String)} if it has been set, otherwise calls * {@link #getWidgetThemeInternal(ITheme)} @@ -621,6 +630,10 @@ public W setEnabledIf(Predicate condition) { // === Resizing === // ---------------- + public void estimateSize(Bounds bounds) { + + } + @Override public int getDefaultWidth() { return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultWidth() : 18; diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java new file mode 100644 index 000000000..f28b6b4f9 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java @@ -0,0 +1,50 @@ +package com.cleanroommc.modularui.widget.sizer; + +public class Bounds { + + public static final int UNLIMITED_MAX = Integer.MAX_VALUE; + public static final int UNLIMITED_MIN = Integer.MIN_VALUE; + + private int minWidth = UNLIMITED_MIN, minHeight = UNLIMITED_MIN; + private int maxWidth = UNLIMITED_MAX, maxHeight = UNLIMITED_MAX; + + public Bounds set(int minWidth, int minHeight, int maxWidth, int maxHeight) { + this.minWidth = minWidth; + this.minHeight = minHeight; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + return this; + } + + public Bounds max(int width, int height) { + this.maxWidth = width; + this.maxHeight = height; + return this; + } + + public Bounds min(int width, int height) { + this.minWidth = width; + this.minHeight = height; + return this; + } + + public Bounds exact(int w, int h) { + return set(w, h, w, h); + } + + public int getMaxHeight() { + return maxHeight; + } + + public int getMaxWidth() { + return maxWidth; + } + + public int getMinHeight() { + return minHeight; + } + + public int getMinWidth() { + return minWidth; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java index 562c5d37b..bbf9be2b5 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java @@ -7,6 +7,7 @@ import com.cleanroommc.modularui.api.value.IBoolValue; import com.cleanroommc.modularui.api.value.IEnumValue; import com.cleanroommc.modularui.api.value.IIntValue; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.UITexture; import com.cleanroommc.modularui.screen.RichTooltip; @@ -14,7 +15,7 @@ import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.value.IntValue; import com.cleanroommc.modularui.value.sync.SyncHandler; -import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widget.SingleChildWidget; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +25,7 @@ import java.util.List; import java.util.function.Consumer; -public class AbstractCycleButtonWidget> extends Widget implements Interactable { +public class AbstractCycleButtonWidget> extends SingleChildWidget implements Interactable { private int stateCount = 1; private IIntValue intValue; @@ -33,6 +34,8 @@ public class AbstractCycleButtonWidget> e protected IDrawable[] hoverBackground = null; protected IDrawable[] overlay = null; protected IDrawable[] hoverOverlay = null; + protected IWidget[] stateChildren = null; + protected IWidget fallbackChild = null; private final List stateTooltip = new ArrayList<>(); @Override @@ -40,6 +43,7 @@ public void onInit() { if (this.intValue == null) { this.intValue = new IntValue(0); } + updateChild(getState()); } @Override @@ -74,6 +78,7 @@ public void setState(int state, boolean setSource) { if (state < 0 || state >= this.stateCount) { throw new IndexOutOfBoundsException("CycleButton state out of bounds"); } + updateChild(state); if (setSource) { this.intValue.setIntValue(state); } @@ -81,6 +86,15 @@ public void setState(int state, boolean setSource) { markTooltipDirty(); } + private void updateChild(int state) { + IWidget child = this.stateChildren != null && this.stateChildren.length > state ? this.stateChildren[state] : null; + if (child != null) { + child(child); + } else if (getChild() != this.fallbackChild) { + child(this.fallbackChild); + } + } + @Override public @NotNull Result onMousePressed(int mouseButton) { switch (mouseButton) { @@ -166,6 +180,17 @@ public W disableHoverOverlay() { return getThis(); } + @Override + public W invisible() { + if (this.background != null) { + Arrays.fill(this.background, IDrawable.EMPTY); + } + if (getBackground() == null) { + super.background(IDrawable.EMPTY); + } + return disableHoverBackground(); + } + protected W value(IIntValue value) { this.intValue = value; setValue(value); @@ -177,6 +202,22 @@ protected W value(IIntValue value) { return getThis(); } + @Override + public W child(IWidget child) { + this.fallbackChild = child; + return super.child(child); + } + + public W stateChild(int state, IWidget child) { + if (this.stateChildren == null) { + this.stateChildren = new IWidget[state + 1]; + } else if (this.stateChildren.length < state + 1) { + this.stateChildren = Arrays.copyOf(this.stateChildren, state + 1); + } + this.stateChildren[state] = child; + return getThis(); + } + /** * Sets the state dependent background. The images should be vertically stacked images from top to bottom * Note: The length must be already set! @@ -467,6 +508,8 @@ protected W stateCount(int stateCount) { this.overlay = checkArray(this.overlay, stateCount); this.hoverBackground = checkArray(this.hoverBackground, stateCount); this.hoverOverlay = checkArray(this.hoverOverlay, stateCount); + if (this.stateChildren == null) this.stateChildren = new IWidget[stateCount]; + else if (this.stateChildren.length < stateCount) this.stateChildren = Arrays.copyOf(this.stateChildren, stateCount); return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java b/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java index 945884578..3af614c56 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java @@ -3,6 +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.api.widget.IWidget; import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.theme.SelectableTheme; import com.cleanroommc.modularui.theme.WidgetTheme; @@ -101,6 +102,10 @@ public ToggleButton invertSelected(boolean invert) { return getThis(); } + public ToggleButton child(boolean selected, IWidget widget) { + return stateChild(selected ? 1 : 0, widget); + } + public boolean invertSelected() { return this.invert; } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java new file mode 100644 index 000000000..9bf52fe5a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -0,0 +1,210 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.IPanelHandler; +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widget.sizer.Flex; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +@ApiStatus.Experimental +public class ContextMenuButton> extends Widget implements IContextMenuOption, Interactable { + + private Direction direction = Direction.DOWN; + private boolean requiresClick; + + private ContextMenuList menuList; + private boolean open, softOpen; + private IPanelHandler panelHandler; + + public boolean isOpen() { + return open; + } + + public boolean isSoftOpen() { + return softOpen; + } + + public void toggleMenu(boolean soft) { + if (this.open) { + if (this.softOpen) { + if (soft) { + closeMenu(true); + } else { + this.softOpen = false; + } + } else if (!soft) { + closeMenu(false); + } + } else { + openMenu(soft); + } + } + + public void openMenu(boolean soft) { + if (this.open) { + if (this.softOpen && !soft) { + this.softOpen = false; + } + return; + } + initMenuList(); + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.openSubMenu(getMenuList()); + } else { + getPanelHandler().openPanel(); + } + this.open = true; + this.softOpen = soft; + } + + public void closeMenu(boolean soft) { + if (!this.open || (!this.softOpen && soft)) return; + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.remove(getMenuList()); + } else { + getPanelHandler().closePanel(); + } + this.open = false; + this.softOpen = false; + } + + private ContextMenuList getMenuList() { + return this.menuList; + } + + private void initMenuList() { + if (this.menuList == null) { + this.menuList = new ContextMenuList<>("no_list") + .width(50) + .maxSize(30) + .child(new ContextMenuOption<>() + .widthRel(50) + .height(12) + .overlay(IKey.str("No options supplied"))); + } + this.menuList.setSource(this); + this.menuList.relative(this); + this.menuList.bypassLayerRestriction(); + this.direction.positioner.accept(this.menuList.flex()); + } + + private IPanelHandler getPanelHandler() { + if (this.panelHandler == null) { + this.panelHandler = IPanelHandler.simple(getPanel(), (parentPanel, player) -> new MenuPanel(getMenuList()), true); + } + return this.panelHandler; + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + toggleMenu(false); + return Result.SUCCESS; + } + + @Override + public void onMouseEnterArea() { + super.onMouseEnterArea(); + if (!this.requiresClick) { + openMenu(true); + } + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + public void checkClose() { + if (!this.requiresClick && !isSelfOrChildHovered()) { + closeMenu(true); + if (getParent() instanceof ContextMenuList parentMenuList) { + parentMenuList.checkClose(); + } + } + } + + @Override + public void closeParent() { + closeMenu(false); + } + + @Override + public boolean isSelfOrChildHovered() { + if (IContextMenuOption.super.isSelfOrChildHovered()) return true; + if (!isOpen() || this.menuList == null) return false; + return this.menuList.isSelfOrChildHovered(); + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return isValid() && getPanel() instanceof MenuPanel ? theme.getWidgetTheme(IThemeApi.MENU_OPTION) : theme.getButtonTheme(); + } + + public W menuList(ContextMenuList menuList) { + this.menuList = menuList; + return getThis(); + } + + public W direction(Direction direction) { + this.direction = direction; + return getThis(); + } + + public W requiresClick() { + this.requiresClick = true; + return getThis(); + } + + public W openUp() { + return direction(Direction.UP); + } + + public W openDown() { + return direction(Direction.DOWN); + } + + public W openLeftUp() { + return direction(Direction.LEFT_UP); + } + + public W openLeftDown() { + return direction(Direction.LEFT_DOWN); + } + + public W openRightUp() { + return direction(Direction.RIGHT_UP); + } + + public W openRightDown() { + return direction(Direction.RIGHT_DOWN); + } + + public W openCustom() { + return direction(Direction.UNDEFINED); + } + + public enum Direction { + UP(flex -> flex.bottomRel(1f)), + DOWN(flex -> flex.topRel(1f)), + LEFT_UP(flex -> flex.rightRel(1f).bottom(0)), + LEFT_DOWN(flex -> flex.rightRel(1f).top(0)), + RIGHT_UP(flex -> flex.leftRel(1f).bottom(0)), + RIGHT_DOWN(flex -> flex.leftRel(1f).top(0)), + UNDEFINED(flex -> {}); + + private final Consumer positioner; + + Direction(Consumer positioner) { + this.positioner = positioner; + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java new file mode 100644 index 000000000..0ab572871 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java @@ -0,0 +1,81 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widgets.ListWidget; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Experimental +public class ContextMenuList> extends ListWidget { + + private final String name; + private ContextMenuButton source; + + public ContextMenuList(String name) { + this.name = name; + padding(2); + } + + public void close() { + if (this.source != null) { + this.source.closeMenu(false); + } + } + + @NotNull + @Override + public String getName() { + return name; + } + + public boolean isSelfOrChildHovered() { + if (isBelowMouse()) return true; + for (IWidget option : getTypeChildren()) { + if ((option instanceof IContextMenuOption menuOption && menuOption.isSelfOrChildHovered()) || option.isBelowMouse()) { + return true; + } + } + return false; + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + if (!child.flex().hasHeight()) { + child.flex().height(12); + } + if (!child.flex().hasWidth()) { + child.flex().widthRel(1f); + } + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getWidgetTheme(IThemeApi.CONTEXT_MENU); + } + + public void checkClose() { + if (this.source != null && !this.source.isBelowMouse() && !isSelfOrChildHovered()) { + this.source.closeMenu(true); + this.source.checkClose(); + } + } + + void setSource(ContextMenuButton menuButton) { + this.source = menuButton; + } + + protected ContextMenuButton getSource() { + return source; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java new file mode 100644 index 000000000..7c34decff --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java @@ -0,0 +1,31 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widget.DelegatingSingleChildWidget; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public class ContextMenuOption> extends DelegatingSingleChildWidget implements IContextMenuOption { + + @Override + protected void onChildAdd(IWidget child) { + if (!child.flex().hasHeight()) { + child.flex().height(12); + } + if (!child.flex().hasWidth()) { + child.flex().widthRel(1f); + } + /*if (child instanceof Widget widget && widget.getWidgetThemeOverride() == null) { + widget.widgetTheme(IThemeApi.MENU_OPTION); + }*/ + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getWidgetTheme(IThemeApi.MENU_OPTION); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java new file mode 100644 index 000000000..0c577c703 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java @@ -0,0 +1,22 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widget.WidgetTree; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.Objects; + +@ApiStatus.Experimental +public interface IContextMenuOption extends IWidget { + + default void closeParent() { + ContextMenuList menuList = WidgetTree.findParent(this, ContextMenuList.class); + Objects.requireNonNull(menuList); + menuList.close(); + } + + default boolean isSelfOrChildHovered() { + return isBelowMouse(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java new file mode 100644 index 000000000..d93d9852f --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java @@ -0,0 +1,36 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.screen.ModularPanel; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public class MenuPanel extends ModularPanel { + + public MenuPanel(ContextMenuList menuList) { + super(menuList.getName()); + fullScreenInvisible(); + child(menuList); + } + + public void openSubMenu(ContextMenuList menuList) { + child(menuList); + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + child.scheduleResize(); + } + + @Override + public boolean isDraggable() { + return false; + } + + @Override + public boolean closeOnOutOfBoundsClick() { + return true; + } +} diff --git a/src/main/resources/assets/modularui/textures/gui/background/menu.png b/src/main/resources/assets/modularui/textures/gui/background/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..a30f24e36fd68ed85b758a9a8a1bc5e042bbfc53 GIT binary patch literal 602 zcmV-g0;TEX>4Tx04R}tkv&MmP!xqvQ$;Bi2MdZgWN4i%6curlDi*;)X)CnqVDi#GXws0R zxHt-~1qXi?s}3&Cx;nTDg5VE`yWphgA|>9J6k5di;PO7sd*^W9eSpxcGS%#f0jg#h z=|o)20!X5qQM?&0J6U6f~e-}`e7C4rtTK|Hf* z>74h8L#!+*#OK5l1~o|h$aUG}H_j!81)do)vgvu^5V2V5V!4Z1*-(k6iNlJjQNECK zS>e3JS*_MtyHEbYU_o2SaGh!l2`nLr6hz3Vqk<|dL}}MZF_EV8xQBn#@u$coldA$o zjs?`9LUR1zfAD*@W^roLO$x?=-WS{chyZ=NK&xTf-^aGyIsyF8z?IhV*P6iWC+Urj z7Cr(7w}Ff6jwbH`mpj17lP(#OBl)R>Vi9;hqi@OsL$^R+&7E8O9H$RJmS(kl0~{Oz z<0Z;o_jq@I_uT%y)1KcC*0plaO~Nq#00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4#NNd4#NS*Z>VGd000McNliru=?WJLGXokFCNTg202y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001&cL_t(I%VR7oEd0+v1;E0gPU}B&W o7&U0rpizTHszEHU@`O?V0M3;Mdx)t(GXMYp07*qoM6N<$g6akUg#Z8m literal 0 HcmV?d00001 From d752265267c991ef90e6710a4ce427a85643af96 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 14 Nov 2025 13:42:15 +0100 Subject: [PATCH 2/2] a bunch of things --- .../com/cleanroommc/modularui/GuiError.java | 13 +- .../modularui/GuiErrorHandler.java | 4 +- .../modularui/api/layout/IResizeParent.java | 76 +++ .../modularui/api/layout/IResizeable.java | 71 +-- .../modularui/api/layout/IResizeable2.java | 120 ++++ .../modularui/api/widget/IDraggable.java | 2 +- .../modularui/api/widget/IGuiElement.java | 8 +- .../modularui/api/widget/IPositioned.java | 3 +- .../modularui/api/widget/IWidget.java | 37 +- .../jei/GhostIngredientTarget.java | 11 +- .../jei/ModularScreenJEIHandler.java | 4 +- .../modularui/overlay/OverlayStack.java | 6 +- .../modularui/screen/ClientScreenHandler.java | 3 +- .../modularui/screen/viewport/GuiContext.java | 7 +- .../screen/viewport/ModularGuiContext.java | 15 +- .../utils/serialization/ByteBufAdapters.java | 2 +- .../modularui/widget/AbstractWidget.java | 353 +++++++++++ .../widget/DelegatingSingleChildWidget.java | 4 +- .../modularui/widget/DragHandle.java | 3 +- .../modularui/widget/EmptyWidget.java | 3 +- .../modularui/widget/InternalWidgetTree.java | 10 +- .../modularui/widget/RenderNode.java | 27 + .../cleanroommc/modularui/widget/Widget.java | 341 +---------- .../modularui/widget/WidgetNode.java | 14 + .../modularui/widget/WidgetTree.java | 2 +- .../modularui/widget/sizer/Area.java | 7 - .../modularui/widget/sizer/AreaResizer.java | 15 + .../widget/sizer/DimensionSizer.java | 44 +- .../modularui/widget/sizer/IUnResizeable.java | 41 +- .../modularui/widget/sizer/ResizeNode.java | 87 +++ .../widget/sizer/StandardResizer.java | 577 ++++++++++++++++++ .../modularui/widget/sizer/StaticResizer.java | 88 +++ .../widget/sizer/WidgetResizeNode.java | 23 + .../modularui/widgets/SortableListWidget.java | 7 +- 34 files changed, 1485 insertions(+), 543 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java create mode 100644 src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/RenderNode.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/WidgetNode.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java diff --git a/src/main/java/com/cleanroommc/modularui/GuiError.java b/src/main/java/com/cleanroommc/modularui/GuiError.java index fcf596241..077f3de6b 100644 --- a/src/main/java/com/cleanroommc/modularui/GuiError.java +++ b/src/main/java/com/cleanroommc/modularui/GuiError.java @@ -1,9 +1,6 @@ package com.cleanroommc.modularui; -import com.cleanroommc.modularui.api.widget.IGuiElement; - -import com.cleanroommc.modularui.network.NetworkHandler; - +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.network.NetworkUtils; import org.apache.logging.log4j.Level; @@ -12,7 +9,7 @@ public class GuiError { - public static void throwNew(IGuiElement guiElement, Type type, String msg) { + public static void throwNew(IWidget guiElement, Type type, String msg) { if (NetworkUtils.isClient()) { GuiErrorHandler.INSTANCE.pushError(guiElement, type, msg); } @@ -20,10 +17,10 @@ public static void throwNew(IGuiElement guiElement, Type type, String msg) { private final Level level = Level.ERROR; private final String msg; - private final IGuiElement reference; + private final IWidget reference; private final Type type; - protected GuiError(String msg, IGuiElement reference, Type type) { + protected GuiError(String msg, IWidget reference, Type type) { this.msg = msg; this.reference = reference; this.type = type; @@ -33,7 +30,7 @@ public Level getLevel() { return level; } - public IGuiElement getReference() { + public IWidget getReference() { return reference; } diff --git a/src/main/java/com/cleanroommc/modularui/GuiErrorHandler.java b/src/main/java/com/cleanroommc/modularui/GuiErrorHandler.java index 2251e265c..4030ad77e 100644 --- a/src/main/java/com/cleanroommc/modularui/GuiErrorHandler.java +++ b/src/main/java/com/cleanroommc/modularui/GuiErrorHandler.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui; -import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IWidget; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -25,7 +25,7 @@ public void clear() { this.errors.clear(); } - void pushError(IGuiElement reference, GuiError.Type type, String msg) { + void pushError(IWidget reference, GuiError.Type type, String msg) { GuiError error = new GuiError(msg, reference, type); if (this.errorSet.add(error)) { ModularUI.LOGGER.log(error.getLevel(), error); diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java new file mode 100644 index 000000000..e34b50421 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java @@ -0,0 +1,76 @@ +package com.cleanroommc.modularui.api.layout; + +import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.widget.sizer.Area; + +public interface IResizeParent { + + /** + * @return area of the element + */ + // TODO doesnt fit with the other api methods in this interface + Area getArea(); + + /** + * @return true if the relative x position is calculated + */ + boolean isXCalculated(); + + /** + * @return true if the relative y position is calculated + */ + boolean isYCalculated(); + + /** + * @return true if the width is calculated + */ + boolean isWidthCalculated(); + + /** + * @return true if the height is calculated + */ + boolean isHeightCalculated(); + + boolean areChildrenCalculated(); + + boolean isLayoutDone(); + + boolean canRelayout(boolean isParentLayout); + + default boolean isSizeCalculated(GuiAxis axis) { + return axis.isHorizontal() ? isWidthCalculated() : isHeightCalculated(); + } + + default boolean isPosCalculated(GuiAxis axis) { + return axis.isHorizontal() ? isXCalculated() : isYCalculated(); + } + + /** + * @return true if the relative position and size are fully calculated + */ + default boolean isSelfFullyCalculated(boolean isParentLayout) { + return isSelfFullyCalculated() && !canRelayout(isParentLayout); + } + + default boolean isSelfFullyCalculated() { + return isXCalculated() && isYCalculated() && isWidthCalculated() && isHeightCalculated(); + } + + default boolean isFullyCalculated() { + return isSelfFullyCalculated() && areChildrenCalculated() && isLayoutDone(); + } + + default boolean isFullyCalculated(boolean isParentLayout) { + return isFullyCalculated() && !canRelayout(isParentLayout); + } + + /** + * @return true if margin and padding are applied on the x-axis + */ + boolean isXMarginPaddingApplied(); + + /** + * @return true if margin and padding are applied on the y-axis + */ + boolean isYMarginPaddingApplied(); +} diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java index 715f8fdc4..5b18bdd14 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java @@ -8,7 +8,7 @@ * An interface that handles resizing of widgets. * Usually this interface is not implemented by the users of this library or will even interact with it. */ -public interface IResizeable { +public interface IResizeable extends IResizeParent { /** * Called once before resizing @@ -40,65 +40,6 @@ public interface IResizeable { */ default void applyPos(IGuiElement guiElement) {} - /** - * @return area of the element - */ - // TODO doesnt fit with the other api methods in this interface - Area getArea(); - - /** - * @return true if the relative x position is calculated - */ - boolean isXCalculated(); - - /** - * @return true if the relative y position is calculated - */ - boolean isYCalculated(); - - /** - * @return true if the width is calculated - */ - boolean isWidthCalculated(); - - /** - * @return true if the height is calculated - */ - boolean isHeightCalculated(); - - boolean areChildrenCalculated(); - - boolean isLayoutDone(); - - default boolean isSizeCalculated(GuiAxis axis) { - return axis.isHorizontal() ? isWidthCalculated() : isHeightCalculated(); - } - - default boolean isPosCalculated(GuiAxis axis) { - return axis.isHorizontal() ? isXCalculated() : isYCalculated(); - } - - /** - * @return true if the relative position and size are fully calculated - */ - default boolean isSelfFullyCalculated(boolean isParentLayout) { - return isSelfFullyCalculated() && !canRelayout(isParentLayout); - } - - default boolean isSelfFullyCalculated() { - return isXCalculated() && isYCalculated() && isWidthCalculated() && isHeightCalculated(); - } - - default boolean isFullyCalculated() { - return isSelfFullyCalculated() && areChildrenCalculated() && isLayoutDone(); - } - - default boolean isFullyCalculated(boolean isParentLayout) { - return isSelfFullyCalculated(isParentLayout) && areChildrenCalculated() && isLayoutDone(); - } - - boolean canRelayout(boolean isParentLayout); - void setChildrenResized(boolean resized); void setLayoutDone(boolean done); @@ -182,14 +123,4 @@ default void setMarginPaddingApplied(GuiAxis axis, boolean b) { setYMarginPaddingApplied(b); } } - - /** - * @return true if margin and padding are applied on the x-axis - */ - boolean isXMarginPaddingApplied(); - - /** - * @return true if margin and padding are applied on the y-axis - */ - boolean isYMarginPaddingApplied(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java new file mode 100644 index 000000000..c632c4081 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java @@ -0,0 +1,120 @@ +package com.cleanroommc.modularui.api.layout; + +import com.cleanroommc.modularui.api.GuiAxis; + +/** + * An interface that handles resizing of widgets. + * Usually this interface is not implemented by the users of this library or will even interact with it. + */ +public interface IResizeable2 extends IResizeParent { + + /** + * Called once before resizing + */ + void initResizing(); + + /** + * Resizes the given element + * + * @param isParentLayout if the parent is a layout widget + * @return true if element is fully resized + */ + boolean resize(boolean isParentLayout); + + /** + * Called if {@link #resize(boolean)} returned false after children have been resized. + * + * @return if element is fully resized + */ + boolean postResize(); + + /** + * Called after all elements in the tree are resized and the absolute positions needs to be calculated from the + * relative postion. + */ + default void applyPos() {} + + void setChildrenResized(boolean resized); + + void setLayoutDone(boolean done); + + /** + * Marks position and size as calculated. + */ + void setResized(boolean x, boolean y, boolean w, boolean h); + + default void setPosResized(boolean x, boolean y) { + setResized(x, y, isWidthCalculated(), isHeightCalculated()); + } + + default void setSizeResized(boolean w, boolean h) { + setResized(isXCalculated(), isYCalculated(), w, h); + } + + default void setXResized(boolean v) { + setResized(v, isYCalculated(), isWidthCalculated(), isHeightCalculated()); + } + + default void setYResized(boolean v) { + setResized(isXCalculated(), v, isWidthCalculated(), isHeightCalculated()); + } + + default void setPosResized(GuiAxis axis, boolean v) { + if (axis.isHorizontal()) { + setXResized(v); + } else { + setYResized(v); + } + } + + default void setWidthResized(boolean v) { + setResized(isXCalculated(), isYCalculated(), v, isHeightCalculated()); + } + + default void setHeightResized(boolean v) { + setResized(isXCalculated(), isYCalculated(), isWidthCalculated(), v); + } + + default void setSizeResized(GuiAxis axis, boolean v) { + if (axis.isHorizontal()) { + setWidthResized(v); + } else { + setHeightResized(v); + } + } + + default void setResized(boolean b) { + setResized(b, b, b, b); + } + + default void updateResized() { + setResized(isXCalculated(), isYCalculated(), isWidthCalculated(), isHeightCalculated()); + } + + /** + * Sets if margin and padding on the x-axis is applied + * + * @param b true if margin and padding are applied + */ + void setXMarginPaddingApplied(boolean b); + + /** + * Sets if margin and padding on the y-axis is applied + * + * @param b true if margin and padding are applied + */ + void setYMarginPaddingApplied(boolean b); + + default void setMarginPaddingApplied(boolean b) { + setXMarginPaddingApplied(b); + setYMarginPaddingApplied(b); + } + + default void setMarginPaddingApplied(GuiAxis axis, boolean b) { + if (axis.isHorizontal()) { + setXMarginPaddingApplied(b); + } else { + setYMarginPaddingApplied(b); + } + } +} 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 f70bd174b..bb240f331 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IDraggable.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IDraggable.java @@ -44,7 +44,7 @@ public interface IDraggable { * @param widget current top most widget below the mouse * @return if the location is valid */ - default boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + default boolean canDropHere(int x, int y, @Nullable IWidget widget) { return true; } 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 2afb8ced1..8f9c64691 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java @@ -1,13 +1,17 @@ package com.cleanroommc.modularui.api.widget; -import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; + +import org.jetbrains.annotations.ApiStatus; /** * Base interface for gui elements. For example widgets. */ +@ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") +@Deprecated public interface IGuiElement { /** @@ -27,7 +31,7 @@ public interface IGuiElement { */ boolean hasParent(); - IResizeable resizer(); + ResizeNode resizer(); /** * @return the area this element occupies diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java b/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java index a61cc0d10..bed0136b0 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java @@ -3,6 +3,7 @@ import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Flex; +import com.cleanroommc.modularui.widget.sizer.StandardResizer; import com.cleanroommc.modularui.widget.sizer.Unit; import java.util.function.Consumer; @@ -16,7 +17,7 @@ @SuppressWarnings({"unused", "UnusedReturnValue"}) public interface IPositioned> { - Flex flex(); + StandardResizer flex(); Area getArea(); 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 b314c65cd..7affb2c43 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -1,7 +1,6 @@ package com.cleanroommc.modularui.api.widget; import com.cleanroommc.modularui.api.ITheme; -import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.screen.ModularPanel; @@ -10,6 +9,8 @@ import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Flex; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -162,12 +163,6 @@ default boolean hasChildren() { return !getChildren().isEmpty(); } - /** - * @return the panel this widget is in - */ - @NotNull - ModularPanel getPanel(); - /** * Returns if this element is enabled. Disabled elements are not drawn and can not be interacted with. If this is disabled, the children * will be considered disabled to without actually being disabled. @@ -248,10 +243,16 @@ default boolean hasParent() { */ ModularGuiContext getContext(); + /** + * @return the panel this widget is in + */ + @NotNull + ModularPanel getPanel(); + /** * @return flex of this widget. Creates a new one if it doesn't already have one. */ - Flex flex(); + //Flex flex(); /** * Does the same as {@link IPositioned#flex(Consumer)} @@ -259,24 +260,17 @@ default boolean hasParent() { * @param builder function to build flex * @return this */ - default IWidget flexBuilder(Consumer builder) { + /*default IWidget flexBuilder(Consumer builder) { builder.accept(flex()); return this; - } + }*/ /** * @return resizer of this widget */ @NotNull @Override - IResizeable resizer(); - - /** - * Sets the resizer of this widget. - * - * @param resizer resizer - */ - void resizer(IResizeable resizer); + ResizeNode resizer(); /** * Called before a widget is resized. @@ -296,12 +290,11 @@ default void postResize() {} /** * @return flex of this widget */ - Flex getFlex(); - - default boolean isExpanded() { + //Flex getFlex(); + /*default boolean isExpanded() { Flex flex = getFlex(); return flex != null && flex.isExpanded(); - } + }*/ @Nullable String getName(); diff --git a/src/main/java/com/cleanroommc/modularui/integration/jei/GhostIngredientTarget.java b/src/main/java/com/cleanroommc/modularui/integration/jei/GhostIngredientTarget.java index 61fe74606..4b1a040ca 100644 --- a/src/main/java/com/cleanroommc/modularui/integration/jei/GhostIngredientTarget.java +++ b/src/main/java/com/cleanroommc/modularui/integration/jei/GhostIngredientTarget.java @@ -1,6 +1,5 @@ package com.cleanroommc.modularui.integration.jei; -import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.integration.recipeviewer.RecipeViewerGhostIngredientSlot; @@ -14,11 +13,11 @@ @Optional.Interface(iface = "mezz.jei.api.gui.IGhostIngredientHandler$Target", modid = "jei") public class GhostIngredientTarget implements IGhostIngredientHandler.Target { - private final IGuiElement guiElement; + private final IWidget widget; private final RecipeViewerGhostIngredientSlot ghostSlot; public static GhostIngredientTarget of(RecipeViewerGhostIngredientSlot slot) { - if (slot instanceof IGuiElement guiElement) { + if (slot instanceof IWidget guiElement) { return new GhostIngredientTarget<>(guiElement, slot); } throw new IllegalArgumentException(); @@ -28,14 +27,14 @@ public static > GhostI return new GhostIngredientTarget<>(slot, slot); } - public GhostIngredientTarget(IGuiElement guiElement, RecipeViewerGhostIngredientSlot ghostSlot) { - this.guiElement = guiElement; + public GhostIngredientTarget(IWidget widget, RecipeViewerGhostIngredientSlot ghostSlot) { + this.widget = widget; this.ghostSlot = ghostSlot; } @Override public @NotNull Rectangle getArea() { - return this.guiElement.getArea(); + return this.widget.getArea(); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/integration/jei/ModularScreenJEIHandler.java b/src/main/java/com/cleanroommc/modularui/integration/jei/ModularScreenJEIHandler.java index e640a26c3..071479e49 100644 --- a/src/main/java/com/cleanroommc/modularui/integration/jei/ModularScreenJEIHandler.java +++ b/src/main/java/com/cleanroommc/modularui/integration/jei/ModularScreenJEIHandler.java @@ -1,7 +1,7 @@ package com.cleanroommc.modularui.integration.jei; import com.cleanroommc.modularui.api.IMuiScreen; -import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.integration.recipeviewer.RecipeViewerIngredientProvider; import net.minecraft.client.gui.GuiScreen; @@ -83,7 +83,7 @@ public List getGuiExtraAreas(@NotNull T guiContainer) { @Nullable @Override public Object getIngredientUnderMouse(@NotNull T guiContainer, int mouseX, int mouseY) { - IGuiElement hovered = guiContainer.getScreen().getContext().getTopHovered(); + IWidget hovered = guiContainer.getScreen().getContext().getTopHovered(); return hovered instanceof RecipeViewerIngredientProvider jip ? jip.getIngredient() : null; } } diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java index 50d2ecaa4..2c62c7df8 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java @@ -1,6 +1,6 @@ package com.cleanroommc.modularui.overlay; -import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.screen.ModularScreen; @@ -96,10 +96,10 @@ public static void onTick() { } @Nullable - public static IGuiElement getHoveredElement() { + public static IWidget getHoveredElement() { for (int i = overlay.size() - 1; i >= 0; i--) { ModularScreen screen = overlay.get(i); - IGuiElement hovered = screen.getContext().getTopHovered(); + IWidget hovered = screen.getContext().getTopHovered(); if (hovered == null) continue; return hovered; } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index 069fed627..53a0c2e28 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -6,7 +6,6 @@ import com.cleanroommc.modularui.api.IMuiScreen; import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.UpOrDown; -import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.core.mixins.early.minecraft.GuiAccessor; @@ -443,7 +442,7 @@ public static void drawContainer(ModularScreen muiScreen, GuiContainer mcScreen, muiScreen.drawForeground(); acc.setHoveredSlot(null); - IGuiElement hovered = muiScreen.getContext().getTopHovered(); + IWidget hovered = muiScreen.getContext().getTopHovered(); if (hovered instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot()) { acc.setHoveredSlot(vanillaSlot.getVanillaSlot()); } 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 3b76986d0..e998bd24e 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/GuiContext.java @@ -3,13 +3,12 @@ import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.drawable.IDrawable; -import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.widget.sizer.Area; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; - import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -46,14 +45,14 @@ public static GuiContext getDefault() { private long tick = 0; private int currentDrawingZ = 0; - public boolean isAbove(IGuiElement widget) { + public boolean isAbove(IWidget widget) { return isMouseAbove(widget.getArea()); } /** * @return true the mouse is anywhere above the widget */ - public boolean isMouseAbove(IGuiElement widget) { + public boolean isMouseAbove(IWidget widget) { return isMouseAbove(widget.getArea()); } diff --git a/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java index 79a4085d6..864629248 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java @@ -5,7 +5,6 @@ import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.widget.IDraggable; import com.cleanroommc.modularui.api.widget.IFocusedWidget; -import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.api.widget.ResizeDragArea; @@ -106,7 +105,7 @@ public boolean isHovered() { */ @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") @Deprecated - public boolean isHovered(IGuiElement guiElement) { + public boolean isHovered(IWidget guiElement) { return guiElement.isHovering(); } @@ -119,7 +118,7 @@ public boolean isHovered(IGuiElement guiElement) { */ @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") @Deprecated - public boolean isHoveredFor(IGuiElement guiElement, int ticks) { + public boolean isHoveredFor(IWidget guiElement, int ticks) { return guiElement.isHoveringFor(ticks); } @@ -141,9 +140,9 @@ public boolean isHoveredFor(IGuiElement guiElement, int ticks) { } /** - * @return all widgets which are below the mouse ({@link GuiContext#isAbove(IGuiElement)} is true) + * @return all widgets which are below the mouse ({@link GuiContext#isAbove(IWidget)} is true) */ - public Iterable getAllBelowMouse() { + public Iterable getAllBelowMouse() { return this.hoveredWidgets; } @@ -487,7 +486,7 @@ public void setSettings(UISettings settings) { } } - private static class HoveredIterable implements Iterable { + private static class HoveredIterable implements Iterable { private final PanelManager panelManager; @@ -497,7 +496,7 @@ private HoveredIterable(PanelManager panelManager) { @NotNull @Override - public Iterator iterator() { + public Iterator iterator() { return new Iterator<>() { private final Iterator panelIt = HoveredIterable.this.panelManager.getOpenPanels().iterator(); @@ -515,7 +514,7 @@ public boolean hasNext() { } @Override - public IGuiElement next() { + public IWidget next() { if (this.widgetIt == null || !this.widgetIt.hasNext()) { this.widgetIt = this.panelIt.next().getHovering().iterator(); } diff --git a/src/main/java/com/cleanroommc/modularui/utils/serialization/ByteBufAdapters.java b/src/main/java/com/cleanroommc/modularui/utils/serialization/ByteBufAdapters.java index 5334f3da5..a6ffdf210 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/serialization/ByteBufAdapters.java +++ b/src/main/java/com/cleanroommc/modularui/utils/serialization/ByteBufAdapters.java @@ -32,7 +32,7 @@ public byte[] deserialize(PacketBuffer buffer) throws IOException { @Override public void serialize(PacketBuffer buffer, byte[] u) throws IOException { - buffer.writeBytes(u); + buffer.writeByteArray(u); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java new file mode 100644 index 000000000..bc87530d8 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -0,0 +1,353 @@ +package com.cleanroommc.modularui.widget; + +import com.cleanroommc.modularui.api.widget.INotifyEnabled; +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.ModularGuiContext; +import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widget.sizer.Flex; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; +import com.cleanroommc.modularui.widget.sizer.StandardResizer; +import com.cleanroommc.modularui.widget.sizer.WidgetResizeNode; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.MustBeInvokedByOverriders; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public abstract class AbstractWidget implements IWidget { + + // gui context + private boolean valid = false; + private IWidget parent = null; + private ModularPanel panel = null; + private ModularGuiContext context = null; + + @Nullable private String name; + private boolean enabled = true; + private int timeHovered = -1; + private int timeBelowMouse = -1; + private boolean excludeAreaInRecipeViewer = false; + + private final Area area = new Area(); + private WidgetResizeNode resizer; + + /** + * Returns the screen of the panel of this widget is being opened in. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public ModularScreen getScreen() { + return getPanel().getScreen(); + } + + @Override + public void scheduleResize() { + this.resizer.markDirty(); + } + + @Override + public boolean requiresResize() { + return this.resizer.requiresResize(); + } + + @MustBeInvokedByOverriders + @Override + public void onResized() { + this.requiresResize = false; + } + + /** + * Called when a panel is opened. Use {@link #onInit()} and {@link #afterInit()} for custom logic. + * + * @param parent the parent this element belongs to + * @param late true if this is called some time after the widget tree of the parent has been initialised + */ + @ApiStatus.Internal + @Override + public final void initialise(@NotNull IWidget parent, boolean late) { + this.timeHovered = -1; + this.timeBelowMouse = -1; + if (!(this instanceof ModularPanel)) { + this.parent = parent; + this.panel = parent.getPanel(); + this.context = parent.getContext(); + getArea().setPanelLayer(this.panel.getArea().getPanelLayer()); + getArea().z(parent.getArea().z() + 1); + /*if (this.guiActionListeners != null) { + for (IGuiAction action : this.guiActionListeners) { + this.context.getScreen().registerGuiActionListener(action); + } + }*/ + } + /*if (this.value != null && this.syncKey != null) { + throw new IllegalStateException("Widget has a value and a sync key for a synced value. This is not allowed!"); + } + this.valid = true; + if (!getScreen().isClientOnly()) { + initialiseSyncHandler(getScreen().getSyncManager(), late); + } + if (isExcludeAreaInRecipeViewer()) { + getContext().getRecipeViewerSettings().addExclusionArea(this); + }*/ + onInit(); + if (hasChildren()) { + for (IWidget child : getChildren()) { + child.initialise(this, false); + } + } + afterInit(); + this.resizer.onResized(); + } + + /** + * Called after this widget is initialised and before the children are initialised. + */ + @ApiStatus.OverrideOnly + public void onInit() {} + + /** + * Called after this widget is initialised and after the children are initialised. + */ + @ApiStatus.OverrideOnly + public void afterInit() {} + + /** + * Called when this widget is removed from the widget tree or after the panel is closed. + * Overriding this is fine, but super must be called. + */ + @MustBeInvokedByOverriders + @Override + public void dispose() { + if (isValid()) { + /*if (this.guiActionListeners != null) { + for (IGuiAction action : this.guiActionListeners) { + this.context.getScreen().removeGuiActionListener(action); + } + } + if (isExcludeAreaInRecipeViewer()) { + getContext().getRecipeViewerSettings().removeExclusionArea(this); + }*/ + } + if (hasChildren()) { + for (IWidget child : getChildren()) { + child.dispose(); + } + } + if (!(this instanceof ModularPanel)) { + this.panel = null; + this.parent = null; + this.context = null; + } + this.timeHovered = -1; + this.timeBelowMouse = -1; + this.valid = false; + } + + // ------------------- + // === Gui context === + // ------------------- + + /** + * Returns if this widget is currently part of an open panel. Only if this is true information about parent, panel and gui context can + * be obtained. + * + * @return true if this widget is part of an open panel + */ + @Override + public boolean isValid() { + return valid; + } + + @Override + public void onUpdate() { + if (isHovering()) this.timeHovered++; + if (isBelowMouse()) this.timeBelowMouse++; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseStartHover() { + this.timeHovered = 0; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseEndHover() { + this.timeHovered = -1; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseEnterArea() { + this.timeBelowMouse = 0; + } + + @MustBeInvokedByOverriders + @Override + public void onMouseLeaveArea() { + this.timeBelowMouse = -1; + } + + @Override + public boolean isHoveringFor(int ticks) { + return timeHovered >= ticks; + } + + @Override + public boolean isBelowMouseFor(int ticks) { + return timeBelowMouse >= ticks; + } + + public int getTicksHovered() { + return timeHovered; + } + + public int getTicksBelowMouse() { + return timeBelowMouse; + } + + /** + * Returns the area of this widget. This contains information such as position, size, relative position to parent, padding and margin. + * Even tho this is a mutable object, you should refrain from modifying the values. + * + * @return area of this widget + */ + @Override + public Area getArea() { + return area; + } + + /** + * Returns if this widget is currently enabled. Disabled widgets (and all its children) are not rendered and can't be interacted with. + * + * @return true if this widget is enabled. + */ + @Override + public boolean isEnabled() { + return this.enabled; + } + + /** + * Sets enabled state. Disabled widgets (and all its children) are not rendered and can't be interacted with. + * + * @param enabled enabled state + */ + @Override + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + this.enabled = enabled; + if (isValid() && getParent() instanceof INotifyEnabled notifyEnabled) { + notifyEnabled.onChildChangeEnabled(this, enabled); + } + } + } + + /** + * Returns the parent of this widget. If this is a {@link ModularPanel} this will always return null contrary to the annotation. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public @NotNull IWidget getParent() { + if (!isValid()) { + throw new IllegalStateException(this + " is not in a valid state!"); + } + return parent; + } + + /** + * Returns the gui context of the screen this widget is part of. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public ModularGuiContext getContext() { + if (!isValid()) { + throw new IllegalStateException(this + " is not in a valid state!"); + } + return context; + } + + /** + * Used to set the gui context on panels internally. + */ + @ApiStatus.Internal + protected final void setContext(ModularGuiContext context) { + this.context = context; + } + + /** + * Returns the panel of this widget is being opened in. + * + * @return the screen of this widget + * @throws IllegalStateException if {@link #isValid()} returns false + */ + @Override + public @NotNull ModularPanel getPanel() { + if (!isValid()) { + throw new IllegalStateException(this + " is not in a valid state!"); + } + return panel; + } + + @Override + public @NotNull ResizeNode resizer() { + if (this.resizer == null) { + this.resizer = new StandardResizer(this); + } + return this.resizer; + } + + public void resizer(WidgetResizeNode resizer) { + this.resizer = Objects.requireNonNull(resizer); + } + + /** + * Returns the flex of this widget. This is responsible for calculating size, pos and relative pos. + * Originally this was intended to be modular for custom flex class. May come back to this in the future. + * Same as {@link #flex()}. + * + * @return flex of this widget + */ + @Override + public StandardResizer getFlex() { + return null; + } + + @Override + public @Nullable String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + /** + * This is only used in {@link #toString()}. + * + * @return the simple class name or other fitting name + */ + protected String getTypeName() { + return getClass().getSimpleName(); + } + + /** + * @return the simple class plus the debug name if set + */ + @Override + public String toString() { + if (getName() != null) { + return getTypeName() + "#" + getName(); + } + return getTypeName(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java index 44eb79ce3..6948c79dc 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -1,10 +1,10 @@ package com.cleanroommc.modularui.widget; -import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.api.widget.IDelegatingWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Flex; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; import org.jetbrains.annotations.NotNull; @@ -62,7 +62,7 @@ public Flex getFlex() { } @Override - public @NotNull IResizeable resizer() { + public @NotNull ResizeNode resizer() { return getDelegate() != null ? getDelegate().resizer() : super.resizer(); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java b/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java index d857bb504..b06f42e60 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DragHandle.java @@ -3,7 +3,6 @@ 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.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.DraggablePanelWrapper; import com.cleanroommc.modularui.screen.ModularPanel; @@ -58,7 +57,7 @@ public void onDrag(int mouseButton, long timeSinceLastClick) { } @Override - public boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + public boolean canDropHere(int x, int y, @Nullable IWidget widget) { return this.parentDraggable != null && this.parentDraggable.canDropHere(x, y, widget); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java index 1aa36bab9..19cfc1eb3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java @@ -9,6 +9,7 @@ import com.cleanroommc.modularui.theme.WidgetThemeEntry; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Flex; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -127,7 +128,7 @@ public Flex flex() { } @Override - public @NotNull IResizeable resizer() { + public @NotNull ResizeNode resizer() { return this.flex; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java index 0d1230508..df8f1b03d 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java @@ -2,11 +2,11 @@ import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.layout.ILayoutWidget; -import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.api.layout.IViewport; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; import com.cleanroommc.modularui.widgets.layout.IExpander; import net.minecraft.client.renderer.GlStateManager; @@ -194,7 +194,7 @@ static void drawTreeForeground(IWidget parent, ModularGuiContext context) { static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolean isParentLayout) { boolean alreadyCalculated = false; // first try to resize this widget - IResizeable resizer = widget.resizer(); + ResizeNode resizer = widget.resizer(); ILayoutWidget layout = widget instanceof ILayoutWidget layoutWidget ? layoutWidget : null; boolean isLayout = layout != null; if (init) { @@ -205,7 +205,7 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea // if this is not the first time check if this widget is already resized alreadyCalculated = resizer.isFullyCalculated(isParentLayout); } - boolean selfFullyCalculated = resizer.isSelfFullyCalculated() || resizer.resize(widget, isParentLayout); + boolean selfFullyCalculated = resizer.isSelfFullyCalculated() || resizer.resize(isParentLayout); GuiAxis expandAxis = widget instanceof IExpander expander ? expander.getExpandAxis() : null; // now resize all children and collect children which could not be fully calculated @@ -213,7 +213,7 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea if (!resizer.areChildrenCalculated() && widget.hasChildren()) { anotherResize = new ArrayList<>(); for (IWidget child : widget.getChildren()) { - if (init) child.flex().checkExpanded(expandAxis); + if (init) child.resizer().checkExpanded(expandAxis); if (!resizeWidget(child, init, onOpen, isLayout)) { anotherResize.add(child); } @@ -232,7 +232,7 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea } // post resize this widget if possible - resizer.postResize(widget); + resizer.postResize(); if (layout != null && shouldLayout) { layoutSuccessful &= layout.postLayoutWidgets(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/RenderNode.java b/src/main/java/com/cleanroommc/modularui/widget/RenderNode.java new file mode 100644 index 000000000..01e331211 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/RenderNode.java @@ -0,0 +1,27 @@ +package com.cleanroommc.modularui.widget; + +import com.cleanroommc.modularui.api.widget.IWidget; + +import java.util.List; + +public class RenderNode implements WidgetNode { + + private IWidget linkedWidget; + private RenderNode parent; + private List children; + + @Override + public IWidget getWidget() { + return linkedWidget; + } + + @Override + public RenderNode getParent() { + return parent; + } + + @Override + public List getChildren() { + return children; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index fbabb11a4..195a64563 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -3,18 +3,14 @@ import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.IThemeApi; import com.cleanroommc.modularui.api.drawable.IDrawable; -import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.value.IValue; import com.cleanroommc.modularui.api.widget.IDragResizeable; import com.cleanroommc.modularui.api.widget.IGuiAction; -import com.cleanroommc.modularui.api.widget.INotifyEnabled; import com.cleanroommc.modularui.api.widget.IPositioned; import com.cleanroommc.modularui.api.widget.ISynced; import com.cleanroommc.modularui.api.widget.ITooltip; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.screen.ModularPanel; -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; @@ -23,10 +19,7 @@ import com.cleanroommc.modularui.value.sync.ModularSyncManager; import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; -import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.Bounds; -import com.cleanroommc.modularui.widget.sizer.Flex; -import com.cleanroommc.modularui.widget.sizer.IUnResizeable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.MustBeInvokedByOverriders; @@ -49,25 +42,12 @@ * * @param the type of this widget. This is used for proper return types in builder like methodsY */ -public class Widget> implements IWidget, IPositioned, ITooltip, ISynced { +public class Widget> extends AbstractWidget implements IPositioned, ITooltip, ISynced { // other - @Nullable private String name; - private boolean enabled = true; - private int timeHovered = -1; - private int timeBelowMouse = -1; private boolean excludeAreaInRecipeViewer = false; - // gui context - private boolean valid = false; - private IWidget parent = null; - private ModularPanel panel = null; - private ModularGuiContext context = null; // sizing - private final Area area = new Area(); - private final Flex flex = new Flex(this); - private IResizeable resizer = this.flex; private BiConsumer transform; - private boolean requiresResize = false; // syncing @Nullable private IValue value; @Nullable private String syncKey; @@ -88,61 +68,6 @@ public class Widget> implements IWidget, IPositioned, ITo // === Lifecycle === // ----------------- - /** - * Called when a panel is opened. Use {@link #onInit()} and {@link #afterInit()} for custom logic. - * - * @param parent the parent this element belongs to - * @param late true if this is called some time after the widget tree of the parent has been initialised - */ - @ApiStatus.Internal - @Override - public final void initialise(@NotNull IWidget parent, boolean late) { - this.timeHovered = -1; - this.timeBelowMouse = -1; - if (!(this instanceof ModularPanel)) { - this.parent = parent; - this.panel = parent.getPanel(); - this.context = parent.getContext(); - getArea().setPanelLayer(this.panel.getArea().getPanelLayer()); - getArea().z(parent.getArea().z() + 1); - if (this.guiActionListeners != null) { - for (IGuiAction action : this.guiActionListeners) { - this.context.getScreen().registerGuiActionListener(action); - } - } - } - if (this.value != null && this.syncKey != null) { - throw new IllegalStateException("Widget has a value and a sync key for a synced value. This is not allowed!"); - } - this.valid = true; - if (!getScreen().isClientOnly()) { - initialiseSyncHandler(getScreen().getSyncManager(), late); - } - if (isExcludeAreaInRecipeViewer()) { - getContext().getRecipeViewerSettings().addExclusionArea(this); - } - onInit(); - if (hasChildren()) { - for (IWidget child : getChildren()) { - child.initialise(this, false); - } - } - afterInit(); - this.requiresResize = false; - } - - /** - * Called after this widget is initialised and before the children are initialised. - */ - @ApiStatus.OverrideOnly - public void onInit() {} - - /** - * Called after this widget is initialised and after the children are initialised. - */ - @ApiStatus.OverrideOnly - public void afterInit() {} - /** * Retrieves, initialises and verifies a linked sync handler. * Custom logic should be handled in {@link #isValidSyncHandler(SyncHandler)}. @@ -172,7 +97,7 @@ public void dispose() { if (isValid()) { if (this.guiActionListeners != null) { for (IGuiAction action : this.guiActionListeners) { - this.context.getScreen().removeGuiActionListener(action); + getScreen().removeGuiActionListener(action); } } if (isExcludeAreaInRecipeViewer()) { @@ -184,14 +109,7 @@ public void dispose() { child.dispose(); } } - if (!(this instanceof ModularPanel)) { - this.panel = null; - this.parent = null; - this.context = null; - } - this.timeHovered = -1; - this.timeBelowMouse = -1; - this.valid = false; + super.dispose(); } // ----------------- @@ -546,8 +464,7 @@ public W invisible() { @MustBeInvokedByOverriders @Override public void onUpdate() { - if (isHovering()) this.timeHovered++; - if (isBelowMouse()) this.timeBelowMouse++; + super.onUpdate(); if (this.onUpdateListener != null) { this.onUpdateListener.accept(getThis()); } @@ -578,7 +495,7 @@ public W listenGuiAction(IGuiAction action) { } this.guiActionListeners.add(action); if (isValid()) { - this.context.getScreen().registerGuiActionListener(action); + getScreen().registerGuiActionListener(action); } return getThis(); } @@ -644,86 +561,9 @@ public int getDefaultHeight() { return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultHeight() : 18; } - @Override - public void scheduleResize() { - this.requiresResize = true; - } - - @Override - public boolean requiresResize() { - return this.requiresResize; - } - - @MustBeInvokedByOverriders - @Override - public void onResized() { - this.requiresResize = false; - } - - /** - * Returns the area of this widget. This contains information such as position, size, relative position to parent, padding and margin. - * Even tho this is a mutable object, you should refrain from modifying the values. - * - * @return area of this widget - */ - @Override - public Area getArea() { - return this.area; - } - - /** - * Returns the flex of this widget. This is responsible for calculating size, pos and relative pos. - * Originally this was intended to be modular for custom flex class. May come back to this in the future. - * Same as {@link #flex()}. - * - * @return flex of this widget - */ - @Override - public Flex getFlex() { - return this.flex; - } - - /** - * Returns the flex of this widget. This is responsible for calculating size, pos and relative pos. - * Originally this was intended to be modular for custom flex class. May come back to this in the future. - * Same as {@link #getFlex()}. - * - * @return flex of this widget - */ - @Override - public Flex flex() { - return getFlex(); - } - - /** - * Returns the resizer of this widget. This is actually the field responsible for resizing this widget. - * Within MUI this is always the same as {@link #flex()}. Custom resizer have not been tested. - * The relevance of separating flex and resizer is left to be investigated in the future. - * - * @return the resizer of this widget - */ - @NotNull - @Override - public IResizeable resizer() { - return this.resizer; - } - - /** - * Sets the resizer of this widget, which is responsible for resizing this widget. - * Within MUI this setter is never used. Custom resizer have not been tested. - * The relevance of separating flex and resizer is left to be investigated in the future. - * - * @param resizer resizer - */ - @ApiStatus.Experimental - @Override - public void resizer(IResizeable resizer) { - this.resizer = resizer != null ? resizer : IUnResizeable.INSTANCE; - } - @Override public void transform(IViewportStack stack) { - IWidget.super.transform(stack); + super.transform(stack); if (this.transform != null) { this.transform.accept(getThis(), stack); } @@ -734,82 +574,6 @@ public W transform(BiConsumer transform) { return getThis(); } - // ------------------- - // === Gui context === - // ------------------- - - /** - * Returns if this widget is currently part of an open panel. Only if this is true information about parent, panel and gui context can - * be obtained. - * - * @return true if this widget is part of an open panel - */ - @Override - public final boolean isValid() { - return this.valid; - } - - /** - * Returns the screen of the panel of this widget is being opened in. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public ModularScreen getScreen() { - return getPanel().getScreen(); - } - - /** - * Returns the panel of this widget is being opened in. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public @NotNull ModularPanel getPanel() { - if (!isValid()) { - throw new IllegalStateException(this + " is not in a valid state!"); - } - return this.panel; - } - - /** - * Returns the parent of this widget. If this is a {@link ModularPanel} this will always return null contrary to the annotation. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public @NotNull IWidget getParent() { - if (!isValid()) { - throw new IllegalStateException(this + " is not in a valid state!"); - } - return this.parent; - } - - /** - * Returns the gui context of the screen this widget is part of. - * - * @return the screen of this widget - * @throws IllegalStateException if {@link #isValid()} returns false - */ - @Override - public ModularGuiContext getContext() { - if (!isValid()) { - throw new IllegalStateException(this + " is not in a valid state!"); - } - return this.context; - } - - /** - * Used to set the gui context on panels internally. - */ - @ApiStatus.Internal - protected final void setContext(ModularGuiContext context) { - this.context = context; - } - // --------------- // === Syncing === // -------------- @@ -884,30 +648,6 @@ protected void setSyncHandler(@Nullable SyncHandler syncHandler) { // === Other === // ------------- - /** - * Returns if this widget is currently enabled. Disabled widgets (and all its children) are not rendered and can't be interacted with. - * - * @return true if this widget is enabled. - */ - @Override - public boolean isEnabled() { - return this.enabled; - } - - /** - * Sets enabled state. Disabled widgets (and all its children) are not rendered and can't be interacted with. - * - * @param enabled enabled state - */ - @Override - public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - this.enabled = enabled; - if (isValid() && getParent() instanceof INotifyEnabled notifyEnabled) { - notifyEnabled.onChildChangeEnabled(this, enabled); - } - } - } /** * Disables the widget from start. Useful inside widget tree creation, where widget references are usually not stored. @@ -919,48 +659,6 @@ public W disabled() { return getThis(); } - @MustBeInvokedByOverriders - @Override - public void onMouseStartHover() { - this.timeHovered = 0; - } - - @MustBeInvokedByOverriders - @Override - public void onMouseEndHover() { - this.timeHovered = -1; - } - - @MustBeInvokedByOverriders - @Override - public void onMouseEnterArea() { - this.timeBelowMouse = 0; - } - - @MustBeInvokedByOverriders - @Override - public void onMouseLeaveArea() { - this.timeBelowMouse = -1; - } - - @Override - public boolean isHoveringFor(int ticks) { - return timeHovered >= ticks; - } - - @Override - public boolean isBelowMouseFor(int ticks) { - return timeBelowMouse >= ticks; - } - - public int getTicksHovered() { - return timeHovered; - } - - public int getTicksBelowMouse() { - return timeBelowMouse; - } - @Override public Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { if (this instanceof IDragResizeable dragResizeable) { @@ -1001,15 +699,10 @@ public W debugName(String name) { * @return this */ public W name(String name) { - this.name = name; + setName(name); return getThis(); } - @Override - public @Nullable String getName() { - return name; - } - /** * Returns this widget with proper generic type. * @@ -1020,24 +713,4 @@ public W name(String name) { public W getThis() { return (W) this; } - - /** - * This is only used in {@link #toString()}. - * - * @return the simple class name or other fitting name - */ - protected String getTypeName() { - return getClass().getSimpleName(); - } - - /** - * @return the simple class plus the debug name if set - */ - @Override - public String toString() { - if (getName() != null) { - return getTypeName() + "#" + getName(); - } - return getTypeName(); - } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetNode.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetNode.java new file mode 100644 index 000000000..39fa99bea --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetNode.java @@ -0,0 +1,14 @@ +package com.cleanroommc.modularui.widget; + +import com.cleanroommc.modularui.api.widget.IWidget; + +import java.util.List; + +public interface WidgetNode { + + IWidget getWidget(); + + T getParent(); + + List getChildren(); +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index 8be39759c..664f97ef0 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -551,7 +551,7 @@ public static void resize(IWidget parent) { public static void resizeInternal(IWidget parent, boolean onOpen) { long fullTime = System.nanoTime(); // check if updating this widget's pos and size can potentially update its parents - while (!(parent instanceof ModularPanel) && (parent.getParent() instanceof ILayoutWidget || parent.getParent().flex().dependsOnChildren())) { + while (!(parent instanceof ModularPanel) && (parent.getParent() instanceof ILayoutWidget || parent.getParent().resizer().dependsOnChildren())) { parent = parent.getParent(); } long rawTime = System.nanoTime(); 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 a7fa47e82..69c938579 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -3,7 +3,6 @@ import com.cleanroommc.modularui.animation.IAnimatable; import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.layout.IViewportStack; -import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.MathUtils; @@ -496,12 +495,6 @@ public Box getPadding() { return this.padding; } - @Override - public boolean resize(IGuiElement guiElement, boolean isParentLayout) { - guiElement.getArea().set(this); - return true; - } - @Override public Area getArea() { return this; diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java new file mode 100644 index 000000000..ca24c4ac4 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java @@ -0,0 +1,15 @@ +package com.cleanroommc.modularui.widget.sizer; + +public class AreaResizer extends StaticResizer { + + private final Area area; + + public AreaResizer(Area area) { + this.area = area; + } + + @Override + public Area getArea() { + return area; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java index c8d92221c..7381fb30e 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java @@ -4,8 +4,7 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.GuiAxis; -import com.cleanroommc.modularui.api.layout.IResizeable; -import com.cleanroommc.modularui.api.widget.IGuiElement; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.network.NetworkUtils; import org.jetbrains.annotations.ApiStatus; @@ -19,6 +18,7 @@ @ApiStatus.Internal public class DimensionSizer { + private final ResizeNode resizer; private final GuiAxis axis; private final Unit p1 = new Unit(), p2 = new Unit(); @@ -32,7 +32,8 @@ public class DimensionSizer { private boolean marginPaddingApplied = false; private boolean canRelayout = false; - public DimensionSizer(GuiAxis axis) { + public DimensionSizer(ResizeNode resizer, GuiAxis axis) { + this.resizer = resizer; this.axis = axis; } @@ -73,7 +74,7 @@ public void resetSize() { } } - public void setCoverChildren(boolean coverChildren, IGuiElement widget) { + public void setCoverChildren(boolean coverChildren, IWidget widget) { getSize(widget); this.coverChildren = coverChildren; } @@ -132,6 +133,10 @@ public boolean isPosCalculated() { return this.posCalculated; } + public void setSizeCalculated(boolean b) { + this.resizer.setSizeResized(this.axis, b); + } + public boolean canRelayout() { return canRelayout; } @@ -157,7 +162,7 @@ public void setResized(boolean pos, boolean size) { } public boolean isMarginPaddingApplied() { - return marginPaddingApplied; + return this.marginPaddingApplied; } public void setMarginPaddingApplied(boolean marginPaddingApplied) { @@ -168,15 +173,17 @@ private boolean needsSize(Unit unit) { return unit.isRelative() && unit.getAnchor() != 0; } - public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { + public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { // is already calculated - if (this.sizeCalculated && this.posCalculated) return; + boolean sizeCalculated = isSizeCalculated(); + boolean posCalculated = isPosCalculated(); + if (sizeCalculated && posCalculated) return; int p, s; int parentSize = relativeTo.getArea().getSize(this.axis); boolean calcParent = relativeTo.isSizeCalculated(this.axis); Box padding = relativeTo.getArea().getPadding(); - if (this.sizeCalculated && !this.posCalculated) { + if (sizeCalculated) { // pos not calculated // size was calculated before s = area.getSize(this.axis); if (this.start != null) { @@ -186,7 +193,7 @@ public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { } else { throw new IllegalStateException(); } - } else if (!this.sizeCalculated && this.posCalculated) { + } else if (posCalculated) { // size not calculated // pos was calculated before p = area.getRelativePoint(this.axis); if (this.size != null) { @@ -195,7 +202,7 @@ public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { s = defaultSize.getAsInt(); this.sizeCalculated = s > 0; } - } else { + } else { // pos and size not calculated // calc start, end and size if (this.start == null && this.end == null) { p = 0; @@ -213,11 +220,10 @@ public void apply(Area area, IResizeable relativeTo, IntSupplier defaultSize) { if (this.size == null) { if (this.start != null && this.end != null) { p = calcPoint(this.start, padding, -1, parentSize, calcParent); - boolean b = this.posCalculated; this.posCalculated = false; int p2 = calcPoint(this.end, padding, -1, parentSize, calcParent); s = Math.abs(p2 - p); - this.posCalculated &= b; + this.posCalculated &= posCalculated; this.sizeCalculated |= this.posCalculated; } else { s = defaultSize.getAsInt(); @@ -298,7 +304,7 @@ public void coverChildrenForEmpty(Area area, Area relativeTo) { } } - public void applyMarginAndPaddingToPos(IGuiElement parent, Area area, Area relativeTo) { + public void applyMarginAndPaddingToPos(IWidget parent, Area area, Area relativeTo) { // apply self margin and parent padding if not done yet if (isMarginPaddingApplied()) return; setMarginPaddingApplied(true); @@ -343,7 +349,7 @@ private int calcSize(Unit s, Box padding, int parentSize, boolean parentSizeCalc val *= parentSize - padding.getTotal(this.axis); } val += s.getOffset(); - this.sizeCalculated = true; + this.resizer.setSizeResized(this.axis, true); return (int) val; } @@ -361,7 +367,7 @@ public int calcPoint(Unit p, Box padding, int width, int parentSize, boolean par if (p == this.end) { val = parentSize - val; } - this.posCalculated = true; + this.resizer.setPosResized(this.axis, true); return (int) val; } @@ -372,7 +378,7 @@ public int calcPoint(Unit p, Box padding, int width, int parentSize, boolean par * @param newState the new unit type for the found unit * @return a used or unused unit. */ - private Unit getNext(IGuiElement widget, Unit.State newState) { + private Unit getNext(IWidget widget, Unit.State newState) { Unit ret = this.next; Unit other = ret == this.p1 ? this.p2 : this.p1; if (ret.state != Unit.State.UNUSED) { @@ -392,21 +398,21 @@ private Unit getNext(IGuiElement widget, Unit.State newState) { return ret; } - protected Unit getStart(IGuiElement widget) { + protected Unit getStart(IWidget widget) { if (this.start == null) { this.start = getNext(widget, Unit.State.START); } return this.start; } - protected Unit getEnd(IGuiElement widget) { + protected Unit getEnd(IWidget widget) { if (this.end == null) { this.end = getNext(widget, Unit.State.END); } return this.end; } - protected Unit getSize(IGuiElement widget) { + protected Unit getSize(IWidget widget) { if (this.size == null) { this.size = getNext(widget, Unit.State.SIZE); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java index d31517294..d4ffa32de 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java @@ -1,34 +1,18 @@ package com.cleanroommc.modularui.widget.sizer; +import com.cleanroommc.modularui.api.layout.IResizeParent; import com.cleanroommc.modularui.api.layout.IResizeable; -import com.cleanroommc.modularui.api.widget.IGuiElement; /** * A variation of {@link IResizeable} with default implementations which don't do anything */ -public interface IUnResizeable extends IResizeable { +public interface IUnResizeable extends IResizeParent { - IUnResizeable INSTANCE = new IUnResizeable() { - @Override - public boolean resize(IGuiElement guiElement, boolean isParentLayout) { - return true; - } - - @Override - public Area getArea() { - Area.SHARED.set(0, 0, 0, 0); - return Area.SHARED; - } + IUnResizeable INSTANCE = () -> { + Area.SHARED.set(0, 0, 0, 0); + return Area.SHARED; }; - @Override - default void initResizing() {} - - @Override - default boolean postResize(IGuiElement guiElement) { - return true; - } - @Override default boolean isXCalculated() { return true; @@ -64,21 +48,6 @@ default boolean canRelayout(boolean isParentLayout) { return false; } - @Override - default void setChildrenResized(boolean resized) {} - - @Override - default void setLayoutDone(boolean done) {} - - @Override - default void setResized(boolean x, boolean y, boolean w, boolean h) {} - - @Override - default void setXMarginPaddingApplied(boolean b) {} - - @Override - default void setYMarginPaddingApplied(boolean b) {} - @Override default boolean isXMarginPaddingApplied() { return true; diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java new file mode 100644 index 000000000..1b6d342c7 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -0,0 +1,87 @@ +package com.cleanroommc.modularui.widget.sizer; + +import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.api.layout.IResizeable2; + +import java.util.ArrayList; +import java.util.List; + +public abstract class ResizeNode implements IResizeable2 { + + private ResizeNode parent; + private final List children = new ArrayList<>(); + private boolean requiresResize = true; + + public ResizeNode getParent() { + return parent; + } + + public List getChildren() { + return children; + } + + public void setParent(ResizeNode resizeNode) { + if (this.parent != null) { + if (this.parent == resizeNode) return; + this.parent.children.remove(this); + } + this.parent = resizeNode; + if (resizeNode != null) { + resizeNode.children.add(this); + } + } + + public void reset() { + initResizing(); + this.parent = null; + this.children.clear(); + } + + public void markDirty() { + this.requiresResize = true; + } + + public void onResized() { + this.requiresResize = false; + } + + public boolean requiresResize() { + return this.requiresResize; + } + + public boolean dependsOnParentX() { + return false; + } + + public boolean dependsOnParentY() { + return false; + } + + public boolean dependsOnParent() { + return dependsOnParentX() || dependsOnParentY(); + } + + public boolean dependsOnParent(GuiAxis axis) { + return axis.isHorizontal() ? dependsOnParentX() : dependsOnParentY(); + } + + public boolean dependsOnChildrenX() { + return false; + } + + public boolean dependsOnChildrenY() { + return false; + } + + public boolean dependsOnChildren() { + return dependsOnChildrenX() || dependsOnChildrenY(); + } + + public boolean dependsOnChildren(GuiAxis axis) { + return axis.isHorizontal() ? dependsOnChildrenX() : dependsOnChildrenY(); + } + + public boolean isSameResizer(ResizeNode node) { + return node == this; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java new file mode 100644 index 000000000..97534d99d --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -0,0 +1,577 @@ +package com.cleanroommc.modularui.widget.sizer; + +import com.cleanroommc.modularui.GuiError; +import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.api.layout.ILayoutWidget; +import com.cleanroommc.modularui.api.layout.IResizeable2; +import com.cleanroommc.modularui.api.widget.IPositioned; +import com.cleanroommc.modularui.api.widget.IVanillaSlot; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.utils.Alignment; + +import net.minecraft.inventory.Slot; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; +import java.util.function.DoubleSupplier; + +public class StandardResizer extends WidgetResizeNode implements IPositioned { + + private final DimensionSizer x; + private final DimensionSizer y; + private boolean expanded = false; + + private boolean childrenResized = false; + private boolean layoutResized = false; + + public StandardResizer(IWidget widget) { + super(widget); + this.x = createDimensionSizer(GuiAxis.X); + this.y = createDimensionSizer(GuiAxis.Y); + } + + protected DimensionSizer createDimensionSizer(GuiAxis axis) { + return new DimensionSizer(this, axis); + } + + @Override + public void reset() { + this.x.reset(); + this.y.reset(); + } + + public void resetPosition() { + this.x.resetPosition(); + this.y.resetPosition(); + } + + @Override + public boolean isXCalculated() { + return this.x.isPosCalculated(); + } + + @Override + public boolean isYCalculated() { + return this.y.isPosCalculated(); + } + + @Override + public boolean isWidthCalculated() { + return this.x.isSizeCalculated(); + } + + @Override + public boolean isHeightCalculated() { + return this.y.isSizeCalculated(); + } + + @Override + public boolean areChildrenCalculated() { + return this.childrenResized; + } + + @Override + public boolean isLayoutDone() { + return this.layoutResized; + } + + @Override + public boolean canRelayout(boolean isParentLayout) { + return false; + } + + @Override + public boolean isXMarginPaddingApplied() { + return this.x.isMarginPaddingApplied(); + } + + @Override + public boolean isYMarginPaddingApplied() { + return this.y.isMarginPaddingApplied(); + } + + @Override + public StandardResizer flex() { + return this; + } + + @Override + public void scheduleResize() { + markDirty(); + } + + @Override + public void initResizing() { + + } + + @Override + public boolean resize(boolean isParentLayout) { + Area area = getArea(); + ResizeNode relativeTo = getParent(); + //Area relativeArea = relativeTo.getArea(); + //byte panelLayer = getArea().getPanelLayer(); + + /*if (!this.bypassLayerRestriction && (relativeArea.getPanelLayer() > panelLayer || + (relativeArea.getPanelLayer() == panelLayer && relativeArea.z() >= this.parent.getArea().z()))) { + Area area = guiElement.getArea(); + area.setSize(18, 18); + area.rx = 0; + area.ry = 0; + guiElement.resizer().setResized(true); + GuiError.throwNew(this.parent, GuiError.Type.SIZING, "Widget can't be relative to a widget at the same level or above"); + return true; + }*/ + + // calculate x, y, width and height if possible + this.x.apply(area, relativeTo, () -> getWidget().getDefaultWidth()); + this.y.apply(area, relativeTo, () -> getWidget().getDefaultHeight()); + return isFullyCalculated(isParentLayout); + } + + @Override + public boolean postResize() { + boolean coverWidth = this.x.dependsOnChildren(); + boolean coverHeight = this.y.dependsOnChildren(); + if (!coverWidth && !coverHeight) return isSelfFullyCalculated(); + IWidget widget = getWidget(); + if (!widget.hasChildren()) { + coverChildrenForEmpty(); + return isSelfFullyCalculated(); + } + if (getWidget() instanceof ILayoutWidget layout) { + // layout widgets handle widget layout's themselves, so we only need to fit the right and bottom border + coverChildrenForLayout(layout, widget); + return isSelfFullyCalculated(); + } + // non layout widgets can have their children in any position + // we try to wrap all edges as close as possible to all widgets + // this means for each edge there is at least one widget that touches it (plus padding and margin) + + // children are now calculated and now this area can be calculated if it requires childrens area + List children = widget.getChildren(); + int moveChildrenX = 0, moveChildrenY = 0; + + Box padding = getWidget().getArea().getPadding(); + // first calculate the area the children span + int x0 = Integer.MAX_VALUE, x1 = Integer.MIN_VALUE, y0 = Integer.MAX_VALUE, y1 = Integer.MIN_VALUE; + int w = 0, h = 0; + boolean hasIndependentChildX = false; + boolean hasIndependentChildY = false; + for (IWidget child : children) { + Box margin = child.getArea().getMargin(); + ResizeNode resizeable = child.resizer(); + Area area = child.getArea(); + if (coverWidth) { + if (!resizeable.dependsOnParentX()) { + hasIndependentChildX = true; + if (resizeable.isWidthCalculated() && resizeable.isXCalculated()) { + w = Math.max(w, area.requestedWidth() + padding.horizontal()); + x0 = Math.min(x0, area.rx - padding.getLeft() - margin.getLeft()); + x1 = Math.max(x1, area.rx + area.width + padding.right + margin.right); + } else { + return isSelfFullyCalculated(); + } + } + } + if (coverHeight) { + if (!resizeable.dependsOnParentY()) { + hasIndependentChildY = true; + if (resizeable.isHeightCalculated() && resizeable.isYCalculated()) { + h = Math.max(h, area.requestedHeight() + padding.vertical()); + y0 = Math.min(y0, area.ry - padding.getTop() - margin.getTop()); + y1 = Math.max(y1, area.ry + area.height + padding.bottom + margin.bottom); + } else { + return isSelfFullyCalculated(); + } + } + } + } + if ((coverWidth && !hasIndependentChildX) || (coverHeight && !hasIndependentChildY)) { + GuiError.throwNew(getWidget(), GuiError.Type.SIZING, "Can't cover children when all children depend on their parent!"); + return false; + } + if (x1 == Integer.MIN_VALUE) x1 = 0; + if (y1 == Integer.MIN_VALUE) y1 = 0; + if (x0 == Integer.MAX_VALUE) x0 = 0; + if (y0 == Integer.MAX_VALUE) y0 = 0; + if (w > x1 - x0) x1 = x0 + w; // we found at least one widget which was wider than what was calculated by start and end pos + if (h > y1 - y0) y1 = y0 + h; + + // now calculate new x, y, width and height based on the children area + Area relativeTo = getParent().getArea(); + if (coverWidth) { + // apply the size to this widget + // the return value is the amount of pixels we need to move the children + moveChildrenX = this.x.postApply(getWidget().getArea(), relativeTo, x0, x1); + } + if (coverHeight) { + moveChildrenY = this.y.postApply(getWidget().getArea(), relativeTo, y0, y1); + } + // since the edges might have been moved closer to the widgets, the widgets should move back into it's original (absolute) position + if (moveChildrenX != 0 || moveChildrenY != 0) { + for (IWidget child : children) { + Area area = child.getArea(); + ResizeNode resizeable = child.resizer(); + if (resizeable.isXCalculated()) area.rx += moveChildrenX; + if (resizeable.isYCalculated()) area.ry += moveChildrenY; + } + } + return isSelfFullyCalculated(); + } + + private void coverChildrenForLayout(ILayoutWidget layout, IWidget widget) { + List children = widget.getChildren(); + Box padding = getWidget().getArea().getPadding(); + // first calculate the area the children span + int x1 = Integer.MIN_VALUE, y1 = Integer.MIN_VALUE; + int w = 0, h = 0; + int withDefaultW = 0, withDefaultH = 0; + boolean coverWidth = this.x.dependsOnChildren(); + boolean coverHeight = this.y.dependsOnChildren(); + boolean hasIndependentChildX = false; + boolean hasIndependentChildY = false; + boolean coverByDefaultSizeX = coverWidth && layout.canCoverByDefaultSize(GuiAxis.X); + boolean coverByDefaultSizeY = coverHeight && layout.canCoverByDefaultSize(GuiAxis.Y); + for (IWidget child : children) { + if (layout.shouldIgnoreChildSize(child)) continue; + Area area = child.getArea(); + Box margin = area.getMargin(); + IResizeable2 resizeable = child.resizer(); + if (coverWidth) { + if (!child.resizer().dependsOnParentX()) { + hasIndependentChildX = true; + if (resizeable.isWidthCalculated() && resizeable.isXCalculated()) { + int s = area.requestedWidth() + padding.horizontal(); + w = Math.max(w, s); + withDefaultW = Math.max(withDefaultW, s); + x1 = Math.max(x1, area.rx + area.width + padding.right + margin.right); + } else { + return; + } + } else if (coverByDefaultSizeX) { + withDefaultW = Math.max(withDefaultW, child.getDefaultWidth() + margin.horizontal() + padding.horizontal()); + } + } + + if (coverHeight) { + if (!child.resizer().dependsOnParentY()) { + hasIndependentChildY = true; + if (resizeable.isHeightCalculated() && resizeable.isYCalculated()) { + int s = area.requestedHeight() + padding.vertical(); + h = Math.max(h, s); + withDefaultH = Math.max(withDefaultH, s); + y1 = Math.max(y1, area.ry + area.height + padding.bottom + margin.bottom); + } else { + return; + } + } else if (coverByDefaultSizeY) { + withDefaultH = Math.max(withDefaultH, child.getDefaultHeight() + margin.vertical() + padding.vertical()); + } + } + } + if ((coverWidth && !hasIndependentChildX && !coverByDefaultSizeX) || + (coverHeight && !hasIndependentChildY && !coverByDefaultSizeY)) { + GuiError.throwNew(getWidget(), GuiError.Type.SIZING, "Can't cover children when all children depend on their parent!"); + return; + } + if (w == 0) w = withDefaultW; // only use default sizes, if no size is defined + if (h == 0) h = withDefaultH; + if (x1 == Integer.MIN_VALUE) x1 = 0; + if (y1 == Integer.MIN_VALUE) y1 = 0; + if (w > x1) x1 = w; + if (h > y1) y1 = h; + + Area relativeTo = getParent().getArea(); + if (coverWidth) this.x.postApply(getArea(), relativeTo, 0, x1); + if (coverHeight) this.y.postApply(getArea(), relativeTo, 0, y1); + } + + private void coverChildrenForEmpty() { + if (this.x.dependsOnChildren()) { + this.x.coverChildrenForEmpty(getWidget().getArea(), getParent().getArea()); + } + if (this.y.dependsOnChildren()) { + this.y.coverChildrenForEmpty(getWidget().getArea(), getParent().getArea()); + } + } + + @Override + public void applyPos() { + IWidget widget = getWidget(); + Area relativeTo = getParent().getArea(); + Area area = widget.getArea(); + // apply margin and padding if not done yet + this.x.applyMarginAndPaddingToPos(widget, area, relativeTo); + this.y.applyMarginAndPaddingToPos(widget, area, relativeTo); + // after all widgets x, y, width and height have been calculated we can now calculate the absolute position + area.applyPos(relativeTo.x, relativeTo.y); + Area parentArea = widget.getParentArea(); + area.rx = area.x - parentArea.x; + area.ry = area.y - parentArea.y; + if (widget instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot()) { + // special treatment for minecraft slots + Slot slot = vanillaSlot.getVanillaSlot(); + Area mainArea = widget.getScreen().getMainPanel().getArea(); + // in vanilla uis the position is relative to the gui area and size is 16 x 16 + // since our slots are 18 x 18 we need to offset by 1 + slot.xPos = widget.getArea().x - mainArea.x + 1; + slot.yPos = widget.getArea().y - mainArea.y + 1; + } + } + + @Override + public void setChildrenResized(boolean resized) { + this.childrenResized = resized; + } + + @Override + public void setLayoutDone(boolean done) { + this.layoutResized = done; + } + + @Override + public void setResized(boolean x, boolean y, boolean w, boolean h) { + this.x.setResized(x, w); + this.y.setResized(y, h); + } + + @Override + public void setXMarginPaddingApplied(boolean b) { + this.x.setMarginPaddingApplied(b); + } + + @Override + public void setYMarginPaddingApplied(boolean b) { + this.y.setMarginPaddingApplied(b); + } + + public boolean hasYPos() { + return this.y.hasPos(); + } + + public boolean hasXPos() { + return this.x.hasPos(); + } + + public boolean hasHeight() { + return this.y.hasSize(); + } + + public boolean hasWidth() { + return this.x.hasSize(); + } + + public boolean hasStartPos(GuiAxis axis) { + return axis.isHorizontal() ? this.x.hasStart() : this.y.hasStart(); + } + + public boolean hasEndPos(GuiAxis axis) { + return axis.isHorizontal() ? this.x.hasEnd() : this.y.hasEnd(); + } + + public boolean hasPos(GuiAxis axis) { + return axis.isHorizontal() ? hasXPos() : hasYPos(); + } + + public boolean hasSize(GuiAxis axis) { + return axis.isHorizontal() ? hasWidth() : hasHeight(); + } + + @Override + public boolean dependsOnParentX() { + return this.x.dependsOnParent(); + } + + @Override + public boolean dependsOnParentY() { + return this.x.dependsOnParent(); + } + + @Override + public boolean dependsOnChildrenX() { + return this.x.dependsOnChildren(); + } + + @Override + public boolean dependsOnChildrenY() { + return this.x.dependsOnChildren(); + } + + public StandardResizer expanded() { + this.expanded = true; + scheduleResize(); + return this; + } + + public boolean isExpanded() { + return this.expanded; + } + + @ApiStatus.Internal + public StandardResizer left(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getLeft(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer left(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getLeft(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer right(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getRight(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer right(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getRight(), x, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer top(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getTop(), y, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer top(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getTop(), y, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer bottom(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getBottom(), y, offset, anchor, measure, autoAnchor); + } + + @ApiStatus.Internal + public StandardResizer bottom(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + return unit(getBottom(), y, offset, anchor, measure, autoAnchor); + } + + private StandardResizer unit(Unit u, float val, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + u.setAnchor(anchor); + u.setAutoAnchor(autoAnchor); + scheduleResize(); + return this; + } + + private StandardResizer unit(Unit u, DoubleSupplier val, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + u.setAnchor(anchor); + u.setAutoAnchor(autoAnchor); + scheduleResize(); + return this; + } + + @ApiStatus.Internal + public StandardResizer width(float val, int offset, Unit.Measure measure) { + return unitSize(getWidth(), val, offset, measure); + } + + @ApiStatus.Internal + public StandardResizer width(DoubleSupplier val, int offset, Unit.Measure measure) { + return unitSize(getWidth(), val, offset, measure); + } + + @ApiStatus.Internal + public StandardResizer height(float val, int offset, Unit.Measure measure) { + return unitSize(getHeight(), val, offset, measure); + } + + @ApiStatus.Internal + public StandardResizer height(DoubleSupplier val, int offset, Unit.Measure measure) { + return unitSize(getHeight(), val, offset, measure); + } + + private StandardResizer unitSize(Unit u, float val, int offset, Unit.Measure measure) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + scheduleResize(); + return this; + } + + private StandardResizer unitSize(Unit u, DoubleSupplier val, int offset, Unit.Measure measure) { + u.setValue(val); + u.setMeasure(measure); + u.setOffset(offset); + scheduleResize(); + return this; + } + + public StandardResizer anchorLeft(float val) { + getLeft().setAnchor(val); + getLeft().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchorRight(float val) { + getRight().setAnchor(1 - val); + getRight().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchorTop(float val) { + getTop().setAnchor(val); + getTop().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchorBottom(float val) { + getBottom().setAnchor(1 - val); + getBottom().setAutoAnchor(false); + scheduleResize(); + return this; + } + + public StandardResizer anchor(Alignment alignment) { + if (this.x.hasStart() || !this.x.hasEnd()) { + anchorLeft(alignment.x); + } else if (this.x.hasEnd()) { + anchorRight(alignment.x); + } + if (this.y.hasStart() || !this.y.hasEnd()) { + anchorTop(alignment.y); + } else if (this.y.hasEnd()) { + anchorBottom(alignment.y); + } + return this; + } + + public void setUnit(Unit unit, GuiAxis axis, Unit.State pos) { + (axis.isHorizontal() ? this.x : this.y).setUnit(unit, pos); + } + + private Unit getLeft() { + return this.x.getStart(getWidget()); + } + + private Unit getRight() { + return this.x.getEnd(getWidget()); + } + + private Unit getTop() { + return this.y.getStart(getWidget()); + } + + private Unit getBottom() { + return this.y.getEnd(getWidget()); + } + + private Unit getWidth() { + return this.x.getSize(getWidget()); + } + + private Unit getHeight() { + return this.y.getSize(getWidget()); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java new file mode 100644 index 000000000..681d4b937 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java @@ -0,0 +1,88 @@ +package com.cleanroommc.modularui.widget.sizer; + +public abstract class StaticResizer extends ResizeNode { + + private boolean childrenCalculated = false; + + public StaticResizer() { + setResized(true); + setMarginPaddingApplied(true); + setChildrenResized(true); + setLayoutDone(true); + } + + @Override + public void initResizing() {} + + @Override + public boolean isXCalculated() { + return true; + } + + @Override + public boolean isYCalculated() { + return true; + } + + @Override + public boolean isWidthCalculated() { + return true; + } + + @Override + public boolean isHeightCalculated() { + return true; + } + + @Override + public boolean areChildrenCalculated() { + return this.childrenCalculated; + } + + @Override + public boolean isLayoutDone() { + return true; + } + + @Override + public boolean canRelayout(boolean isParentLayout) { + return false; + } + + @Override + public boolean isXMarginPaddingApplied() { + return true; + } + + @Override + public boolean isYMarginPaddingApplied() { + return true; + } + + @Override + public boolean resize(boolean isParentLayout) { + return true; + } + + @Override + public boolean postResize() { + return true; + } + + @Override + public void setChildrenResized(boolean resized) { + this.childrenCalculated = resized; + } + + @Override + public void setLayoutDone(boolean done) {} + + @Override + public void setResized(boolean x, boolean y, boolean w, boolean h) {} + + @Override + public void setXMarginPaddingApplied(boolean b) {} + + @Override + public void setYMarginPaddingApplied(boolean b) {} +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java new file mode 100644 index 000000000..2370f2661 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java @@ -0,0 +1,23 @@ +package com.cleanroommc.modularui.widget.sizer; + +import com.cleanroommc.modularui.api.widget.IWidget; + +import java.util.Objects; + +public abstract class WidgetResizeNode extends ResizeNode { + + private final IWidget widget; + + protected WidgetResizeNode(IWidget widget) { + this.widget = Objects.requireNonNull(widget); + } + + public IWidget getWidget() { + return widget; + } + + @Override + public Area getArea() { + return widget.getArea(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java index ff077cc77..cee7831fc 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java @@ -2,7 +2,6 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.animation.Animator; -import com.cleanroommc.modularui.api.widget.IGuiElement; import com.cleanroommc.modularui.api.widget.IValueWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.drawable.GuiTextures; @@ -157,7 +156,7 @@ public static class Item extends DraggableWidget> implements IValueWi private final T value; private List children; - private Predicate dropPredicate; + private Predicate dropPredicate; private SortableListWidget listWidget; private int index = -1; private int movingFrom = -1; @@ -183,7 +182,7 @@ public List getChildren() { } @Override - public boolean canDropHere(int x, int y, @Nullable IGuiElement widget) { + public boolean canDropHere(int x, int y, @Nullable IWidget widget) { return this.dropPredicate == null || this.dropPredicate.test(widget); } @@ -225,7 +224,7 @@ public Item child(Function, IWidget> widgetCreator) { return child(widgetCreator.apply(this)); } - public Item dropPredicate(Predicate dropPredicate) { + public Item dropPredicate(Predicate dropPredicate) { this.dropPredicate = dropPredicate; return this; }