From 305cf6e6356194f7dc5d17d0d51ff4782871b59d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 1 Nov 2025 11:49:58 +0100 Subject: [PATCH 01/20] 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 02/20] 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; } From 6b1343e972d6865f32a58f2608e6d65480897a5c Mon Sep 17 00:00:00 2001 From: brachy84 Date: Tue, 27 Jan 2026 10:52:38 +0100 Subject: [PATCH 03/20] compiles --- .../modularui/api/layout/IResizeable2.java | 2 +- .../modularui/api/widget/IDragResizeable.java | 15 +-- .../modularui/api/widget/IPositioned.java | 27 +++-- .../modularui/api/widget/IWidget.java | 29 +++-- .../screen/DraggablePanelWrapper.java | 9 +- .../modularui/screen/ModularPanel.java | 3 +- .../modularui/screen/ModularScreen.java | 11 +- .../modularui/screen/PanelManager.java | 2 +- .../screen/viewport/ModularGuiContext.java | 2 +- .../modularui/widget/AbstractWidget.java | 59 +++------- .../widget/DelegatingSingleChildWidget.java | 14 +-- .../modularui/widget/EmptyWidget.java | 15 +-- .../modularui/widget/InternalWidgetTree.java | 48 ++++----- .../cleanroommc/modularui/widget/Widget.java | 29 ++++- .../modularui/widget/WidgetTree.java | 56 +++++----- .../modularui/widget/sizer/Area.java | 2 +- .../modularui/widget/sizer/AreaResizer.java | 5 + .../modularui/widget/sizer/Flex.java | 25 +---- .../modularui/widget/sizer/ResizeNode.java | 101 +++++++++++++++--- .../widget/sizer/ScreenResizeNode.java | 26 +++++ .../widget/sizer/StandardResizer.java | 82 ++++++++++++-- .../modularui/widget/sizer/StaticResizer.java | 44 +++++++- .../widget/sizer/WidgetResizeNode.java | 39 +++++++ .../modularui/widgets/CategoryList.java | 2 +- .../modularui/widgets/ListWidget.java | 4 +- .../modularui/widgets/PopupMenu.java | 71 ------------ .../modularui/widgets/SlotGroupWidget.java | 2 +- .../modularui/widgets/TextWidget.java | 2 +- .../modularui/widgets/layout/Flow.java | 20 ++-- .../widgets/menu/ContextMenuButton.java | 7 +- .../widgets/menu/ContextMenuList.java | 8 +- .../widgets/menu/ContextMenuOption.java | 8 +- 32 files changed, 467 insertions(+), 302 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/PopupMenu.java diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java index c632c4081..0929cdb08 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java @@ -11,7 +11,7 @@ public interface IResizeable2 extends IResizeParent { /** * Called once before resizing */ - void initResizing(); + void initResizing(boolean onOpen); /** * Resizes the given element diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java b/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java index 4ac2ee934..ceb7f68e5 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java @@ -84,18 +84,19 @@ static ResizeDragArea getDragResizeCorner(IDragResizeable widget, Area area, IVi */ static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea dragArea, Area startArea, int dx, int dy) { int keepPosFactor = resizeable.keepPosOnDragResize() || GuiScreen.isShiftKeyDown() ? 2 : 1; - if (dx != 0) { + // TODO + /*if (dx != 0) { if (dragArea.left) { int s = startArea.width - dx * keepPosFactor; if (s >= resizeable.getMinDragWidth()) { - widget.flex().left(startArea.rx + dx); - widget.flex().width(s); + widget.left(startArea.rx + dx); + widget.resizer().width(s); } } else if (dragArea.right) { int s = startArea.width + dx * keepPosFactor; if (s >= resizeable.getMinDragWidth()) { widget.flex().left(startArea.rx - dx * (keepPosFactor - 1)); - widget.flex().width(s); + widget.resizer().width(s); } } } @@ -104,16 +105,16 @@ static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea int s = startArea.height - dy * keepPosFactor; if (s >= resizeable.getMinDragHeight()) { widget.flex().top(startArea.ry + dy); - widget.flex().height(s); + widget.resizer().height(s); } } else if (dragArea.bottom) { int s = startArea.height + dy * keepPosFactor; if (s >= resizeable.getMinDragHeight()) { widget.flex().top(startArea.ry - dy * (keepPosFactor - 1)); - widget.flex().height(s); + widget.resizer().height(s); } } - } + }*/ resizeable.onDragResize(); } 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 bed0136b0..6b12d5073 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java @@ -2,10 +2,13 @@ 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.AreaResizer; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; import com.cleanroommc.modularui.widget.sizer.StandardResizer; import com.cleanroommc.modularui.widget.sizer.Unit; +import org.jetbrains.annotations.ApiStatus; + import java.util.function.Consumer; import java.util.function.DoubleSupplier; @@ -49,15 +52,26 @@ default W expanded() { return getThis(); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") default W relative(IGuiElement guiElement) { return relative(guiElement.getArea()); } - default W relative(Area guiElement) { - flex().relative(guiElement); + @Deprecated + default W relative(Area area) { + return relative(new AreaResizer(area)); + } + + default W relative(ResizeNode resizeNode) { + flex().relative(resizeNode); return getThis(); } + default W relative(IWidget widget) { + return relative(widget.resizer()); + } + default W relativeToScreen() { flex().relativeToScreen(); return getThis(); @@ -68,11 +82,6 @@ default W relativeToParent() { return getThis(); } - default W bypassLayerRestriction() { - flex().bypassLayerRestriction(); - return getThis(); - } - default W left(int val) { flex().left(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); @@ -434,7 +443,7 @@ default W center() { return align(Alignment.Center); } - default W flex(Consumer flexConsumer) { + default W flex(Consumer flexConsumer) { flexConsumer.accept(flex()); return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java index db93d1a23..0d157bde9 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -7,9 +7,7 @@ import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; 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 com.cleanroommc.modularui.widget.sizer.StandardResizer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -252,7 +250,10 @@ default boolean hasParent() { /** * @return flex of this widget. Creates a new one if it doesn't already have one. */ - //Flex flex(); + @NotNull + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + StandardResizer flex(); /** * Does the same as {@link IPositioned#flex(Consumer)} @@ -260,17 +261,19 @@ default boolean hasParent() { * @param builder function to build flex * @return this */ - /*default IWidget flexBuilder(Consumer builder) { + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + default IWidget flexBuilder(Consumer builder) { builder.accept(flex()); return this; - }*/ + } /** * @return resizer of this widget */ @NotNull @Override - ResizeNode resizer(); + StandardResizer resizer(); /** * Called before a widget is resized. @@ -290,11 +293,15 @@ default void postResize() {} /** * @return flex of this widget */ - //Flex getFlex(); - /*default boolean isExpanded() { - Flex flex = getFlex(); + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + @Nullable + StandardResizer getFlex(); + + default boolean isExpanded() { + StandardResizer flex = getFlex(); return flex != null && flex.isExpanded(); - }*/ + } @Nullable String getName(); diff --git a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java index 329f3c54e..53fb17817 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java +++ b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java @@ -48,10 +48,11 @@ public void onDragEnd(boolean successful) { float x = this.panel.getContext().getAbsMouseX() - this.relativeClickX; y = y / (this.panel.getScreen().getScreenArea().height - this.panel.getArea().height); x = x / (this.panel.getScreen().getScreenArea().width - this.panel.getArea().width); - this.panel.flex().resetPosition(); - this.panel.flex().relativeToScreen(); - this.panel.flex().topRelAnchor(y, y) - .leftRelAnchor(x, x); + // TODO + //this.panel.flex().resetPosition(); + //this.panel.flex().relativeToScreen(); + //this.panel.flex().topRelAnchor(y, y) + // .leftRelAnchor(x, x); this.panel.scheduleResize(); } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index b6e48ff47..33cd28369 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -234,6 +234,7 @@ public boolean canHover() { public void onOpen(ModularScreen screen) { this.screen = screen; getArea().z(1); + resizer().setDefaultParent(this.screen.getResizeNode()); initialise(this, false); // call first tick after everything is initialised WidgetTree.onUpdate(this); @@ -787,7 +788,7 @@ void closeClientSubPanels() { @Override public boolean isExcludeAreaInRecipeViewer() { - return super.isExcludeAreaInRecipeViewer() || (!getScreen().isOverlay() && !this.invisible && !flex().isFullSize()); + return super.isExcludeAreaInRecipeViewer() || (!getScreen().isOverlay() && !this.invisible && !resizer().isFullSize()); } public ModularPanel bindPlayerInventory() { diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index c0ae0dbc1..fb674f88d 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -15,6 +15,8 @@ import com.cleanroommc.modularui.value.sync.ModularSyncManager; import com.cleanroommc.modularui.widget.WidgetTree; import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; +import com.cleanroommc.modularui.widget.sizer.ScreenResizeNode; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; @@ -77,6 +79,7 @@ public static ModularScreen getCurrent() { private final ModularGuiContext context = new ModularGuiContext(this); private final Map, List> guiActionListeners = new Object2ObjectOpenHashMap<>(); private final Object2ObjectArrayMap frameUpdates = new Object2ObjectArrayMap<>(); + private final ScreenResizeNode resizeNode = new ScreenResizeNode(this); private boolean pausesGame = false; private boolean openParentOnClose = false; @@ -179,9 +182,7 @@ public void onResize(int width, int height) { } this.context.pushViewport(null, this.context.getScreenArea()); - for (ModularPanel panel : this.panelManager.getReverseOpenPanels()) { - WidgetTree.resizeInternal(panel, true); - } + WidgetTree.resizeInternal(this.resizeNode, true); this.context.popViewport(null); if (!isOverlay()) { @@ -599,6 +600,10 @@ public IMuiScreen getScreenWrapper() { return this.screenWrapper; } + public ResizeNode getResizeNode() { + return resizeNode; + } + public Area getScreenArea() { return this.context.getScreenArea(); } diff --git a/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java b/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java index 21390b66d..39b23055d 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java +++ b/src/main/java/com/cleanroommc/modularui/screen/PanelManager.java @@ -99,7 +99,7 @@ private void openPanel(ModularPanel panel, boolean resize) { this.dirty = true; panel.onOpen(this.screen); if (resize) { - WidgetTree.resizeInternal(panel, true); + WidgetTree.resizeInternal(panel.resizer(), true); } } 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 22b35ef7d..86a910083 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java +++ b/src/main/java/com/cleanroommc/modularui/screen/viewport/ModularGuiContext.java @@ -331,7 +331,7 @@ public boolean onHoveredClick(int button, LocatedWidget hovered) { draggable = new LocatedElement<>(iDraggable, hovered.getTransformationMatrix()); } else if (widget instanceof ModularPanel panel) { if (panel.isDraggable()) { - if (!panel.flex().hasFixedSize()) { + if (!panel.resizer().hasFixedSize()) { throw new IllegalStateException("Panel must have a fixed size. It can't specify left AND right or top AND bottom!"); } draggable = new LocatedElement<>(new DraggablePanelWrapper(panel), TransformationMatrix.EMPTY); diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java index bc87530d8..08da0a5b3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -6,10 +6,7 @@ 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; @@ -30,10 +27,9 @@ public abstract class AbstractWidget implements IWidget { 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; + private StandardResizer resizer; /** * Returns the screen of the panel of this widget is being opened in. @@ -56,12 +52,6 @@ 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. * @@ -77,24 +67,11 @@ public final void initialise(@NotNull IWidget parent, boolean late) { 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!"); + resizer().setDefaultParent(parent.resizer()); } this.valid = true; - if (!getScreen().isClientOnly()) { - initialiseSyncHandler(getScreen().getSyncManager(), late); - } - if (isExcludeAreaInRecipeViewer()) { - getContext().getRecipeViewerSettings().addExclusionArea(this); - }*/ + onInitInternal(late); onInit(); if (hasChildren()) { for (IWidget child : getChildren()) { @@ -102,9 +79,11 @@ public final void initialise(@NotNull IWidget parent, boolean late) { } } afterInit(); - this.resizer.onResized(); + onResized(); } + void onInitInternal(boolean late) {} + /** * Called after this widget is initialised and before the children are initialised. */ @@ -124,16 +103,6 @@ public void afterInit() {} @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(); @@ -144,6 +113,7 @@ public void dispose() { this.parent = null; this.context = null; } + resizer().dispose(); this.timeHovered = -1; this.timeBelowMouse = -1; this.valid = false; @@ -299,14 +269,11 @@ protected final void setContext(ModularGuiContext context) { } @Override - public @NotNull ResizeNode resizer() { - if (this.resizer == null) { - this.resizer = new StandardResizer(this); - } + public @NotNull StandardResizer resizer() { return this.resizer; } - public void resizer(WidgetResizeNode resizer) { + protected void resizer(StandardResizer resizer) { this.resizer = Objects.requireNonNull(resizer); } @@ -317,9 +284,15 @@ public void resizer(WidgetResizeNode resizer) { * * @return flex of this widget */ + @Nullable @Override public StandardResizer getFlex() { - return null; + return resizer; + } + + @Override + public @NotNull StandardResizer flex() { + return resizer(); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java index 6948c79dc..c877d9fe0 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -3,8 +3,7 @@ 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 com.cleanroommc.modularui.widget.sizer.StandardResizer; import org.jetbrains.annotations.NotNull; @@ -17,12 +16,7 @@ public class DelegatingSingleChildWidget> extends @Override public void onInit() { super.onInit(); - if (hasChildren()) getChild().flex().relative(getParent()); - } - - @Override - protected void onChildAdd(IWidget child) { - super.onChildAdd(child); + if (hasChildren()) getChild().resizer().relative(getParent()); } @Override @@ -57,12 +51,12 @@ public void postResize() { } @Override - public Flex getFlex() { + public StandardResizer getFlex() { return getDelegate() != null ? getDelegate().getFlex() : super.getFlex(); } @Override - public @NotNull ResizeNode resizer() { + public @NotNull StandardResizer resizer() { return getDelegate() != null ? getDelegate().resizer() : super.resizer(); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java index b30a3034b..aaa5d8e75 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java @@ -1,6 +1,5 @@ package com.cleanroommc.modularui.widget; -import com.cleanroommc.modularui.api.layout.IResizeable; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; @@ -8,8 +7,7 @@ import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; 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 com.cleanroommc.modularui.widget.sizer.StandardResizer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,7 +15,7 @@ public class EmptyWidget implements IWidget { private final Area area = new Area(); - private final Flex flex = new Flex(this); + private final StandardResizer flex = new StandardResizer(this); private boolean requiresResize = false; private boolean enabled = true; private IWidget parent; @@ -122,20 +120,17 @@ public ModularGuiContext getContext() { } @Override - public Flex flex() { + public StandardResizer flex() { return this.flex; } @Override - public @NotNull ResizeNode resizer() { + public @NotNull StandardResizer resizer() { return this.flex; } @Override - public void resizer(IResizeable resizer) {} - - @Override - public Flex getFlex() { + public StandardResizer getFlex() { 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 4855a3bcb..d192fceeb 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java @@ -1,7 +1,6 @@ package com.cleanroommc.modularui.widget; import com.cleanroommc.modularui.api.GuiAxis; -import com.cleanroommc.modularui.api.layout.ILayoutWidget; import com.cleanroommc.modularui.api.layout.IViewport; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; @@ -55,7 +54,7 @@ static T findChildAt(IWidget parent, Class type, String[] static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEnabled, boolean drawBackground) { if (!parent.isEnabled() && !ignoreEnabled) return; if (parent.requiresResize()) { - WidgetTree.resizeInternal(parent, false); + WidgetTree.resizeInternal(parent.resizer(), false); } float alpha = parent.getPanel().getAlpha(); @@ -189,15 +188,12 @@ static void drawTreeForeground(IWidget parent, ModularGuiContext context) { context.popMatrix(); } - static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolean isParentLayout) { + static boolean resize(ResizeNode resizer, boolean init, boolean onOpen, boolean isParentLayout) { boolean alreadyCalculated = false; // first try to resize this widget - ResizeNode resizer = widget.resizer(); - ILayoutWidget layout = widget instanceof ILayoutWidget layoutWidget ? layoutWidget : null; - boolean isLayout = layout != null; + boolean isLayout = resizer.isLayout(); if (init) { - widget.beforeResize(onOpen); - resizer.initResizing(); + resizer.initResizing(onOpen); if (!isLayout) resizer.setLayoutDone(true); } else { // if this is not the first time check if this widget is already resized @@ -205,14 +201,14 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea } boolean selfFullyCalculated = resizer.isSelfFullyCalculated() || resizer.resize(isParentLayout); - GuiAxis expandAxis = widget instanceof IExpander expander ? expander.getExpandAxis() : null; + GuiAxis expandAxis = resizer instanceof IExpander expander ? expander.getExpandAxis() : null; // now resize all children and collect children which could not be fully calculated - List anotherResize = Collections.emptyList(); - if (!resizer.areChildrenCalculated() && widget.hasChildren()) { + List anotherResize = Collections.emptyList(); + if (!resizer.areChildrenCalculated() && !resizer.getChildren().isEmpty()) { anotherResize = new ArrayList<>(); - for (IWidget child : widget.getChildren()) { - if (init) child.resizer().checkExpanded(expandAxis); - if (!resizeWidget(child, init, onOpen, isLayout)) { + for (ResizeNode child : resizer.getChildren()) { + if (init) child.checkExpanded(expandAxis); + if (!resize(child, init, onOpen, isLayout)) { anotherResize.add(child); } } @@ -225,15 +221,15 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea // we need to keep track of which widgets are not yet fully calculated, so we can call onResized on those which later are // fully calculated BitSet state = getCalculatedState(anotherResize, isLayout); - if (layout != null && shouldLayout) { - layoutSuccessful = layout.layoutWidgets(); + if (isLayout && shouldLayout) { + layoutSuccessful = resizer.layoutChildren(); } // post resize this widget if possible resizer.postResize(); - if (layout != null && shouldLayout) { - layoutSuccessful &= layout.postLayoutWidgets(); + if (isLayout && shouldLayout) { + layoutSuccessful &= resizer.postLayoutChildren(); } if (shouldLayout) resizer.setLayoutDone(layoutSuccessful); checkFullyCalculated(anotherResize, state, isLayout); @@ -242,7 +238,7 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea // now fully resize all children which needs it if (!anotherResize.isEmpty()) { for (int i = 0; i < anotherResize.size(); i++) { - if (resizeWidget(anotherResize.get(i), false, onOpen, isLayout)) { + if (resize(anotherResize.get(i), false, onOpen, isLayout)) { anotherResize.remove(i--); } } @@ -250,29 +246,29 @@ static boolean resizeWidget(IWidget widget, boolean init, boolean onOpen, boolea resizer.setChildrenResized(anotherResize.isEmpty()); selfFullyCalculated = resizer.isFullyCalculated(isParentLayout); - if (selfFullyCalculated && !alreadyCalculated) widget.onResized(); + if (selfFullyCalculated && !alreadyCalculated) resizer.onResized(); return selfFullyCalculated; } - private static BitSet getCalculatedState(List children, boolean isLayout) { + private static BitSet getCalculatedState(List children, boolean isLayout) { if (children.isEmpty()) return null; BitSet state = new BitSet(); for (int i = 0; i < children.size(); i++) { - IWidget widget = children.get(i); - if (widget.resizer().isFullyCalculated(isLayout)) { + ResizeNode widget = children.get(i); + if (widget.isFullyCalculated(isLayout)) { state.set(i); } } return state; } - private static void checkFullyCalculated(List children, BitSet state, boolean isLayout) { + private static void checkFullyCalculated(List children, BitSet state, boolean isLayout) { if (children.isEmpty() || state == null) return; int j = 0; for (int i = 0; i < children.size(); i++) { - IWidget widget = children.get(i); - if (!state.get(j) && widget.resizer().isFullyCalculated(isLayout)) { + ResizeNode widget = children.get(i); + if (!state.get(j) && widget.isFullyCalculated(isLayout)) { widget.onResized(); state.set(j); children.remove(i--); diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 129ba1837..fa4a0ed00 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -22,6 +22,7 @@ import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import com.cleanroommc.modularui.widget.sizer.Bounds; +import com.cleanroommc.modularui.widget.sizer.StandardResizer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.MustBeInvokedByOverriders; @@ -66,10 +67,33 @@ public class Widget> extends AbstractWidget implements IPosi @Nullable private List guiActionListeners; // TODO replace with proper event system @Nullable private Consumer onUpdateListener; + public Widget() { + resizer(new StandardResizer(this)); + } + // ----------------- // === Lifecycle === // ----------------- + @Override + void onInitInternal(boolean late) { + if (this.guiActionListeners != null) { + for (IGuiAction action : this.guiActionListeners) { + getContext().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!"); + } + if (!getScreen().isClientOnly()) { + initialiseSyncHandler(getScreen().getSyncManager(), late); + } + if (isExcludeAreaInRecipeViewer()) { + getContext().getRecipeViewerSettings().addExclusionArea(this); + } + } + /** * Retrieves, verifies, and initialises a linked sync handler. * Custom logic should be handled in {@link #setSyncOrValue(ISyncOrValue)}. @@ -106,11 +130,6 @@ public void dispose() { getContext().getRecipeViewerSettings().removeExclusionArea(this); } } - if (hasChildren()) { - for (IWidget child : getChildren()) { - child.dispose(); - } - } super.dispose(); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index 16eb94028..e6cdc8fcd 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -12,6 +12,7 @@ import com.cleanroommc.modularui.utils.ObjectList; import com.cleanroommc.modularui.value.sync.ModularSyncManager; import com.cleanroommc.modularui.value.sync.PanelSyncManager; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; import net.minecraft.util.text.TextComponentString; @@ -453,13 +454,6 @@ public static T findFirst(IWidget parent, Class type, @Nu return InternalWidgetTree.findChildAt(parent, type, path, 0, false); } - public static void applyPos(IWidget parent) { - WidgetTree.foreachChildBFS(parent, child -> { - child.resizer().applyPos(child); - return true; - }, true); - } - public static IWidget findParent(IWidget parent, Predicate filter) { if (parent == null) return null; while (!(parent instanceof ModularPanel)) { @@ -548,35 +542,45 @@ public static void resize(IWidget parent) { } @ApiStatus.Internal - public static void resizeInternal(IWidget parent, boolean onOpen) { - long fullTime = System.nanoTime(); + public static void resizeInternal(ResizeNode parent, boolean onOpen) { + long time = 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().resizer().dependsOnChildren())) { + while (parent.getParent() != null && (parent.getParent().dependsOnChildren() || parent.isLayout())) { parent = parent.getParent(); } - long rawTime = System.nanoTime(); // resize each widget and calculate their relative pos - if (!InternalWidgetTree.resizeWidget(parent, true, onOpen, false) && !InternalWidgetTree.resizeWidget(parent, false, onOpen, false)) { + if (!InternalWidgetTree.resize(parent, true, onOpen, false) && !InternalWidgetTree.resize(parent, false, onOpen, false)) { if (MCHelper.getPlayer() != null) { - MCHelper.getPlayer().sendMessage(new TextComponentString(IKey.RED + "ModularUI: Failed to resize sub tree of widget '" - + parent + "' of screen '" + parent.getScreen().toString() + "'. See log for more info.")); + MCHelper.getPlayer().sendMessage(new TextComponentString(IKey.RED + "ModularUI: Failed to resize sub tree of " + + parent.getDebugDisplayName() + ". See log for more info.")); } - ModularUI.LOGGER.error("Failed to resize widget. Affected widget tree:"); - printTree(parent, INFO_RESIZED_COLLAPSED); + //ModularUI.LOGGER.error("Failed to resize widget. Affected widget tree:"); + //printTree(parent, INFO_RESIZED_COLLAPSED); } - rawTime = System.nanoTime() - rawTime; // now apply the calculated pos applyPos(parent); - WidgetTree.foreachChildBFS(parent, child -> { - child.postResize(); - return true; - }, true); - + onResized(parent); if (WidgetTree.logResizeTime) { - fullTime = System.nanoTime() - fullTime; - ModularUI.LOGGER.info("Resized widget tree in {}s and {}s for full resize.", - NumberFormat.formatNanos(rawTime), - NumberFormat.formatNanos(fullTime)); + time = System.nanoTime() - time; + ModularUI.LOGGER.info("Resized widget tree in {}s.", NumberFormat.formatNanos(time)); + } + } + + public static void applyPos(ResizeNode parent) { + parent.applyPos(); + for (ResizeNode resizeNode : parent.getChildren()) { + if (!resizeNode.getChildren().isEmpty()) { + applyPos(resizeNode); + } + } + } + + public static void onResized(ResizeNode parent) { + parent.onResized(); + for (ResizeNode resizeNode : parent.getChildren()) { + if (!resizeNode.getChildren().isEmpty()) { + onResized(resizeNode); + } } } 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 b0f252b12..d760f1dff 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -472,7 +472,7 @@ public void set(Rectangle area) { /** * Transforms the four corners of this rectangle with the given pose stack. The new rectangle can be rotated. - * Then a min fit rectangle, which is not rotated and aligned with the screen, is put around the corners. + * Then a min fit rectangle, which is aligned with the screen axis, is put around the corners. * * @param stack pose stack */ diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java index ca24c4ac4..3be791cfa 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java @@ -12,4 +12,9 @@ public AreaResizer(Area area) { public Area getArea() { return area; } + + @Override + public String getDebugDisplayName() { + return ""; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java index 9b8806269..fac508632 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java @@ -1,26 +1,9 @@ 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.IResizeable; -import com.cleanroommc.modularui.api.widget.IGuiElement; -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 org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.DoubleSupplier; - /** * This class handles resizing and positioning of widgets. */ +/* public class Flex implements IResizeable, IPositioned { private final DimensionSizer x; @@ -444,8 +427,8 @@ public boolean resize(IGuiElement guiElement, boolean isParentLayout) { return true; }*/ - // calculate x, y, width and height if possible - this.x.apply(guiElement.getArea(), relativeTo, guiElement::getDefaultWidth); +// calculate x, y, width and height if possible + /* this.x.apply(guiElement.getArea(), relativeTo, guiElement::getDefaultWidth); this.y.apply(guiElement.getArea(), relativeTo, guiElement::getDefaultHeight); return isFullyCalculated(isParentLayout); } @@ -662,4 +645,4 @@ private Unit getWidth() { private Unit getHeight() { return this.y.getSize(this.parent); } -} +}*/ diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java index 1b6d342c7..f41b03def 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -3,40 +3,70 @@ import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.layout.IResizeable2; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; import java.util.List; public abstract class ResizeNode implements IResizeable2 { - private ResizeNode parent; + private ResizeNode defaultParent; + private ResizeNode parentOverride; private final List children = new ArrayList<>(); private boolean requiresResize = true; + @ApiStatus.Internal + public List getChildren() { + return children; + } + public ResizeNode getParent() { - return parent; + return parentOverride != null ? parentOverride : defaultParent; } - public List getChildren() { - return children; + public void dispose() { + this.defaultParent = null; + this.parentOverride = null; + this.children.clear(); } - public void setParent(ResizeNode resizeNode) { - if (this.parent != null) { - if (this.parent == resizeNode) return; - this.parent.children.remove(this); + private boolean removeFromParent(ResizeNode parent, ResizeNode parent2, ResizeNode replacement) { + if (parent != null) { + if (parent == replacement) return true; + parent.children.remove(this); + } else if (parent2 != null) { + if (parent2 == replacement) return true; + parent2.children.remove(this); } - this.parent = resizeNode; + return false; + } + + public void setDefaultParent(ResizeNode resizeNode) { + if (removeFromParent(this.defaultParent, null, resizeNode)) return; + this.defaultParent = resizeNode; if (resizeNode != null) { resizeNode.children.add(this); } } - public void reset() { - initResizing(); - this.parent = null; - this.children.clear(); + protected void setParentOverride(ResizeNode resizeNode) { + if (removeFromParent(this.parentOverride, this.defaultParent, resizeNode)) return; + this.parentOverride = resizeNode; + if (this.parentOverride != null) { + this.parentOverride.children.add(this); + } else if (this.defaultParent != null) { + this.defaultParent.children.add(this); + } + } + + @Override + public void initResizing(boolean onOpen) { + reset(); } + public void reset() {} + public void markDirty() { this.requiresResize = true; } @@ -84,4 +114,49 @@ public boolean dependsOnChildren(GuiAxis axis) { public boolean isSameResizer(ResizeNode node) { return node == this; } + + public boolean isLayout() { + return false; + } + + public boolean layoutChildren() { + return true; + } + + public boolean postLayoutChildren() { + return true; + } + + @ApiStatus.Internal + public void checkExpanded(@Nullable GuiAxis axis) {} + + public abstract boolean hasYPos(); + + public abstract boolean hasXPos(); + + public abstract boolean hasHeight(); + + public abstract boolean hasWidth(); + + public abstract boolean hasStartPos(GuiAxis axis); + + public abstract boolean hasEndPos(GuiAxis axis); + + public boolean hasPos(GuiAxis axis) { + return axis.isHorizontal() ? hasXPos() : hasYPos(); + } + + public boolean hasSize(GuiAxis axis) { + return axis.isHorizontal() ? hasWidth() : hasHeight(); + } + + public boolean isExpanded() { + return false; + } + + public abstract boolean isFullSize(); + + public abstract boolean hasFixedSize(); + + public abstract String getDebugDisplayName(); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java new file mode 100644 index 000000000..bbb55d204 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java @@ -0,0 +1,26 @@ +package com.cleanroommc.modularui.widget.sizer; + +import com.cleanroommc.modularui.screen.ModularScreen; + +public class ScreenResizeNode extends StaticResizer { + + private final ModularScreen screen; + + public ScreenResizeNode(ModularScreen screen) { + this.screen = screen; + } + + public ModularScreen getScreen() { + return screen; + } + + @Override + public Area getArea() { + return screen.getScreenArea(); + } + + @Override + public String getDebugDisplayName() { + return "screen '" + this.screen + "'"; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 97534d99d..827c85e43 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -12,6 +12,7 @@ import net.minecraft.inventory.Slot; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.function.DoubleSupplier; @@ -101,9 +102,15 @@ public void scheduleResize() { markDirty(); } + @ApiStatus.Internal @Override - public void initResizing() { - + public void checkExpanded(@Nullable GuiAxis axis) { + this.x.setExpanded(false); + this.y.setExpanded(false); + if (this.expanded && axis != null) { + if (axis.isHorizontal()) this.x.setExpanded(true); + else this.y.setExpanded(true); + } } @Override @@ -307,6 +314,13 @@ public void applyPos() { 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); + } + + @Override + public void onResized() { + IWidget widget = getWidget(); + Area area = widget.getArea(); + // update rx and ry to be relative to the widget parent not the resize node parent Area parentArea = widget.getParentArea(); area.rx = area.x - parentArea.x; area.ry = area.y - parentArea.y; @@ -319,6 +333,7 @@ public void applyPos() { slot.xPos = widget.getArea().x - mainArea.x + 1; slot.yPos = widget.getArea().y - mainArea.y + 1; } + super.onResized(); } @Override @@ -347,38 +362,36 @@ public void setYMarginPaddingApplied(boolean b) { this.y.setMarginPaddingApplied(b); } + @Override public boolean hasYPos() { return this.y.hasPos(); } + @Override public boolean hasXPos() { return this.x.hasPos(); } + @Override public boolean hasHeight() { return this.y.hasSize(); } + @Override public boolean hasWidth() { return this.x.hasSize(); } + @Override public boolean hasStartPos(GuiAxis axis) { return axis.isHorizontal() ? this.x.hasStart() : this.y.hasStart(); } + @Override 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(); @@ -405,10 +418,59 @@ public StandardResizer expanded() { return this; } + @Override public boolean isExpanded() { return this.expanded; } + @Override + public boolean hasFixedSize() { + return this.x.hasFixedSize() && this.y.hasFixedSize(); + } + + @Override + public boolean isFullSize() { + if (!hasHeight() || !hasWidth()) return false; + return this.x.isFullSize() && this.y.isFullSize(); + } + + @Override + public StandardResizer relative(ResizeNode resizeNode) { + setParentOverride(resizeNode); + return this; + } + + @Override + public StandardResizer relativeToParent() { + setParentOverride(null); + return this; + } + + @Override + public StandardResizer relativeToScreen() { + // TODO + return this; + } + + @Override + public StandardResizer coverChildren() { + this.x.setCoverChildren(true, getWidget()); + this.y.setCoverChildren(true, getWidget()); + return this; + } + + @Override + public StandardResizer coverChildrenWidth() { + this.x.setCoverChildren(true, getWidget()); + return this; + } + + @Override + public StandardResizer coverChildrenHeight() { + this.y.setCoverChildren(true, getWidget()); + return this; + } + @ApiStatus.Internal public StandardResizer left(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { return unit(getLeft(), x, offset, anchor, measure, autoAnchor); diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java index 681d4b937..316bc87d5 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java @@ -1,5 +1,7 @@ package com.cleanroommc.modularui.widget.sizer; +import com.cleanroommc.modularui.api.GuiAxis; + public abstract class StaticResizer extends ResizeNode { private boolean childrenCalculated = false; @@ -12,7 +14,7 @@ public StaticResizer() { } @Override - public void initResizing() {} + public void initResizing(boolean onOpen) {} @Override public boolean isXCalculated() { @@ -85,4 +87,44 @@ public void setXMarginPaddingApplied(boolean b) {} @Override public void setYMarginPaddingApplied(boolean b) {} + + @Override + public boolean hasYPos() { + return true; + } + + @Override + public boolean hasXPos() { + return true; + } + + @Override + public boolean hasHeight() { + return true; + } + + @Override + public boolean hasWidth() { + return true; + } + + @Override + public boolean hasStartPos(GuiAxis axis) { + return true; + } + + @Override + public boolean hasEndPos(GuiAxis axis) { + return false; + } + + @Override + public boolean hasFixedSize() { + return true; + } + + @Override + public boolean isFullSize() { + return false; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java index 2370f2661..1475420dd 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.widget.sizer; +import com.cleanroommc.modularui.api.layout.ILayoutWidget; import com.cleanroommc.modularui.api.widget.IWidget; import java.util.Objects; @@ -20,4 +21,42 @@ public IWidget getWidget() { public Area getArea() { return widget.getArea(); } + + @Override + public void initResizing(boolean onOpen) { + super.initResizing(onOpen); + this.widget.beforeResize(onOpen); + } + + @Override + public void onResized() { + super.onResized(); + this.widget.onResized(); + } + + @Override + public boolean isLayout() { + return this.widget instanceof ILayoutWidget; + } + + @Override + public boolean layoutChildren() { + if (this.widget instanceof ILayoutWidget layoutWidget) { + return layoutWidget.layoutWidgets(); + } + return true; + } + + @Override + public boolean postLayoutChildren() { + if (this.widget instanceof ILayoutWidget layoutWidget) { + return layoutWidget.postLayoutWidgets(); + } + return true; + } + + @Override + public String getDebugDisplayName() { + return "widget '" + this.widget + "' of screen '" + this.widget.getScreen() + "'"; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java index 9b714badb..b046f1978 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java @@ -139,7 +139,7 @@ public void onChildAdd(IWidget child) { private void updateHeight() { layoutWidgets(); - WidgetTree.applyPos(this); + WidgetTree.applyPos(resizer()); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java index b21797aae..0f0318220 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java @@ -99,7 +99,7 @@ public boolean layoutWidgets() { widget.resizer().updateResized(); continue; } - if (widget.flex().hasPos(axis)) { + if (widget.resizer().hasPos(axis)) { widget.resizer().updateResized(); // this is required when the widget has a pos on the main axis, but not on the cross axis continue; } @@ -113,7 +113,7 @@ public boolean layoutWidgets() { this.separatorPositions.add(p); p += separatorSize; if (isValid()) { - widget.flex().applyPos(widget); + widget.resizer().applyPos(); } } int size = p + getArea().getPadding().getEnd(axis); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/PopupMenu.java b/src/main/java/com/cleanroommc/modularui/widgets/PopupMenu.java deleted file mode 100644 index f8b39b1d4..000000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/PopupMenu.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.cleanroommc.modularui.widgets; - -import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.widget.Widget; - -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.List; - -public class PopupMenu> extends Widget { - - private final MenuWrapper menu; - private final List children; - - public PopupMenu(IWidget child) { - this.menu = new MenuWrapper(child); - child.flex().relative(this.getArea()); - this.menu.setEnabled(false); - this.children = Collections.singletonList(this.menu); - } - - @NotNull - @Override - public List getChildren() { - return this.children; - } - - @Override - public void onMouseStartHover() { - super.onMouseStartHover(); - this.menu.setEnabled(true); - this.menu.mightClose = false; - } - - @Override - public void onMouseEndHover() { - super.onMouseEndHover(); - this.menu.mightClose = true; - } - - public static class MenuWrapper extends Widget { - - private final IWidget child; - private final List children; - private boolean mightClose = false; - - private MenuWrapper(IWidget child) { - this.child = child; - this.children = Collections.singletonList(child); - flex().coverChildren().cancelMovementX().cancelMovementY(); - } - - @Override - public @NotNull List getChildren() { - return this.children; - } - - @Override - public void onUpdate() { - super.onUpdate(); - if (this.mightClose && !isBelowMouse()) { - setEnabled(false); - } - } - - public void setMightClose(boolean mightClose) { - this.mightClose = mightClose; - } - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java index 6ec403d4e..add102bc1 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java @@ -248,7 +248,7 @@ public SlotGroupWidget build() { x += 18; continue; } - widget.flex().left(x).top(y); + widget.resizer().left(x).top(y); slotGroupWidget.child(widget); if (this.syncKey != null && widget instanceof ISynced synced) { synced.syncHandler(this.syncKey, syncId++); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java index 6fc0dca79..9d390e8d9 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/TextWidget.java @@ -62,7 +62,7 @@ protected String checkString() { protected void onTextChanged(String newText) { // scheduling it would resize it on next frame, but we need it now - WidgetTree.resizeInternal(this, false); + WidgetTree.resizeInternal(resizer(), false); } private TextRenderer simulate(float maxWidth) { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index 8e92b6729..59481499f 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -67,8 +67,8 @@ public int getDefaultMainAxisSize() { GuiAxis axis = this.axis; int total = getArea().getPadding().getTotal(axis); for (IWidget widget : getChildren()) { - if (shouldIgnoreChildSize(widget) || widget.flex().hasPos(axis)) continue; - if (widget.flex().isExpanded() || !widget.resizer().isSizeCalculated(axis)) { + if (shouldIgnoreChildSize(widget) || widget.resizer().hasPos(axis)) continue; + if (widget.resizer().isExpanded() || !widget.resizer().isSizeCalculated(axis)) { total += axis.isHorizontal() ? widget.getDefaultWidth() : widget.getDefaultHeight(); } else { total += widget.getArea().getSize(axis); @@ -123,9 +123,9 @@ public boolean layoutWidgets() { // ignore disabled child if configured as such if (shouldIgnoreChildSize(widget)) continue; // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(this.axis)) continue; + if (widget.resizer().hasPos(this.axis)) continue; amount++; - if (widget.flex().isExpanded()) { + if (widget.resizer().isExpanded()) { expandedAmount++; childrenSize += widget.getArea().getMargin().getTotal(this.axis); continue; @@ -155,8 +155,8 @@ public boolean layoutWidgets() { // ignore disabled child if configured as such if (shouldIgnoreChildSize(widget)) continue; // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(this.axis)) continue; - if (widget.flex().isExpanded()) { + if (widget.resizer().hasPos(this.axis)) continue; + if (widget.resizer().isExpanded()) { widget.getArea().setSize(this.axis, newSize); widget.resizer().setSizeResized(this.axis, true); } @@ -181,7 +181,7 @@ public boolean layoutWidgets() { continue; } // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(this.axis)) { + if (widget.resizer().hasPos(this.axis)) { widget.resizer().updateResized(); // this is required when the widget has a pos on the main axis, but not on the cross axis continue; } @@ -215,10 +215,10 @@ public static boolean layoutCrossAxisListLike(IWidget parent, GuiAxis axis, Alig List childrenList = reverseLayout ? new ReversedList<>(parent.getChildren()) : parent.getChildren(); for (IWidget widget : childrenList) { // exclude children whose position of main axis is fixed - if (widget.flex().hasPos(axis)) continue; + if (widget.resizer().hasPos(axis)) continue; Box margin = widget.getArea().getMargin(); // don't align auto positioned children in cross axis - if (!widget.flex().hasPos(other) && widget.resizer().isSizeCalculated(other)) { + if (!widget.resizer().hasPos(other) && widget.resizer().isSizeCalculated(other)) { int crossAxisPos = margin.getStart(other) + padding.getStart(other); if (hasWidth) { if (caa == Alignment.CrossAxis.CENTER) { @@ -234,7 +234,7 @@ public static boolean layoutCrossAxisListLike(IWidget parent, GuiAxis axis, Alig } if (parent.isValid()) { // we changed rel pos, but we need to calculate the new absolute pos and other stuff - widget.flex().applyPos(widget); + widget.resizer().applyPos(); } } return true; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java index 9bf52fe5a..81cc3fad6 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -7,7 +7,7 @@ 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 com.cleanroommc.modularui.widget.sizer.StandardResizer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -92,7 +92,6 @@ private void initMenuList() { } this.menuList.setSource(this); this.menuList.relative(this); - this.menuList.bypassLayerRestriction(); this.direction.positioner.accept(this.menuList.flex()); } @@ -201,9 +200,9 @@ public enum Direction { RIGHT_DOWN(flex -> flex.leftRel(1f).top(0)), UNDEFINED(flex -> {}); - private final Consumer positioner; + private final Consumer positioner; - Direction(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 index 0ab572871..bdddd991a 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java @@ -51,11 +51,11 @@ public void onMouseLeaveArea() { @Override protected void onChildAdd(IWidget child) { super.onChildAdd(child); - if (!child.flex().hasHeight()) { - child.flex().height(12); + if (!child.resizer().hasHeight()) { + child.resizer().height(12); } - if (!child.flex().hasWidth()) { - child.flex().widthRel(1f); + if (!child.resizer().hasWidth()) { + child.resizer().widthRel(1f); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java index 7c34decff..dbba2a8d4 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java @@ -13,11 +13,11 @@ public class ContextMenuOption> extends Delegatin @Override protected void onChildAdd(IWidget child) { - if (!child.flex().hasHeight()) { - child.flex().height(12); + if (!child.resizer().hasHeight()) { + child.resizer().height(12); } - if (!child.flex().hasWidth()) { - child.flex().widthRel(1f); + if (!child.resizer().hasWidth()) { + child.resizer().widthRel(1f); } /*if (child instanceof Widget widget && widget.getWidgetThemeOverride() == null) { widget.widgetTheme(IThemeApi.MENU_OPTION); From ccbd6f4195334da0bbfd39856911f6f8916ac2d5 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Tue, 27 Jan 2026 19:58:20 +0100 Subject: [PATCH 04/20] finally works --- .../cleanroommc/modularui/api/ITreeNode.java | 18 + .../modularui/api/widget/IWidget.java | 6 +- .../modularui/overlay/DebugOverlay.java | 5 +- .../modularui/screen/ModularScreen.java | 2 + .../modularui/test/EventHandler.java | 13 +- .../cleanroommc/modularui/test/TestTile.java | 5 +- .../cleanroommc/modularui/utils/TreeUtil.java | 493 ++++++++++++++++++ .../modularui/widget/WidgetTree.java | 447 +--------------- .../modularui/widget/sizer/AreaResizer.java | 5 + .../widget/sizer/DimensionSizer.java | 42 +- .../widget/sizer/ExpanderStandardResizer.java | 20 + .../modularui/widget/sizer/ResizeNode.java | 15 +- .../widget/sizer/ScreenResizeNode.java | 5 + .../widget/sizer/StandardResizer.java | 21 +- .../modularui/widget/sizer/StaticResizer.java | 11 +- .../widget/sizer/WidgetResizeNode.java | 10 + .../modularui/widgets/Expandable.java | 6 + .../modularui/widgets/layout/Flow.java | 9 +- 18 files changed, 662 insertions(+), 471 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/api/ITreeNode.java create mode 100644 src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java diff --git a/src/main/java/com/cleanroommc/modularui/api/ITreeNode.java b/src/main/java/com/cleanroommc/modularui/api/ITreeNode.java new file mode 100644 index 000000000..d5718f87f --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/ITreeNode.java @@ -0,0 +1,18 @@ +package com.cleanroommc.modularui.api; + +import java.util.List; + +public interface ITreeNode> { + + T getParent(); + + default boolean hasParent() { + return getParent() != null; + } + + List getChildren(); + + default boolean hasChildren() { + return !getChildren().isEmpty(); + } +} 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 0d157bde9..2c0d031e8 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.api.widget; import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.ITreeNode; import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.screen.ModularPanel; @@ -20,7 +21,7 @@ /** * A widget in a Gui */ -public interface IWidget extends IGuiElement { +public interface IWidget extends IGuiElement, ITreeNode { /** * Validates and initialises this element. @@ -150,6 +151,7 @@ default boolean isInside(IViewportStack stack, int mx, int my, boolean absolute) * @return all children of this widget */ @NotNull + @Override default List getChildren() { return Collections.emptyList(); } @@ -157,6 +159,7 @@ default List getChildren() { /** * @return if this widget has any children */ + @Override default boolean hasChildren() { return !getChildren().isEmpty(); } @@ -229,6 +232,7 @@ default boolean canHoverThrough() { * @return the parent of this widget */ @NotNull + @Override IWidget getParent(); @Override diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index 0fef8cf05..e958e2088 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -1,6 +1,5 @@ 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; @@ -27,7 +26,7 @@ public class DebugOverlay extends CustomModularScreen { public static void register() { - OverlayManager.register(new OverlayHandler(screen -> ModularUIConfig.guiDebugMode && screen instanceof IMuiScreen, screen -> new DebugOverlay((IMuiScreen) screen))); + //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); @@ -92,7 +91,7 @@ private void drawDebug(GuiContext context, int x, int y, int w, int h, WidgetThe private boolean logWidgetTrees(int b) { for (ModularPanel panel : parent.getScreen().getPanelManager().getOpenPanels()) { - WidgetTree.printTree(panel); + WidgetTree.print(panel); } return true; } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index fb674f88d..2b94c3f2f 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -32,6 +32,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import mezz.jei.gui.ghost.GhostIngredientDrag; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.MustBeInvokedByOverriders; @@ -182,6 +183,7 @@ public void onResize(int width, int height) { } this.context.pushViewport(null, this.context.getScreenArea()); + WidgetTree.verifyTree(this.resizeNode, new ReferenceOpenHashSet<>()); WidgetTree.resizeInternal(this.resizeNode, true); this.context.popViewport(null); diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index a59c251b1..f31c68b2a 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -8,6 +8,8 @@ import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.factory.ClientGUI; import com.cleanroommc.modularui.holoui.HoloUI; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.RichTooltipEvent; import com.cleanroommc.modularui.screen.viewport.GuiContext; import com.cleanroommc.modularui.theme.ReloadThemeEvent; @@ -16,6 +18,7 @@ import com.cleanroommc.modularui.theme.WidgetTheme; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.utils.Platform; +import com.cleanroommc.modularui.widgets.ButtonWidget; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; @@ -52,7 +55,15 @@ public void onItemUse(PlayerInteractEvent.RightClickItem event) { if (event.getEntityPlayer().getEntityWorld().isRemote) { ItemStack itemStack = event.getItemStack(); if (itemStack.getItem() == Items.DIAMOND) { - ClientGUI.open(new TestGuis()); + //ClientGUI.open(new TestGuis()); + ClientGUI.open(new ModularScreen( + new ModularPanel("test") + .size(150) + .child(new ButtonWidget<>() + .size(50) + .center() + .overlay(IKey.str("Button"))) + )); } else if (itemStack.getItem() == Items.EMERALD) { HoloUI.builder() .inFrontOf(Platform.getClientPlayer(), 5, false) diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index c990003e7..cee3afc7b 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -179,7 +179,8 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI return new Column() .widthRel(1f) .coverChildrenHeight() - .children(vals.size(), i -> IKey.str(String.valueOf(vals.get(i))).asWidget().padding(2)); + .children(vals.size(), i -> IKey.str(String.valueOf(vals.get(i))).asWidget().padding(2)) + .name("synced number col"); }); Rectangle colorPickerBackground = new Rectangle().color(Color.RED.main); @@ -239,6 +240,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI .build() .margin(5, 5, 20, 5).name("crafting")))) .child(Flow.column() + .name("main col") .sizeRel(1f) .paddingBottom(7) .child(new ParentWidget<>() @@ -476,6 +478,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI .name("page 4 storage") .sizeRel(1f) .child(new Column() + .name("page 4 col, dynamic widgets") .padding(7) .child(new ItemSlot() .slot(new ModularSlot(this.storageInventory0, 0) diff --git a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java new file mode 100644 index 000000000..b1e018608 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java @@ -0,0 +1,493 @@ +package com.cleanroommc.modularui.utils; + +import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.api.ITreeNode; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.widget.sizer.ResizeNode; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.Streams; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class TreeUtil { + + public static boolean allowUnicode = true; + + private static final String U_T = "✓"; + private static final String U_F = "✘"; + private static final String T = "T"; + private static final String F = "F"; + private static final String U_PIPE = "│"; + private static final String U_PIPE_MID = "├"; + private static final String U_PIPE_END = "└"; + private static final String PIPE = "|"; + private static final String PIPE_MID = "+"; + private static final String PIPE_END = "-"; + + public static final NodeInfo RESIZE_NODE_INFO_FULLY_RESIZED = (root, node, builder) -> builder + .append("Fully resized: ") + .append(str(node.isFullyCalculated(node.hasParent() && node.getParent().isLayout()))); + public static final NodeInfo RESIZE_NODE_INFO_SELF_RESIZED_DETAIL = (root, node, builder) -> builder + .append("X: ").append(str(node.isXCalculated())) + .append(", Y: ").append(str(node.isYCalculated())) + .append(", W: ").append(str(node.isWidthCalculated())) + .append(", H: ").append(str(node.isHeightCalculated())) + .append(", Can relayout: ").append(node.canRelayout(node.hasParent() && node.getParent().isLayout())); + public static final NodeInfo RESIZE_NODE_INFO_RESIZED_DETAILED = (root, node, builder) -> { + boolean self = node.isSelfFullyCalculated(node.hasParent() && node.getParent().isLayout()); + builder.append("Self resized: ").append(str(self)) + .append(", Is pos final: ").append(str(!node.canRelayout(node.hasParent() && node.getParent().isLayout()))) + .append(", Children resized: ").append(str(node.areChildrenCalculated())) + .append(", Layout done: ").append(str(node.isLayoutDone())); + if (!self) { + builder.append(", Self detail: "); + RESIZE_NODE_INFO_SELF_RESIZED_DETAIL.addInfo(root, node, builder); + } + }; + public static final NodeInfo RESIZE_NODE_INFO_RESIZED_COLLAPSED = (root, node, builder) -> { + if (node.isFullyCalculated(node.hasParent() && node.getParent().isLayout())) { + RESIZE_NODE_INFO_FULLY_RESIZED.addInfo(root, node, builder); + } else { + RESIZE_NODE_INFO_RESIZED_DETAILED.addInfo(root, node, builder); + } + }; + + private static String str(boolean b) { + if (TreeUtil.allowUnicode) return b ? U_T : U_F; + return b ? T : F; + } + + public static > boolean foreachChildBFS(T parent, Predicate consumer) { + return foreachChildBFS(parent, consumer, false); + } + + /** + * Iterates through the whole sub widget tree by using breath-first-search. + *

+ * This method delivers good performance and can outperform {@link #foreachChild(T, Predicate, boolean)} in certain small widget + * trees. + * + * @param parent starting point + * @param consumer Operation on each child. Return false to terminate the iteration. + * @param includeSelf true if the consumer should also consume the parent + * @return true if the iteration was not terminated by the consumer + */ + public static > boolean foreachChildBFS(T parent, Predicate consumer, boolean includeSelf) { + if (includeSelf && !consumer.test(parent)) return false; + ObjectList parents = ObjectList.create(); + parents.add(parent); + while (!parents.isEmpty()) { + for (T child : parents.removeFirst().getChildren()) { + if (child.hasChildren()) { + parents.addLast(child); + } + if (!consumer.test(child)) return false; + } + } + return true; + } + + /** + * @see #foreachChild(T, Predicate, boolean) + */ + public static > boolean foreachChild(T parent, Predicate consumer) { + return foreachChild(parent, consumer, false); + } + + /** + * Iterates through the whole sub widget tree recursively. + *

+ * This method has the best performance in most cases, but can be outperformed on certain small widget trees. + * + * @param parent starting point + * @param consumer Operation on each child. Return false to terminate the iteration. + * @param includeSelf true if the consumer should also consume the parent + * @return true if the iteration was not terminated by the consumer + */ + public static > boolean foreachChild(T parent, Predicate consumer, boolean includeSelf) { + if (includeSelf && !consumer.test(parent)) return false; + if (!parent.hasChildren()) return true; + for (T widget : parent.getChildren()) { + if (!consumer.test(widget)) return false; + if (widget.hasChildren() && !foreachChild(widget, consumer, false)) { + return false; + } + } + return true; + } + + /** + * Iterates through the whole sub widget tree recursively. Unlike {@link #foreachChild(T, Predicate, boolean)}, which can only + * return a boolean, this method can return any type. Once the consumer returns a non-null value, the iteration is terminated and the + * value will be returned. + * + * @param parent starting point + * @param consumer Operation on each child. Return a non-null value to terminate the iteration and to return the value. + * @param includeSelf true if the consumer should also consume the parent + * @return the first resulting value of the consumer or null of it always returned null + */ + public static , V> @Nullable V foreachChildWithResult(T parent, Function consumer, boolean includeSelf) { + if (includeSelf) { + V t = consumer.apply(parent); + if (t != null) return t; + } + if (!parent.hasChildren()) return null; + for (T widget : parent.getChildren()) { + V t = consumer.apply(widget); + if (t != null) return t; + if (widget.hasChildren()) { + t = foreachChildWithResult(widget, consumer, false); + if (t != null) return t; + } + } + return null; + } + + public static > boolean foreachChildReverse(T parent, Predicate consumer, boolean includeSelf) { + if (parent.getChildren().isEmpty()) { + return !includeSelf || consumer.test(parent); + } + for (T widget : parent.getChildren()) { + if (!widget.getChildren().isEmpty() && foreachChildReverse(widget, consumer, false)) { + return false; + } + if (!consumer.test(widget)) return false; + } + return !includeSelf || consumer.test(parent); + } + + /** + * Creates a flat stream of the whole sub widget tree. + *

+ * {@link Stream#forEach(Consumer)} on this has slightly worse performance than {@link #foreachChildBFS(T, Predicate, boolean)} on + * small widget trees and has similar performance on large widget trees. The performance is significantly better than + * {@link #iteratorBFS(T)} even though this method uses it. + * + * @param parent starting point. + * @return stream of the sub widget tree + */ + @SuppressWarnings("UnstableApiUsage") + public static > Stream flatStreamBFS(T parent) { + if (!parent.hasChildren()) return Stream.of(parent); + return Streams.stream(iteratorBFS(parent)); + } + + public static > @UnmodifiableView Iterable iterableBFS(T parent) { + return () -> iteratorBFS(parent); + } + + /** + * Creates an unmodifiable iterator of the whole sub widget tree. + *

+ * This method of iterating has the worst performance in every case. It's roughly 4 times worse than + * {@link #foreachChildBFS(T, Predicate, boolean)}. If not used extensively the performance is still nothing to worry about. + * + * @param parent starting point + * @return an unmodifiable iterator of the sub widget tree + */ + public static > @UnmodifiableView Iterator iteratorBFS(T parent) { + return new AbstractIterator<>() { + + private final ObjectList queue = ObjectList.create(); + private Iterator currentIt; + + @Override + protected T computeNext() { + if (currentIt == null) { + currentIt = parent.getChildren().iterator(); + return parent; + } + if (currentIt.hasNext()) return handleWidget(currentIt.next()); + while (!queue.isEmpty()) { + currentIt = queue.removeFirst().getChildren().iterator(); + if (currentIt.hasNext()) return handleWidget(currentIt.next()); + } + return endOfData(); + } + + private T handleWidget(T widget) { + if (widget.hasChildren()) { + queue.addLast(widget); + } + return widget; + } + }; + } + + /** + * Finds all children in the sub widget tree which match the test and puts them in a list. + * + * @param parent starting point + * @param test test which the target children have to pass + * @return a list of matching children + */ + public static > List flatList(T parent, Predicate test) { + List widgets = new ArrayList<>(); + foreachChild(parent, w -> { + if (test.test(w)) widgets.add(w); + return true; + }, true); + return widgets; + } + + /** + * Finds all children in the sub widget tree which match the test and puts them in a list. + * + * @param parent starting point + * @param test test which the target children have to pass + * @return a list of matching children + */ + public static > List flatListBFS(T parent, Predicate test) { + List widgets = new ArrayList<>(); + foreachChildBFS(parent, w -> { + if (test.test(w)) widgets.add(w); + return true; + }, true); + return widgets; + } + + public static , R extends ITreeNode> List flatListByType(T parent, Class type) { + return flatListByType(parent, type, null); + } + + /** + * Finds all widgets in the sub widget tree which match the given type and additional test. + * + * @param parent starting point + * @param type type of the target widgets + * @param test test which the target widgets have to pass + * @param type of the target widgets + * @return a list of matching widgets + */ + @SuppressWarnings("unchecked") + public static , R extends ITreeNode> List flatListByType(T parent, Class type, @Nullable Predicate test) { + List widgets = new ArrayList<>(); + foreachChild(parent, w -> { + if (type.isAssignableFrom(w.getClass())) { + if (test == null || test.test((R) w)) widgets.add((R) w); + } + return true; + }, true); + return widgets; + } + + /** + * Finds the first widget in the sub widget tree, for which the test returns true. + * + * @param parent starting point + * @param test test which the widget has to pass + * @return the first matching widget + */ + public static > T findFirst(T parent, @NotNull Predicate test) { + return foreachChildWithResult(parent, w -> { + if (test.test(w)) { + return w; + } + return null; + }, true); + } + + /** + * Finds the first widget in the sub widget tree with the given type, for which the test returns true. + * + * @param parent starting point + * @param type type of the target widget + * @param test test which the widget has to pass + * @return the first matching widget + */ + @SuppressWarnings("unchecked") + public static , R extends ITreeNode> R findFirst(T parent, Class type, @Nullable Predicate test) { + return foreachChildWithResult(parent, t -> { + if (type.isAssignableFrom(t.getClass())) { + if (test == null || test.test((R) t)) { + return (R) t; + } + } + return null; + }, true); + } + + public static > T findParent(T parent, Predicate filter) { + if (parent == null) return null; + while (!(parent instanceof ModularPanel)) { + if (filter.test(parent)) { + return parent; + } + parent = parent.getParent(); + } + return filter.test(parent) ? parent : null; + } + + public static , R extends ITreeNode> R findParent(T parent, Class type) { + return findParent(parent, type, null); + } + + @SuppressWarnings("unchecked") + public static , R extends ITreeNode> R findParent(T parent, Class type, @Nullable Predicate test) { + if (parent == null) return null; + while (!(parent instanceof ModularPanel)) { + if (type.isAssignableFrom(parent.getClass()) && (test == null || test.test((R) parent))) { + return (R) parent; + } + parent = parent.getParent(); + } + return type.isAssignableFrom(parent.getClass()) && (test == null || test.test((R) parent)) ? (R) parent : null; + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + */ + public static > void print(T parent) { + print(parent, w -> true, null); + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + * @param additionalInfo additional info function which is executed for each widget + */ + public static > void print(T parent, NodeInfo additionalInfo) { + print(parent, w -> true, additionalInfo); + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + * @param test test widgets have to pass to be added to the string builder + */ + public static > void print(T parent, Predicate test) { + print(parent, test, null); + } + + /** + * Prints the whole sub widget tree to the log as a human-readable tree graph with Unicode characters. You may need to enabled Unicode + * characters in your IDE terminal to display them properly. + * + * @param parent starting point + * @param test test widgets have to pass to be added to the string builder + * @param additionalInfo additional info function which is executed for each widget + */ + public static > void print(T parent, Predicate test, NodeInfo additionalInfo) { + StringBuilder builder = new StringBuilder("Widget tree of ") + .append(parent) + .append('\n'); + ModularUI.LOGGER.info(toString(builder, parent, test, additionalInfo)); + } + + public static > String toString(T parent) { + return toString(parent, w -> true, null); + } + + public static > String toString(T parent, NodeInfo additionalInfo) { + return toString(parent, w -> true, additionalInfo); + } + + public static > String toString(T parent, Predicate test) { + return toString(parent, test, null); + } + + public static > String toString(T parent, Predicate test, NodeInfo additionalInfo) { + return toString(null, parent, test, additionalInfo).toString(); + } + + /** + * Writes the sub widget tree into a human-readable tree graph with Unicode characters. + * + * @param builder the string builder to add the tree to or null for a new builder + * @param parent starting point + * @param test test widgets have to pass to be added to the string builder + * @param additionalInfo additional info function which is executed for each widget + * @return the string builder which was used to build the graph + */ + public static > StringBuilder toString(StringBuilder builder, T parent, Predicate test, NodeInfo additionalInfo) { + if (builder == null) builder = new StringBuilder(); + getTree(parent, parent, test, builder, additionalInfo, "", false); + return builder; + } + + protected static > void getTree(T root, T parent, Predicate test, StringBuilder builder, NodeInfo additionalInfo, String indent, boolean hasNextSibling) { + if (!indent.isEmpty()) { + builder.append(indent); + if (TreeUtil.allowUnicode) { + builder.append(hasNextSibling ? U_PIPE_MID : U_PIPE_END); + } else { + builder.append(hasNextSibling ? PIPE_MID : PIPE_END); + } + builder.append(' '); + } + builder.append(parent); + if (additionalInfo != null) { + builder.append(" {"); + additionalInfo.addInfo(root, parent, builder); + builder.append("}"); + } + builder.append('\n'); + if (parent.hasChildren()) { + List children = parent.getChildren(); + for (int i = 0; i < children.size(); i++) { + T child = children.get(i); + if (test.test(child)) { + String nextIndent = indent; + if (hasNextSibling) { + nextIndent += (TreeUtil.allowUnicode ? U_PIPE : PIPE) + ' '; + } else { + nextIndent += " "; + } + getTree(root, child, test, builder, additionalInfo, nextIndent, i < children.size() - 1); + } + } + } + } + + public interface NodeInfo> { + + void addInfo(T root, T widget, StringBuilder builder); + + default NodeInfo combine(NodeInfo other, String joiner) { + return (root, widget, builder) -> { + addInfo(root, widget, builder); + builder.append(joiner); + other.addInfo(root, widget, builder); + }; + } + + default NodeInfo combine(NodeInfo other) { + return combine(other, " | "); + } + + @SafeVarargs + static > NodeInfo of(String joiner, NodeInfo... infos) { + return (root, widget, builder) -> { + for (int i = 0; i < infos.length; i++) { + NodeInfo info = infos[i]; + info.addInfo(root, widget, builder); + if (i < infos.length - 1) { + builder.append(joiner); + } + } + }; + } + + @SafeVarargs + static > NodeInfo of(NodeInfo... infos) { + return of(" | ", infos); + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index e6cdc8fcd..ae8848cca 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -3,40 +3,30 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.api.layout.ILayoutWidget; import com.cleanroommc.modularui.api.widget.ISynced; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.utils.NumberFormat; -import com.cleanroommc.modularui.utils.ObjectList; +import com.cleanroommc.modularui.utils.TreeUtil; import com.cleanroommc.modularui.value.sync.ModularSyncManager; import com.cleanroommc.modularui.value.sync.PanelSyncManager; import com.cleanroommc.modularui.widget.sizer.ResizeNode; import net.minecraft.util.text.TextComponentString; -import com.google.common.collect.AbstractIterator; -import com.google.common.collect.Streams; import org.apache.commons.lang3.mutable.MutableInt; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.UnmodifiableView; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; import java.util.NoSuchElementException; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; +import java.util.Set; /** * Helper class to perform operations on widget trees such as traversing, drawing, resizing, finding widgets and printing it. */ -public class WidgetTree { +public class WidgetTree extends TreeUtil { /** * If this variable is true, the time it takes to resize a sub widget tree is logged each time. @@ -54,282 +44,11 @@ public class WidgetTree { .append(", ") .append(widget.getArea().height); public static final WidgetInfo INFO_ENABLED = (root, widget, builder) -> builder.append("Enabled: ").append(widget.isEnabled()); - public static final WidgetInfo INFO_FULLY_RESIZED = (root, widget, builder) -> builder - .append("Fully resized: ") - .append(widget.resizer().isFullyCalculated(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)); - public static final WidgetInfo INFO_RESIZED_DETAILED = (root, widget, builder) -> builder - .append("Self resized: ").append(widget.resizer().isSelfFullyCalculated(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)) - .append(", Is pos final: ").append(!widget.resizer().canRelayout(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)) - .append(", Children resized: ").append(widget.resizer().areChildrenCalculated()) - .append(", Layout done: ").append(widget.resizer().isLayoutDone()); - public static final WidgetInfo INFO_RESIZED_COLLAPSED = (root, widget, builder) -> { - if (widget.resizer().isFullyCalculated(widget.hasParent() && widget.getParent() instanceof ILayoutWidget)) { - INFO_FULLY_RESIZED.addInfo(root, widget, builder); - } else { - INFO_RESIZED_DETAILED.addInfo(root, widget, builder); - } - }; public static final WidgetInfo INFO_WIDGET_THEME = (root, widget, builder) -> builder.append("Widget theme: ") .append(widget.getWidgetTheme(widget.getContext().getTheme()).getKey().getFullName()); private WidgetTree() {} - public static List getAllChildrenByLayer(IWidget parent) { - return getAllChildrenByLayer(parent, false); - } - - public static List getAllChildrenByLayer(IWidget parent, boolean includeSelf) { - List children = new ArrayList<>(); - if (includeSelf) children.add(parent); - ObjectList parents = ObjectList.create(); - parents.add(parent); - while (!parents.isEmpty()) { - for (IWidget child : parents.removeFirst().getChildren()) { - if (!child.getChildren().isEmpty()) { - parents.add(child); - } - children.add(child); - } - } - return children; - } - - public static boolean foreachChildBFS(IWidget parent, Predicate consumer) { - return foreachChildBFS(parent, consumer, false); - } - - /** - * Iterates through the whole sub widget tree by using breath-first-search. - *

- * This method delivers good performance and can outperform {@link #foreachChild(IWidget, Predicate, boolean)} in certain small widget - * trees. - * - * @param parent starting point - * @param consumer Operation on each child. Return false to terminate the iteration. - * @param includeSelf true if the consumer should also consume the parent - * @return true if the iteration was not terminated by the consumer - */ - public static boolean foreachChildBFS(IWidget parent, Predicate consumer, boolean includeSelf) { - if (includeSelf && !consumer.test(parent)) return false; - ObjectList parents = ObjectList.create(); - parents.add(parent); - while (!parents.isEmpty()) { - for (IWidget child : parents.removeFirst().getChildren()) { - if (child.hasChildren()) { - parents.addLast(child); - } - if (!consumer.test(child)) return false; - } - } - return true; - } - - /** - * @see #foreachChild(IWidget, Predicate, boolean) - */ - public static boolean foreachChild(IWidget parent, Predicate consumer) { - return foreachChild(parent, consumer, false); - } - - /** - * Iterates through the whole sub widget tree recursively. - *

- * This method has the best performance in most cases, but can be outperformed on certain small widget trees. - * - * @param parent starting point - * @param consumer Operation on each child. Return false to terminate the iteration. - * @param includeSelf true if the consumer should also consume the parent - * @return true if the iteration was not terminated by the consumer - */ - public static boolean foreachChild(IWidget parent, Predicate consumer, boolean includeSelf) { - if (includeSelf && !consumer.test(parent)) return false; - if (!parent.hasChildren()) return true; - for (IWidget widget : parent.getChildren()) { - if (!consumer.test(widget)) return false; - if (widget.hasChildren() && !foreachChild(widget, consumer, false)) { - return false; - } - } - return true; - } - - /** - * Iterates through the whole sub widget tree recursively. Unlike {@link #foreachChild(IWidget, Predicate, boolean)}, which can only - * return a boolean, this method can return any type. Once the consumer returns a non-null value, the iteration is terminated and the - * value will be returned. - * - * @param parent starting point - * @param consumer Operation on each child. Return a non-null value to terminate the iteration and to return the value. - * @param includeSelf true if the consumer should also consume the parent - * @return the first resulting value of the consumer or null of it always returned null - */ - public static @Nullable T foreachChildWithResult(IWidget parent, Function consumer, boolean includeSelf) { - if (includeSelf) { - T t = consumer.apply(parent); - if (t != null) return t; - } - if (!parent.hasChildren()) return null; - for (IWidget widget : parent.getChildren()) { - T t = consumer.apply(widget); - if (t != null) return t; - if (widget.hasChildren()) { - t = foreachChildWithResult(widget, consumer, false); - if (t != null) return t; - } - } - return null; - } - - public static boolean foreachChildReverse(IWidget parent, Predicate consumer, boolean includeSelf) { - if (parent.getChildren().isEmpty()) { - return !includeSelf || consumer.test(parent); - } - for (IWidget widget : parent.getChildren()) { - if (!widget.getChildren().isEmpty() && foreachChildReverse(widget, consumer, false)) { - return false; - } - if (!consumer.test(widget)) return false; - } - return !includeSelf || consumer.test(parent); - } - - /** - * Creates a flat stream of the whole sub widget tree. - *

- * {@link Stream#forEach(Consumer)} on this has slightly worse performance than {@link #foreachChildBFS(IWidget, Predicate, boolean)} on - * small widget trees and has similar performance on large widget trees. The performance is significantly better than - * {@link #iteratorBFS(IWidget)} even though this method uses it. - * - * @param parent starting point. - * @return stream of the sub widget tree - */ - @SuppressWarnings("UnstableApiUsage") - public static Stream flatStream(IWidget parent) { - if (!parent.hasChildren()) return Stream.of(parent); - return Streams.stream(iteratorBFS(parent)); - } - - public static @UnmodifiableView Iterable iterableBFS(IWidget parent) { - return () -> iteratorBFS(parent); - } - - /** - * Creates an unmodifiable iterator of the whole sub widget tree. - *

- * This method of iterating has the worst performance in every case. It's roughly 4 times worse than - * {@link #foreachChildBFS(IWidget, Predicate, boolean)}. If not used extensively the performance is still nothing to worry about. - * - * @param parent starting point - * @return an unmodifiable iterator of the sub widget tree - */ - public static @UnmodifiableView Iterator iteratorBFS(IWidget parent) { - return new AbstractIterator<>() { - - private final ObjectList queue = ObjectList.create(); - private Iterator currentIt; - - @Override - protected IWidget computeNext() { - if (currentIt == null) { - currentIt = parent.getChildren().iterator(); - return parent; - } - if (currentIt.hasNext()) return handleWidget(currentIt.next()); - while (!queue.isEmpty()) { - currentIt = queue.removeFirst().getChildren().iterator(); - if (currentIt.hasNext()) return handleWidget(currentIt.next()); - } - return endOfData(); - } - - private IWidget handleWidget(IWidget widget) { - if (widget.hasChildren()) { - queue.addLast(widget); - } - return widget; - } - }; - } - - /** - * Finds all widgets in the sub widget tree which match the test. - * - * @param parent starting point - * @param test test which the target widgets have to pass - * @return a list of matching widgets - */ - public static List collectWidgets(IWidget parent, Predicate test) { - List widgets = new ArrayList<>(); - foreachChild(parent, w -> { - if (test.test(w)) widgets.add(w); - return true; - }, true); - return widgets; - } - - public static List collectWidgetsByType(IWidget parent, Class type) { - return collectWidgetsByType(parent, type, null); - } - - /** - * Finds all widgets in the sub widget tree which match the given type and additional test. - * - * @param parent starting point - * @param type type of the target widgets - * @param test test which the target widgets have to pass - * @param type of the target widgets - * @return a list of matching widgets - */ - @SuppressWarnings("unchecked") - public static List collectWidgetsByType(IWidget parent, Class type, @Nullable Predicate test) { - List widgets = new ArrayList<>(); - foreachChild(parent, w -> { - if (w.isType(type)) { - T t = (T) w; - if (test == null || test.test(t)) widgets.add(t); - } - return true; - }, true); - return widgets; - } - - /** - * Finds the first widget in the sub widget tree, for which the test returns true. - * - * @param parent starting point - * @param test test which the widget has to pass - * @return the first matching widget - */ - public static IWidget findFirst(IWidget parent, @NotNull Predicate test) { - return foreachChildWithResult(parent, w -> { - if (test.test(w)) { - return w; - } - return null; - }, true); - } - - /** - * Finds the first widget in the sub widget tree with the given type, for which the test returns true. - * - * @param parent starting point - * @param type type of the target widget - * @param test test which the widget has to pass - * @return the first matching widget - */ - @SuppressWarnings("unchecked") - public static T findFirst(IWidget parent, Class type, @Nullable Predicate test) { - return foreachChildWithResult(parent, w -> { - if (w.isType(type)) { - T t = (T) w; - if (test == null || test.test(t)) { - return t; - } - } - return null; - }, true); - } - /** * Finds the first widget in the sub widget tree that matches the given name. * @@ -454,29 +173,6 @@ public static T findFirst(IWidget parent, Class type, @Nu return InternalWidgetTree.findChildAt(parent, type, path, 0, false); } - public static IWidget findParent(IWidget parent, Predicate filter) { - if (parent == null) return null; - while (!(parent instanceof ModularPanel)) { - if (filter.test(parent)) { - return parent; - } - parent = parent.getParent(); - } - return filter.test(parent) ? parent : null; - } - - @SuppressWarnings("unchecked") - public static T findParent(IWidget parent, Class type) { - if (parent == null) return null; - while (!(parent instanceof ModularPanel)) { - if (type.isAssignableFrom(parent.getClass())) { - return (T) parent; - } - parent = parent.getParent(); - } - return type.isAssignableFrom(parent.getClass()) ? (T) parent : null; - } - public static boolean hasSyncedValues(ModularPanel panel) { return !foreachChild(panel, widget -> !(widget instanceof ISynced synced) || !synced.isSynced(), true); } @@ -554,12 +250,12 @@ public static void resizeInternal(ResizeNode parent, boolean onOpen) { MCHelper.getPlayer().sendMessage(new TextComponentString(IKey.RED + "ModularUI: Failed to resize sub tree of " + parent.getDebugDisplayName() + ". See log for more info.")); } - //ModularUI.LOGGER.error("Failed to resize widget. Affected widget tree:"); - //printTree(parent, INFO_RESIZED_COLLAPSED); + ModularUI.LOGGER.error("Failed to resize widget. Affected resize node tree:"); + print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); } // now apply the calculated pos applyPos(parent); - onResized(parent); + postFullResize(parent); if (WidgetTree.logResizeTime) { time = System.nanoTime() - time; ModularUI.LOGGER.info("Resized widget tree in {}s.", NumberFormat.formatNanos(time)); @@ -569,132 +265,31 @@ public static void resizeInternal(ResizeNode parent, boolean onOpen) { public static void applyPos(ResizeNode parent) { parent.applyPos(); for (ResizeNode resizeNode : parent.getChildren()) { - if (!resizeNode.getChildren().isEmpty()) { - applyPos(resizeNode); - } + applyPos(resizeNode); } } - public static void onResized(ResizeNode parent) { - parent.onResized(); + public static void postFullResize(ResizeNode parent) { + parent.postFullResize(); for (ResizeNode resizeNode : parent.getChildren()) { - if (!resizeNode.getChildren().isEmpty()) { - onResized(resizeNode); - } + postFullResize(resizeNode); } } - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - */ - public static void printTree(IWidget parent) { - printTree(parent, w -> true, null); - } - - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - * @param additionalInfo additional info function which is executed for each widget - */ - public static void printTree(IWidget parent, WidgetInfo additionalInfo) { - printTree(parent, w -> true, additionalInfo); - } - - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - * @param test test widgets have to pass to be added to the string builder - */ - public static void printTree(IWidget parent, Predicate test) { - printTree(parent, test, null); - } - - /** - * Prints the whole sub widget tree to the log as a human-readable tree graph with unicode characters. You may need to enabled unicode - * characters in your IDE terminal to display them properly. - * - * @param parent starting point - * @param test test widgets have to pass to be added to the string builder - * @param additionalInfo additional info function which is executed for each widget - */ - public static void printTree(IWidget parent, Predicate test, WidgetInfo additionalInfo) { - StringBuilder builder = new StringBuilder("Widget tree of ") - .append(parent) - .append('\n'); - ModularUI.LOGGER.info(widgetTreeToString(builder, parent, test, additionalInfo)); - } - - public static String widgetTreeToString(IWidget parent) { - return widgetTreeToString(parent, w -> true, null); - } - - public static String widgetTreeToString(IWidget parent, WidgetInfo additionalInfo) { - return widgetTreeToString(parent, w -> true, additionalInfo); - } - - public static String widgetTreeToString(IWidget parent, Predicate test) { - return widgetTreeToString(parent, test, null); - } - - public static String widgetTreeToString(IWidget parent, Predicate test, WidgetInfo additionalInfo) { - return widgetTreeToString(null, parent, test, additionalInfo).toString(); - } - - /** - * Writes the sub widget tree into a human-readable tree graph with unicode characters. - * - * @param builder the string builder to add the tree to or null for a new builder - * @param parent starting point - * @param test test widgets have to pass to be added to the string builder - * @param additionalInfo additional info function which is executed for each widget - * @return the string builder which was used to build the graph - */ - public static StringBuilder widgetTreeToString(StringBuilder builder, IWidget parent, Predicate test, WidgetInfo additionalInfo) { - if (builder == null) builder = new StringBuilder(); - InternalWidgetTree.getTree(parent, parent, test, builder, additionalInfo, "", false); - return builder; + public static void verifyTree(ResizeNode parent, Set visited) { + if (visited.contains(parent)) { + throw new IllegalStateException("Found cycling resize node dependencies!"); + } + visited.add(parent); + if (!parent.getChildren().isEmpty()) { + for (ResizeNode child : parent.getChildren()) { + verifyTree(child, visited); + } + } } /** * An interface to add information of a widget to a string builder. */ - public interface WidgetInfo { - - void addInfo(IWidget root, IWidget widget, StringBuilder builder); - - default WidgetInfo combine(WidgetInfo other, String joiner) { - return (root, widget, builder) -> { - addInfo(root, widget, builder); - builder.append(joiner); - other.addInfo(root, widget, builder); - }; - } - - default WidgetInfo combine(WidgetInfo other) { - return combine(other, " | "); - } - - static WidgetInfo of(String joiner, WidgetInfo... infos) { - return (root, widget, builder) -> { - for (int i = 0; i < infos.length; i++) { - WidgetInfo info = infos[i]; - info.addInfo(root, widget, builder); - if (i < infos.length - 1) { - builder.append(joiner); - } - } - }; - } - - static WidgetInfo of(WidgetInfo... infos) { - return of(" | ", infos); - } - } + public interface WidgetInfo extends NodeInfo {} } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java index 3be791cfa..70c82bc18 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/AreaResizer.java @@ -17,4 +17,9 @@ public Area getArea() { public String getDebugDisplayName() { return ""; } + + @Override + public String toString() { + return "AreaResizer(" + this.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 7381fb30e..33010041c 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java @@ -133,10 +133,6 @@ public boolean isPosCalculated() { return this.posCalculated; } - public void setSizeCalculated(boolean b) { - this.resizer.setSizeResized(this.axis, b); - } - public boolean canRelayout() { return canRelayout; } @@ -146,9 +142,13 @@ public boolean dependsOnChildren() { } public boolean dependsOnParent() { - return !this.coverChildren && (this.end != null || + if (this.coverChildren) { + // if we cover children we ignore size config + return this.end != null || (this.start != null && this.start.isRelative()); + } + return this.end != null || (this.start != null && this.start.isRelative()) || - (this.size != null && this.size.isRelative())); + (this.size != null && this.size.isRelative()); } public void setResized(boolean all) { @@ -175,13 +175,22 @@ private boolean needsSize(Unit unit) { public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { // is already calculated - boolean sizeCalculated = isSizeCalculated(); - boolean posCalculated = isPosCalculated(); - if (sizeCalculated && posCalculated) return; + boolean sizeCalculated; + boolean posCalculated; int p, s; - int parentSize = relativeTo.getArea().getSize(this.axis); - boolean calcParent = relativeTo.isSizeCalculated(this.axis); - Box padding = relativeTo.getArea().getPadding(); + int parentSize; + boolean calcParent; + Box padding; + try { + sizeCalculated = isSizeCalculated(); + posCalculated = isPosCalculated(); + if (sizeCalculated && posCalculated) return; + parentSize = relativeTo.getArea().getSize(this.axis); + calcParent = relativeTo.isSizeCalculated(this.axis); + padding = relativeTo.getArea().getPadding(); + } catch (Throwable e) { + throw e; + } if (sizeCalculated) { // pos not calculated // size was calculated before @@ -220,10 +229,11 @@ public void apply(Area area, ResizeNode 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 &= posCalculated; + this.posCalculated &= b; this.sizeCalculated |= this.posCalculated; } else { s = defaultSize.getAsInt(); @@ -258,7 +268,7 @@ public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { s = Math.min(s, parentSize /*- padding.getTotal(this.axis)*/ - margin.getTotal(this.axis)); } area.setRelativePoint(this.axis, p); - area.setPoint(this.axis, p + relativeTo.getArea().x); // temporary + area.setPoint(this.axis, p + relativeTo.getArea().getPoint(this.axis)); // temporary area.setSize(this.axis, s); } @@ -349,7 +359,7 @@ private int calcSize(Unit s, Box padding, int parentSize, boolean parentSizeCalc val *= parentSize - padding.getTotal(this.axis); } val += s.getOffset(); - this.resizer.setSizeResized(this.axis, true); + this.sizeCalculated = true; return (int) val; } @@ -367,7 +377,7 @@ public int calcPoint(Unit p, Box padding, int width, int parentSize, boolean par if (p == this.end) { val = parentSize - val; } - this.resizer.setPosResized(this.axis, true); + this.posCalculated = true; return (int) val; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java new file mode 100644 index 000000000..074115f87 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java @@ -0,0 +1,20 @@ +package com.cleanroommc.modularui.widget.sizer; + +import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widgets.layout.IExpander; + +public class ExpanderStandardResizer extends StandardResizer implements IExpander { + + private final GuiAxis axis; + + public ExpanderStandardResizer(IWidget widget, GuiAxis axis) { + super(widget); + this.axis = axis; + } + + @Override + public GuiAxis getExpandAxis() { + return axis; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java index f41b03def..d0d9fb672 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.widget.sizer; import com.cleanroommc.modularui.api.GuiAxis; +import com.cleanroommc.modularui.api.ITreeNode; import com.cleanroommc.modularui.api.layout.IResizeable2; import org.jetbrains.annotations.ApiStatus; @@ -9,7 +10,7 @@ import java.util.ArrayList; import java.util.List; -public abstract class ResizeNode implements IResizeable2 { +public abstract class ResizeNode implements IResizeable2, ITreeNode { private ResizeNode defaultParent; private ResizeNode parentOverride; @@ -17,15 +18,18 @@ public abstract class ResizeNode implements IResizeable2 { private boolean requiresResize = true; @ApiStatus.Internal + @Override public List getChildren() { return children; } + @Override public ResizeNode getParent() { return parentOverride != null ? parentOverride : defaultParent; } public void dispose() { + if (getParent() != null) getParent().children.remove(this); this.defaultParent = null; this.parentOverride = null; this.children.clear(); @@ -61,9 +65,7 @@ protected void setParentOverride(ResizeNode resizeNode) { } @Override - public void initResizing(boolean onOpen) { - reset(); - } + public void initResizing(boolean onOpen) {} public void reset() {} @@ -75,6 +77,8 @@ public void onResized() { this.requiresResize = false; } + public void postFullResize() {} + public boolean requiresResize() { return this.requiresResize; } @@ -159,4 +163,7 @@ public boolean isExpanded() { public abstract boolean hasFixedSize(); public abstract String getDebugDisplayName(); + + @Override + public abstract String toString(); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java index bbb55d204..cd9f6ebf4 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ScreenResizeNode.java @@ -23,4 +23,9 @@ public Area getArea() { public String getDebugDisplayName() { return "screen '" + this.screen + "'"; } + + @Override + public String toString() { + return "ScreenResizeNode(" + this.screen + ")"; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 827c85e43..22c0c099f 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -36,6 +36,15 @@ protected DimensionSizer createDimensionSizer(GuiAxis axis) { return new DimensionSizer(this, axis); } + @Override + public void initResizing(boolean onOpen) { + setMarginPaddingApplied(false); + setResized(false); + this.childrenResized = false; + this.layoutResized = false; + super.initResizing(onOpen); + } + @Override public void reset() { this.x.reset(); @@ -79,7 +88,7 @@ public boolean isLayoutDone() { @Override public boolean canRelayout(boolean isParentLayout) { - return false; + return isParentLayout && (this.x.canRelayout() || this.y.canRelayout()); } @Override @@ -157,7 +166,7 @@ public boolean postResize() { // 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(); + List children = widget.getChildren(); // TODO cover resizer children instead? int moveChildrenX = 0, moveChildrenY = 0; Box padding = getWidget().getArea().getPadding(); @@ -317,7 +326,7 @@ public void applyPos() { } @Override - public void onResized() { + public void postFullResize() { IWidget widget = getWidget(); Area area = widget.getArea(); // update rx and ry to be relative to the widget parent not the resize node parent @@ -333,7 +342,7 @@ public void onResized() { slot.xPos = widget.getArea().x - mainArea.x + 1; slot.yPos = widget.getArea().y - mainArea.y + 1; } - super.onResized(); + super.postFullResize(); } @Override @@ -399,7 +408,7 @@ public boolean dependsOnParentX() { @Override public boolean dependsOnParentY() { - return this.x.dependsOnParent(); + return this.y.dependsOnParent(); } @Override @@ -409,7 +418,7 @@ public boolean dependsOnChildrenX() { @Override public boolean dependsOnChildrenY() { - return this.x.dependsOnChildren(); + return this.y.dependsOnChildren(); } public StandardResizer expanded() { diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java index 316bc87d5..6f92aaf00 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java @@ -6,15 +6,12 @@ public abstract class StaticResizer extends ResizeNode { private boolean childrenCalculated = false; - public StaticResizer() { - setResized(true); - setMarginPaddingApplied(true); - setChildrenResized(true); - setLayoutDone(true); - } + public StaticResizer() {} @Override - public void initResizing(boolean onOpen) {} + public void initResizing(boolean onOpen) { + setChildrenResized(false); + } @Override public boolean isXCalculated() { diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java index 1475420dd..9fbf80744 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java @@ -34,6 +34,11 @@ public void onResized() { this.widget.onResized(); } + @Override + public void postFullResize() { + this.widget.postResize(); + } + @Override public boolean isLayout() { return this.widget instanceof ILayoutWidget; @@ -59,4 +64,9 @@ public boolean postLayoutChildren() { public String getDebugDisplayName() { return "widget '" + this.widget + "' of screen '" + this.widget.getScreen() + "'"; } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.widget + ")"; + } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java b/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java index c77de8fcd..d0e0e2f15 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/Expandable.java @@ -49,6 +49,12 @@ public void onInit() { @Override public void beforeResize(boolean onOpen) { super.beforeResize(onOpen); + if (resizer().getChildren().isEmpty() || resizer().getChildren().size() > 2) + throw new IllegalStateException("Invalid Expandable children size"); + if (resizer().getChildren().size() > 1) { + resizer().getChildren().remove(1); + } + resizer().getChildren().set(0, this.expanded ? this.expandedView.resizer() : this.normalView.resizer()); this.currentChildren = Collections.singletonList(this.expanded ? this.expandedView : this.normalView); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index 59481499f..0274f2d81 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -7,11 +7,12 @@ import com.cleanroommc.modularui.utils.ReversedList; import com.cleanroommc.modularui.widget.ParentWidget; import com.cleanroommc.modularui.widget.sizer.Box; +import com.cleanroommc.modularui.widget.sizer.ExpanderStandardResizer; import java.util.List; import java.util.function.IntFunction; -public class Flow extends ParentWidget implements ILayoutWidget, IExpander { +public class Flow extends ParentWidget implements ILayoutWidget { public static Flow row() { return new Flow(GuiAxis.X); @@ -49,6 +50,7 @@ public static Flow column() { public Flow(GuiAxis axis) { this.axis = axis; + resizer(new ExpanderStandardResizer(this, axis)); sizeRel(1f, 1f); } @@ -322,11 +324,6 @@ public GuiAxis getAxis() { return axis; } - @Override - public GuiAxis getExpandAxis() { - return this.axis; - } - @Override protected String getTypeName() { return this.axis.isHorizontal() ? "Row" : "Column"; From b839d72e3bae1160bdfeb6135f8e149facd07ff8 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 28 Jan 2026 15:57:13 +0100 Subject: [PATCH 05/20] context menus finally working again --- .../modularui/overlay/DebugOverlay.java | 33 ++++---- .../cleanroommc/modularui/test/TestTile.java | 28 +++---- .../cleanroommc/modularui/utils/TreeUtil.java | 35 ++++++--- .../modularui/widget/AbstractWidget.java | 38 +++++++++- .../widget/DelegatingSingleChildWidget.java | 46 +++++------- .../modularui/widget/InternalWidgetTree.java | 6 ++ .../modularui/widget/WidgetTree.java | 25 ++++--- .../modularui/widget/sizer/Area.java | 10 ++- .../widget/sizer/DelegatingResizer.java | 12 +++ .../widget/sizer/DimensionSizer.java | 54 ++++++------- .../modularui/widget/sizer/ResizeNode.java | 75 ++++++++++++++++++- .../widget/sizer/StandardResizer.java | 5 ++ .../modularui/widget/sizer/StaticResizer.java | 1 + .../widget/sizer/WidgetResizeNode.java | 1 + .../modularui/widgets/ListWidget.java | 4 +- .../modularui/widgets/layout/Flow.java | 4 +- .../widgets/menu/ContextMenuList.java | 10 +-- 17 files changed, 258 insertions(+), 129 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/DelegatingResizer.java diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index e958e2088..fe342bfc3 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -1,5 +1,6 @@ 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; @@ -26,7 +27,7 @@ public class DebugOverlay extends CustomModularScreen { public static void register() { - //OverlayManager.register(new OverlayHandler(screen -> ModularUIConfig.guiDebugMode && screen instanceof IMuiScreen, screen -> new DebugOverlay((IMuiScreen) screen))); + 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); @@ -41,41 +42,43 @@ public DebugOverlay(IMuiScreen screen) { public @NotNull ModularPanel buildUI(ModularGuiContext context) { return new ModularPanel("debug") .fullScreenInvisible() - .child(new ContextMenuButton<>() + .child(new ContextMenuButton<>().name("ctx_mb_main") .horizontalCenter() .bottom(0) .height(12) .width(100) - .background(new Rectangle().setColor(Color.withAlpha(Color.WHITE.main, 0.2f)).setCornerRadius(4)) + .background(new Rectangle().color(Color.withAlpha(Color.WHITE.main, 0.2f)).cornerRadius(4)) .overlay(IKey.str("Debug Options")) .openUp() - .menuList(new ContextMenuList<>("debug_options") + .menuList(new ContextMenuList<>("debug_options_ctx_ml1") .maxSize(100) .widthRel(1f) - .child(new ContextMenuOption<>() - .child(new ButtonWidget<>() + .child(new ContextMenuOption<>().name("ctx_mo") + .child(new ButtonWidget<>().name("ctx_b") .invisible() .overlay(IKey.str("Print widget trees")) .onMousePressed(this::logWidgetTrees))) .child(new ContextMenuButton<>() + .name("ctx_mb_sub") .height(10) .overlay(IKey.str("Widget hover info")) .openRightUp() - .menuList(new ContextMenuList<>("hover_info") + .menuList(new ContextMenuList<>("hover_info_ctx_ml2") .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))) + .child(toggleOption(1, "Name", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showName, v -> DebugOptions.INSTANCE.showName = v))) + .child(toggleOption(2, "Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showPos, v -> DebugOptions.INSTANCE.showPos = v))) + .child(toggleOption(3, "Size", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showSize, v -> DebugOptions.INSTANCE.showSize = v))) + .child(toggleOption(4, "Rel Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showRelPos, v -> DebugOptions.INSTANCE.showRelPos = v))) + .child(toggleOption(5, "Widget Theme", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showWidgetTheme, v -> DebugOptions.INSTANCE.showWidgetTheme = v))) + .child(toggleOption(6, "Outline", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showOutline, v -> DebugOptions.INSTANCE.showOutline = v))) )))); } - public static ContextMenuOption toggleOption(String name, IBoolValue boolValue) { + public static ContextMenuOption toggleOption(int i, String name, IBoolValue boolValue) { return new ContextMenuOption<>() + .name("ctx_mo_sub" + i) .child(new ToggleButton() - .name("menu toggle " + name) + .name("menu toggle " + name + "_ctx_tb" + i) .invisible() .value(boolValue) .overlay(true, new NamedDrawableRow() diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index cee3afc7b..62f6fe33d 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -478,22 +478,22 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI .name("page 4 storage") .sizeRel(1f) .child(new Column() - .name("page 4 col, dynamic widgets") - .padding(7) - .child(new ItemSlot() - .slot(new ModularSlot(this.storageInventory0, 0) - .changeListener(((newItem, onlyAmountChanged, client, init) -> { - if (client && !onlyAmountChanged) { - dynamicSyncHandler.notifyUpdate(packet -> NetworkUtils.writeItemStack(packet, newItem)); - } - })))) - .child(new DynamicSyncedWidget<>() - .widthRel(1f) - .syncHandler(dynamicSyncHandler)) - .child(new DynamicSyncedWidget<>() + .name("page 4 col, dynamic widgets") + .padding(7) + .child(new ItemSlot() + .slot(new ModularSlot(this.storageInventory0, 0) + .changeListener(((newItem, onlyAmountChanged, client, init) -> { + if (client && !onlyAmountChanged) { + dynamicSyncHandler.notifyUpdate(packet -> NetworkUtils.writeItemStack(packet, newItem)); + } + })))) + .child(new DynamicSyncedWidget<>() + .widthRel(1f) + .syncHandler(dynamicSyncHandler)) + /*.child(new DynamicSyncedWidget<>() .widthRel(1f) .coverChildrenHeight() - .syncHandler(dynamicLinkedSyncHandler))) + .syncHandler(dynamicLinkedSyncHandler))*/) ) .addPage(createSchemaPage(guiData)))) .child(SlotGroupWidget.playerInventory(false)) diff --git a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java index b1e018608..9b4ea8299 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java +++ b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java @@ -7,6 +7,7 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.Streams; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; @@ -14,6 +15,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -41,18 +43,21 @@ public class TreeUtil { .append("X: ").append(str(node.isXCalculated())) .append(", Y: ").append(str(node.isYCalculated())) .append(", W: ").append(str(node.isWidthCalculated())) - .append(", H: ").append(str(node.isHeightCalculated())) - .append(", Can relayout: ").append(node.canRelayout(node.hasParent() && node.getParent().isLayout())); + .append(", H: ").append(str(node.isHeightCalculated())); public static final NodeInfo RESIZE_NODE_INFO_RESIZED_DETAILED = (root, node, builder) -> { - boolean self = node.isSelfFullyCalculated(node.hasParent() && node.getParent().isLayout()); - builder.append("Self resized: ").append(str(self)) - .append(", Is pos final: ").append(str(!node.canRelayout(node.hasParent() && node.getParent().isLayout()))) + builder.append("XYWH: ") + .append(str(node.isXCalculated())) + .append(str(node.isYCalculated())) + .append(str(node.isWidthCalculated())) + .append(str(node.isHeightCalculated())) .append(", Children resized: ").append(str(node.areChildrenCalculated())) - .append(", Layout done: ").append(str(node.isLayoutDone())); - if (!self) { - builder.append(", Self detail: "); + .append(", Self layout done: ").append(str(node.isLayoutDone())) + .append(", Parent layout done: ").append(str(!node.canRelayout(node.hasParent() && node.getParent().isLayout()))); + /*if (!self) { + builder.append(", Self detail: ("); RESIZE_NODE_INFO_SELF_RESIZED_DETAIL.addInfo(root, node, builder); - } + builder.append(")"); + }*/ }; public static final NodeInfo RESIZE_NODE_INFO_RESIZED_COLLAPSED = (root, node, builder) -> { if (node.isFullyCalculated(node.hasParent() && node.getParent().isLayout())) { @@ -418,11 +423,11 @@ public static > String toString(T parent, Predicate te */ public static > StringBuilder toString(StringBuilder builder, T parent, Predicate test, NodeInfo additionalInfo) { if (builder == null) builder = new StringBuilder(); - getTree(parent, parent, test, builder, additionalInfo, "", false); + getTree(parent, parent, test, builder, additionalInfo, "", false, null); return builder; } - protected static > void getTree(T root, T parent, Predicate test, StringBuilder builder, NodeInfo additionalInfo, String indent, boolean hasNextSibling) { + protected static > void getTree(T root, T parent, Predicate test, StringBuilder builder, NodeInfo additionalInfo, String indent, boolean hasNextSibling, Set visited) { if (!indent.isEmpty()) { builder.append(indent); if (TreeUtil.allowUnicode) { @@ -432,6 +437,12 @@ protected static > void getTree(T root, T parent, Predica } builder.append(' '); } + if (visited == null) visited = new ReferenceOpenHashSet<>(); + if (visited.contains(parent)) { + builder.append("CYCLING TREE FOUND (").append(parent).append(")\n"); + return; + } + visited.add(parent); builder.append(parent); if (additionalInfo != null) { builder.append(" {"); @@ -450,7 +461,7 @@ protected static > void getTree(T root, T parent, Predica } else { nextIndent += " "; } - getTree(root, child, test, builder, additionalInfo, nextIndent, i < children.size() - 1); + getTree(root, child, test, builder, additionalInfo, nextIndent, i < children.size() - 1, visited); } } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java index 08da0a5b3..36b64bc90 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.widget; +import com.cleanroommc.modularui.api.widget.IDelegatingWidget; import com.cleanroommc.modularui.api.widget.INotifyEnabled; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; @@ -31,6 +32,10 @@ public abstract class AbstractWidget implements IWidget { private final Area area = new Area(); private StandardResizer resizer; + public AbstractWidget() { + area.widget = this; + } + /** * Returns the screen of the panel of this widget is being opened in. * @@ -63,12 +68,19 @@ public boolean requiresResize() { public final void initialise(@NotNull IWidget parent, boolean late) { this.timeHovered = -1; this.timeBelowMouse = -1; + if (this.resizer == null) { + throw new IllegalStateException("Resizer must be set before the widget initializes! Affected widget: " + this); + } if (!(this instanceof ModularPanel)) { this.parent = parent; this.panel = parent.getPanel(); this.context = parent.getContext(); getArea().z(parent.getArea().z() + 1); - resizer().setDefaultParent(parent.resizer()); + if (parent instanceof AbstractWidget aw) { + this.resizer.setDefaultParent(aw.resizer); + } else { + this.resizer.setDefaultParent(parent.resizer()); + } } this.valid = true; onInitInternal(late); @@ -193,6 +205,19 @@ public Area getArea() { return area; } + /** + * Shortcut to get the area of the parent + * + * @return parent area + */ + public Area getParentArea() { + IWidget parent = getParent(); + while (parent instanceof IDelegatingWidget dw) { + parent = dw.getParent(); + } + return parent.getArea(); + } + /** * Returns if this widget is currently enabled. Disabled widgets (and all its children) are not rendered and can't be interacted with. * @@ -273,8 +298,17 @@ protected final void setContext(ModularGuiContext context) { return this.resizer; } + protected final StandardResizer rawResizer() { + return this.resizer; + } + protected void resizer(StandardResizer resizer) { - this.resizer = Objects.requireNonNull(resizer); + Objects.requireNonNull(resizer); + if (this.resizer == resizer) return; + if (isValid() && this.resizer != null) { + resizer.replacementOf(this.resizer); + } + this.resizer = resizer; } /** diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java index c877d9fe0..4eafe911b 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.widget; +import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IDelegatingWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.widget.sizer.Area; @@ -7,44 +8,28 @@ import org.jetbrains.annotations.NotNull; -import java.util.List; - public class DelegatingSingleChildWidget> extends SingleChildWidget implements IDelegatingWidget { - private boolean currentlyResizing = false; - - @Override - public void onInit() { - super.onInit(); - if (hasChildren()) getChild().resizer().relative(getParent()); - } - - @Override - public void beforeResize(boolean onOpen) { - this.currentlyResizing = true; - super.beforeResize(onOpen); - if (getDelegate() != null) getDelegate().beforeResize(onOpen); - } - @Override - public void onResized() { - super.onResized(); - if (getDelegate() != null) getDelegate().onResized(); + public void afterInit() { + super.resizer().setDefaultParent(null); // remove this widget from the resize node tree + if (hasChildren()) { + getChild().resizer().setDefaultParentIsDelegating(true); + getChild().resizer().relative(getParent()); // add the delegated widget at the place of this widget on the resize node tree + } } @Override public void postResize() { super.postResize(); - if (getDelegate() != null) getDelegate().postResize(); - this.currentlyResizing = false; 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.x = 0; + //childArea.y = 0; childArea.rx = 0; childArea.ry = 0; } @@ -62,17 +47,22 @@ public StandardResizer getFlex() { @Override public Area getArea() { - return getDelegate() != null && this.currentlyResizing ? getDelegate().getArea() : super.getArea(); + return getDelegate() != null ? getDelegate().getArea() : super.getArea(); + } + + @Override + public void transform(IViewportStack stack) { + stack.translate(super.getArea().rx, super.getArea().ry, 0); } @Override - public @NotNull List getChildren() { - return getDelegate() != null && this.currentlyResizing ? getDelegate().getChildren() : super.getChildren(); + public boolean canBeSeen(IViewportStack stack) { + return false; } @Override public boolean requiresResize() { - return super.requiresResize() || (getDelegate() != null && getDelegate().requiresResize()); + return getDelegate() != null && getDelegate().requiresResize(); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java index d192fceeb..b879e9b46 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java @@ -52,6 +52,12 @@ static T findChildAt(IWidget parent, Class type, String[] } static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEnabled, boolean drawBackground) { + // skip delegating widgets and draw the delegates directly + /*while (parent instanceof IDelegatingWidget dw) { + if (dw.getDelegate() == null || (!parent.isEnabled() && !ignoreEnabled)) return; + parent = dw.getDelegate(); + }*/ + if (!parent.isEnabled() && !ignoreEnabled) return; if (parent.requiresResize()) { WidgetTree.resizeInternal(parent.resizer(), false); diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index ae8848cca..45d26ca66 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -241,21 +241,28 @@ public static void resize(IWidget parent) { public static void resizeInternal(ResizeNode parent, boolean onOpen) { long time = System.nanoTime(); // check if updating this widget's pos and size can potentially update its parents - while (parent.getParent() != null && (parent.getParent().dependsOnChildren() || parent.isLayout())) { + while (parent.getParent() != null && (parent.getParent().dependsOnChildren() || parent.getParent().isLayout())) { parent = parent.getParent(); } // resize each widget and calculate their relative pos - if (!InternalWidgetTree.resize(parent, true, onOpen, false) && !InternalWidgetTree.resize(parent, false, onOpen, false)) { - if (MCHelper.getPlayer() != null) { - MCHelper.getPlayer().sendMessage(new TextComponentString(IKey.RED + "ModularUI: Failed to resize sub tree of " - + parent.getDebugDisplayName() + ". See log for more info.")); + try { + if (!InternalWidgetTree.resize(parent, true, onOpen, false) && !InternalWidgetTree.resize(parent, false, onOpen, false)) { + if (MCHelper.getPlayer() != null) { + MCHelper.getPlayer().sendMessage(new TextComponentString(IKey.RED + "ModularUI: Failed to resize sub tree of " + + parent.getDebugDisplayName() + ". See log for more info.")); + } + ModularUI.LOGGER.error("Failed to resize widget. Affected resize node tree:"); + print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); } - ModularUI.LOGGER.error("Failed to resize widget. Affected resize node tree:"); + // now apply the calculated pos + applyPos(parent); + postFullResize(parent); + } catch (Throwable e) { + ModularUI.LOGGER.fatal("An exception was thrown while resizing widgets. Affected node tree:"); print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); + ModularUI.LOGGER.fatal("."); } - // now apply the calculated pos - applyPos(parent); - postFullResize(parent); + if (WidgetTree.logResizeTime) { time = System.nanoTime() - time; ModularUI.LOGGER.info("Resized widget tree in {}s.", NumberFormat.formatNanos(time)); 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 d760f1dff..7a2f86591 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -3,6 +3,7 @@ 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.IWidget; import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.MathUtils; @@ -30,7 +31,8 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) { /** * relative position (in most cases the direct parent) */ - public int rx, ry; + public int rx; + public int ry; /** * the widget layer within this panel */ @@ -38,6 +40,8 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) { private final Box margin = new Box(); private final Box padding = new Box(); + public IWidget widget; + public Area() {} public Area(int x, int y, int w, int h) { @@ -57,6 +61,10 @@ public Area(Area area) { getPadding().set(area.getPadding()); } + public boolean isWidget(String s) { + return widget != null && widget.getName() != null && (widget.getName().endsWith("ctx_tb6") || widget.getName().endsWith("ctx_mo_sub6")); + } + public int x() { return this.x; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/DelegatingResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/DelegatingResizer.java new file mode 100644 index 000000000..ca765635d --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/DelegatingResizer.java @@ -0,0 +1,12 @@ +package com.cleanroommc.modularui.widget.sizer; + +import com.cleanroommc.modularui.api.widget.IWidget; + +public class DelegatingResizer extends StandardResizer { + + public DelegatingResizer(IWidget widget) { + super(widget); + } + + +} 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 33010041c..99b52cef2 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java @@ -174,23 +174,13 @@ private boolean needsSize(Unit unit) { } public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { - // is already calculated - boolean sizeCalculated; - boolean posCalculated; + boolean sizeCalculated = isSizeCalculated(); + boolean posCalculated = isPosCalculated(); + if (sizeCalculated && posCalculated) return; int p, s; - int parentSize; - boolean calcParent; - Box padding; - try { - sizeCalculated = isSizeCalculated(); - posCalculated = isPosCalculated(); - if (sizeCalculated && posCalculated) return; - parentSize = relativeTo.getArea().getSize(this.axis); - calcParent = relativeTo.isSizeCalculated(this.axis); - padding = relativeTo.getArea().getPadding(); - } catch (Throwable e) { - throw e; - } + int parentSize = relativeTo.getArea().getSize(this.axis); + boolean calcParent = relativeTo.isSizeCalculated(this.axis); + Box padding = relativeTo.getArea().getPadding(); if (sizeCalculated) { // pos not calculated // size was calculated before @@ -318,36 +308,36 @@ public void applyMarginAndPaddingToPos(IWidget parent, Area area, Area relativeT // apply self margin and parent padding if not done yet if (isMarginPaddingApplied()) return; setMarginPaddingApplied(true); - int left = area.getMargin().getStart(this.axis) + relativeTo.getPadding().getStart(this.axis); - int right = area.getMargin().getEnd(this.axis) + relativeTo.getPadding().getEnd(this.axis); - if (left > 0 && ((this.start != null && !this.start.isRelative()) || + int start = area.getMargin().getStart(this.axis) + relativeTo.getPadding().getStart(this.axis); + int end = area.getMargin().getEnd(this.axis) + relativeTo.getPadding().getEnd(this.axis); + if (start > 0 && ((this.start != null && !this.start.isRelative()) || (this.end != null && !this.end.isRelative() && (this.size == null || !this.size.isRelative())))) { - left = 0; + start = 0; } - if (right > 0 && ((this.end != null && !this.end.isRelative()) || + if (end > 0 && ((this.end != null && !this.end.isRelative()) || (this.start != null && !this.start.isRelative() && (this.size == null || !this.size.isRelative())))) { - right = 0; + end = 0; } - if (left == 0 && right == 0) return; + if (start == 0 && end == 0) return; int parentS = relativeTo.getSize(this.axis); int s = area.getSize(this.axis); int rp = area.getRelativePoint(this.axis); // relative pos - if (left > 0) { - if (right > 0) { - if (left + right + s > parentS) { + if (start > 0) { + if (end > 0) { + if (start + end + s > parentS) { // widget and margin + padding is larger than available space - area.setRelativePoint(this.axis, left); + area.setRelativePoint(this.axis, start); GuiError.throwNew(parent, GuiError.Type.SIZING, "Margin/padding is set on both sides on axis " + this.axis + ", but total size exceeds parent size."); return; } - if (right > parentS - s - rp) area.setRelativePoint(this.axis, parentS - right - s); - else if (left > rp) area.setRelativePoint(this.axis, left); + if (end > parentS - s - rp) area.setRelativePoint(this.axis, parentS - end - s); + else if (start > rp) area.setRelativePoint(this.axis, start); return; } - if (left > rp) area.setRelativePoint(this.axis, left); - } else if (right > 0) { - if (right > parentS - s - rp) area.setRelativePoint(this.axis, parentS - right - s); + if (start > rp) area.setRelativePoint(this.axis, start); + } else if (end > 0) { + if (end > parentS - s - rp) area.setRelativePoint(this.axis, parentS - end - s); } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java index d0d9fb672..c2c90a781 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -15,6 +15,7 @@ public abstract class ResizeNode implements IResizeable2, ITreeNode private ResizeNode defaultParent; private ResizeNode parentOverride; private final List children = new ArrayList<>(); + private boolean defaultParentIsDelegating = false; private boolean requiresResize = true; @ApiStatus.Internal @@ -28,6 +29,55 @@ public ResizeNode getParent() { return parentOverride != null ? parentOverride : defaultParent; } + @ApiStatus.Internal + public void replacementOf(ResizeNode node) { + if (this == node) return; + //ModularUI.LOGGER.info("Replacing resizer node {} with node {}", node, this); + // remove this node from the tree by removing itself from the parents and removing the children + if (this.defaultParent != null) this.defaultParent.children.remove(this); + if (this.parentOverride != null) this.parentOverride.children.remove(this); + for (ResizeNode n : this.children) { + if (n.parentOverride == this) { + n.setParentOverride(null); + } + if (n.defaultParent == this) { + n.setDefaultParent(null); + } + } + this.children.clear(); + + // remove the node to replace from its tree and remember the exact position + int defI = -1; + int ovrI = -1; + if (node.defaultParent != null) { + defI = node.defaultParent.children.indexOf(node); + if (defI >= 0) node.defaultParent.children.remove(defI); + } + if (node.parentOverride != null) { + ovrI = node.parentOverride.children.indexOf(node); + if (ovrI >= 0) node.parentOverride.children.remove(ovrI); + } + // take over the parent and ourselves to the new parents with the remembered position + this.defaultParent = node.defaultParent; + this.parentOverride = node.parentOverride; + if (this.parentOverride != null) { + if (ovrI < 0) throw new IllegalStateException(); + this.parentOverride.children.add(ovrI, this); + } + if (this.defaultParent != null) { + if (defI < 0) throw new IllegalStateException(); + this.defaultParent.children.add(ovrI, this); + } + // take all children and update their parent to ourselves + this.children.addAll(node.children); + for (ResizeNode n : this.children) { + if (n.parentOverride == node) n.parentOverride = this; + if (n.defaultParent == node) n.defaultParent = this; + } + // finally invalidate replaced node + node.dispose(); + } + public void dispose() { if (getParent() != null) getParent().children.remove(this); this.defaultParent = null; @@ -47,14 +97,18 @@ private boolean removeFromParent(ResizeNode parent, ResizeNode parent2, ResizeNo } public void setDefaultParent(ResizeNode resizeNode) { + //ModularUI.LOGGER.info("Set default parent of {} to {}. Current: default: {}, override: {}", this, resizeNode, this.defaultParent, this.parentOverride); + if (resizeNode == this) throw new IllegalArgumentException("Tried to set itself as default parent in " + this); if (removeFromParent(this.defaultParent, null, resizeNode)) return; this.defaultParent = resizeNode; - if (resizeNode != null) { + if (this.parentOverride == null && resizeNode != null) { resizeNode.children.add(this); } } protected void setParentOverride(ResizeNode resizeNode) { + //ModularUI.LOGGER.info("Set override parent of {} to {}. Current: default: {}, override: {}", this, resizeNode, this.defaultParent, this.parentOverride); + if (resizeNode == this) throw new IllegalArgumentException("Tried to set itself as parent override in " + this); if (removeFromParent(this.parentOverride, this.defaultParent, resizeNode)) return; this.parentOverride = resizeNode; if (this.parentOverride != null) { @@ -64,8 +118,16 @@ protected void setParentOverride(ResizeNode resizeNode) { } } + public void setDefaultParentIsDelegating(boolean defaultParentIsDelegating) { + this.defaultParentIsDelegating = defaultParentIsDelegating; + } + @Override - public void initResizing(boolean onOpen) {} + public void initResizing(boolean onOpen) { + if (this.defaultParentIsDelegating && this.parentOverride != null) { + this.defaultParent.initResizing(onOpen); + } + } public void reset() {} @@ -75,9 +137,16 @@ public void markDirty() { public void onResized() { this.requiresResize = false; + if (this.defaultParentIsDelegating && this.parentOverride != null) { + this.defaultParent.onResized(); + } } - public void postFullResize() {} + public void postFullResize() { + if (this.defaultParentIsDelegating && this.parentOverride != null) { + this.defaultParent.postFullResize(); + } + } public boolean requiresResize() { return this.requiresResize; diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 22c0c099f..76c977556 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -4,6 +4,7 @@ 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.IDelegatingWidget; import com.cleanroommc.modularui.api.widget.IPositioned; import com.cleanroommc.modularui.api.widget.IVanillaSlot; import com.cleanroommc.modularui.api.widget.IWidget; @@ -328,6 +329,10 @@ public void applyPos() { @Override public void postFullResize() { IWidget widget = getWidget(); + if (widget instanceof IDelegatingWidget dw && dw.getDelegate() != null) { + super.postFullResize(); + return; + } Area area = widget.getArea(); // update rx and ry to be relative to the widget parent not the resize node parent Area parentArea = widget.getParentArea(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java index 6f92aaf00..48500a050 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StaticResizer.java @@ -10,6 +10,7 @@ public StaticResizer() {} @Override public void initResizing(boolean onOpen) { + super.initResizing(onOpen); setChildrenResized(false); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java index 9fbf80744..e3ee4441a 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/WidgetResizeNode.java @@ -36,6 +36,7 @@ public void onResized() { @Override public void postFullResize() { + super.postFullResize(); this.widget.postResize(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java index 0f0318220..e16a056cc 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java @@ -112,9 +112,9 @@ public boolean layoutWidgets() { widget.resizer().setMarginPaddingApplied(true); this.separatorPositions.add(p); p += separatorSize; - if (isValid()) { + /*if (isValid()) { widget.resizer().applyPos(); - } + }*/ } int size = p + getArea().getPadding().getEnd(axis); getScrollData().setScrollSize(size); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index 0274f2d81..fa55869ec 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -234,10 +234,10 @@ public static boolean layoutCrossAxisListLike(IWidget parent, GuiAxis axis, Alig widget.resizer().setPosResized(other, true); widget.resizer().setMarginPaddingApplied(other, true); } - if (parent.isValid()) { + /*if (parent.isValid()) { // we changed rel pos, but we need to calculate the new absolute pos and other stuff widget.resizer().applyPos(); - } + }*/ } return true; } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java index bdddd991a..c67c006f5 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java @@ -7,16 +7,14 @@ 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; + name(name); padding(2); } @@ -26,12 +24,6 @@ public void close() { } } - @NotNull - @Override - public String getName() { - return name; - } - public boolean isSelfOrChildHovered() { if (isBelowMouse()) return true; for (IWidget option : getTypeChildren()) { From 6f3d7785df57840691e97fca7812bf5ee87912e1 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 28 Jan 2026 16:43:11 +0100 Subject: [PATCH 06/20] mroe stuff --- .../modularui/overlay/DebugOptions.java | 36 +++--- .../modularui/overlay/DebugOverlay.java | 36 ++++-- .../modularui/screen/ClientScreenHandler.java | 115 +++++++++++------- .../widget/DelegatingSingleChildWidget.java | 2 - 4 files changed, 111 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java index b4badcf98..926511077 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java @@ -1,30 +1,30 @@ package com.cleanroommc.modularui.overlay; import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.value.BoolValue; +import com.cleanroommc.modularui.value.FloatValue; +import com.cleanroommc.modularui.value.IntValue; 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 BoolValue showHovered = new BoolValue(true); + public BoolValue showPos = new BoolValue(true); + public BoolValue showSize = new BoolValue(true); + public BoolValue showWidgetTheme = new BoolValue(true); + public BoolValue showExtra = new BoolValue(true); + public BoolValue showOutline = new BoolValue(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 BoolValue showParent = new BoolValue(true); + public BoolValue showParentPos = new BoolValue(true); + public BoolValue showParentSize = new BoolValue(true); + public BoolValue showParentWidgetTheme = new BoolValue(false); + public BoolValue showParentOutline = new BoolValue(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; + public IntValue textColor = new IntValue(Color.argb(180, 40, 115, 220)); + public IntValue outlineColor = new IntValue(textColor.getIntValue()); + public IntValue cursorColor = new IntValue(Color.withAlpha(Color.GREEN.main, 0.8f)); + public FloatValue scale = new FloatValue(0.8f); } diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index fe342bfc3..b1366df46 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -14,7 +14,6 @@ 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; @@ -46,7 +45,7 @@ public DebugOverlay(IMuiScreen screen) { .horizontalCenter() .bottom(0) .height(12) - .width(100) + .width(160) .background(new Rectangle().color(Color.withAlpha(Color.WHITE.main, 0.2f)).cornerRadius(4)) .overlay(IKey.str("Debug Options")) .openUp() @@ -59,26 +58,39 @@ public DebugOverlay(IMuiScreen screen) { .overlay(IKey.str("Print widget trees")) .onMousePressed(this::logWidgetTrees))) .child(new ContextMenuButton<>() - .name("ctx_mb_sub") + .name("menu_button_hover_info") .height(10) .overlay(IKey.str("Widget hover info")) .openRightUp() - .menuList(new ContextMenuList<>("hover_info_ctx_ml2") + .menuList(new ContextMenuList<>("menu_list_hover_info") .maxSize(100) - .child(toggleOption(1, "Name", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showName, v -> DebugOptions.INSTANCE.showName = v))) - .child(toggleOption(2, "Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showPos, v -> DebugOptions.INSTANCE.showPos = v))) - .child(toggleOption(3, "Size", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showSize, v -> DebugOptions.INSTANCE.showSize = v))) - .child(toggleOption(4, "Rel Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showRelPos, v -> DebugOptions.INSTANCE.showRelPos = v))) - .child(toggleOption(5, "Widget Theme", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showWidgetTheme, v -> DebugOptions.INSTANCE.showWidgetTheme = v))) - .child(toggleOption(6, "Outline", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showOutline, v -> DebugOptions.INSTANCE.showOutline = v))) + .child(toggleOption(0, "Any", DebugOptions.INSTANCE.showHovered)) + .child(toggleOption(1, "Pos", DebugOptions.INSTANCE.showPos)) + .child(toggleOption(2, "Size", DebugOptions.INSTANCE.showSize)) + .child(toggleOption(3, "Widgettheme", DebugOptions.INSTANCE.showWidgetTheme)) + .child(toggleOption(4, "Extra info", DebugOptions.INSTANCE.showExtra)) + .child(toggleOption(5, "Outline", DebugOptions.INSTANCE.showOutline)) + )) + .child(new ContextMenuButton<>() + .name("menu_button_parent_hover_info") + .height(10) + .overlay(IKey.str("Parent widget hover info")) + .openRightUp() + .menuList(new ContextMenuList<>("menu_list_parent_hover_info") + .maxSize(100) + .child(toggleOption(10, "Any", DebugOptions.INSTANCE.showParent)) + .child(toggleOption(11, "Pos", DebugOptions.INSTANCE.showParentPos)) + .child(toggleOption(12, "Size", DebugOptions.INSTANCE.showParentSize)) + .child(toggleOption(13, "Widgettheme", DebugOptions.INSTANCE.showParentWidgetTheme)) + .child(toggleOption(14, "Outline", DebugOptions.INSTANCE.showParentOutline)) )))); } public static ContextMenuOption toggleOption(int i, String name, IBoolValue boolValue) { return new ContextMenuOption<>() - .name("ctx_mo_sub" + i) + .name("hover_info_option" + i) .child(new ToggleButton() - .name("menu toggle " + name + "_ctx_tb" + i) + .name("hover_info_toggle" + i) .invisible() .value(boolValue) .overlay(true, new NamedDrawableRow() diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index cebf7786d..d761f95b7 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -575,18 +575,21 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable ModularGuiContext context = muiScreen.getContext(); int mouseX = context.getAbsMouseX(), mouseY = context.getAbsMouseY(); int screenH = muiScreen.getScreenArea().height; - int color = Color.argb(180, 40, 115, 220); - float scale = 0.80f; + int outlineColor = DebugOptions.INSTANCE.outlineColor.getIntValue();//Color.argb(180, 40, 115, 220); + int textColor = DebugOptions.INSTANCE.textColor.getIntValue();//Color.argb(180, 40, 115, 220); + float scale = DebugOptions.INSTANCE.scale.getFloatValue(); int shift = (int) (11 * scale + 0.5f); int lineY = screenH - shift - 2; - GuiDraw.drawText("Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, scale, color, true); + GuiDraw.drawText("Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, scale, outlineColor, true); lineY -= shift; - GuiDraw.drawText("FPS: " + fpsCounter.getFps(), 5, lineY, scale, color, true); + GuiDraw.drawText("FPS: " + fpsCounter.getFps(), 5, lineY, scale, outlineColor, true); lineY -= shift; - GuiDraw.drawText("Theme ID: " + context.getTheme().getId(), 5, lineY, scale, color, true); + GuiDraw.drawText("Theme ID: " + context.getTheme().getId(), 5, lineY, scale, outlineColor, true); LocatedWidget locatedHovered = muiScreen.getPanelManager().getTopWidgetLocated(true); - if (locatedHovered != null) { - drawSegmentLine(lineY -= 4, scale, color); + boolean showHovered = DebugOptions.INSTANCE.showHovered.getBoolValue(); + boolean showParent = DebugOptions.INSTANCE.showParent.getBoolValue(); + if (locatedHovered != null && (showHovered || showParent)) { + drawSegmentLine(lineY -= 4, scale, outlineColor); lineY -= 10; IWidget hovered = locatedHovered.getElement(); @@ -597,55 +600,75 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable Area area = hovered.getArea(); IWidget parent = hovered.getParent(); - GuiDraw.drawBorderOutsideXYWH(0, 0, area.width, area.height, scale, color); - if (hovered.hasParent()) { - GuiDraw.drawBorderOutsideXYWH(-area.rx, -area.ry, parent.getArea().width, parent.getArea().height, scale, Color.withAlpha(color, 0.3f)); + if (showHovered && DebugOptions.INSTANCE.showOutline.getBoolValue()) { + GuiDraw.drawBorderOutsideXYWH(0, 0, area.width, area.height, scale, outlineColor); + } + if (hovered.hasParent() && showParent && DebugOptions.INSTANCE.showParentOutline.getBoolValue()) { + GuiDraw.drawBorderOutsideXYWH(-area.rx, -area.ry, parent.getArea().width, parent.getArea().height, scale, Color.withAlpha(outlineColor, 0.3f)); } GlStateManager.popMatrix(); locatedHovered.unapplyMatrix(context); - GuiDraw.drawText("Widget Theme: " + hovered.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 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; + if (showHovered) { + if (DebugOptions.INSTANCE.showWidgetTheme.getBoolValue()) { + GuiDraw.drawText("Widget Theme: " + hovered.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, lineY, scale, textColor, true); + lineY -= shift; + } + if (DebugOptions.INSTANCE.showSize.getBoolValue()) { + GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, scale, textColor, true); + lineY -= shift; + } + if (DebugOptions.INSTANCE.showPos.getBoolValue()) { + GuiDraw.drawText("Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, lineY, scale, textColor, true); + lineY -= shift; + } + GuiDraw.drawText("Widget: " + hovered, 5, lineY, scale, textColor, true); } - 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); - if (hovered.hasParent()) { - drawSegmentLine(lineY -= 4, scale, color); - lineY -= 10; - GuiDraw.drawText("Widget Theme: " + parent.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, lineY, scale, color, true); - lineY -= shift; + if (hovered.hasParent() && showParent) { + if (showHovered) { + drawSegmentLine(lineY -= 4, scale, textColor); + lineY -= 10; + } + if (DebugOptions.INSTANCE.showParentWidgetTheme.getBoolValue()) { + GuiDraw.drawText("Widget Theme: " + parent.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, lineY, scale, textColor, true); + lineY -= shift; + } area = parent.getArea(); - GuiDraw.drawText("Parent size: " + area.width + ", " + area.height, 5, lineY, scale, color, true); - lineY -= shift; - GuiDraw.drawText("Parent: " + parent, 5, lineY, scale, color, true); + if (DebugOptions.INSTANCE.showParentSize.getBoolValue()) { + GuiDraw.drawText("Parent size: " + area.width + ", " + area.height, 5, lineY, scale, textColor, true); + lineY -= shift; + } + if (DebugOptions.INSTANCE.showParentPos.getBoolValue()) { + GuiDraw.drawText("Parent pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, lineY, scale, textColor, true); + lineY -= shift; + } + GuiDraw.drawText("Parent: " + parent, 5, lineY, scale, outlineColor, true); } - if (hovered instanceof ItemSlot slotWidget) { - drawSegmentLine(lineY -= 4, scale, color); - lineY -= 10; - ModularSlot slot = slotWidget.getSlot(); - GuiDraw.drawText("Slot Index: " + slot.getSlotIndex(), 5, lineY, scale, color, false); - lineY -= shift; - GuiDraw.drawText("Slot Number: " + slot.slotNumber, 5, lineY, scale, color, false); - lineY -= shift; - if (slotWidget.isSynced()) { - SlotGroup slotGroup = slot.getSlotGroup(); - boolean allowShiftTransfer = slotGroup != null && slotGroup.allowShiftTransfer(); - GuiDraw.drawText("Shift-Click Priority: " + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), 5, lineY, scale, color, true); + if (showHovered && DebugOptions.INSTANCE.showExtra.getBoolValue()) { + if (hovered instanceof ItemSlot slotWidget) { + drawSegmentLine(lineY -= 4, scale, textColor); + lineY -= 10; + ModularSlot slot = slotWidget.getSlot(); + GuiDraw.drawText("Slot Index: " + slot.getSlotIndex(), 5, lineY, scale, textColor, false); + lineY -= shift; + GuiDraw.drawText("Slot Number: " + slot.slotNumber, 5, lineY, scale, textColor, false); + lineY -= shift; + if (slotWidget.isSynced()) { + SlotGroup slotGroup = slot.getSlotGroup(); + boolean allowShiftTransfer = slotGroup != null && slotGroup.allowShiftTransfer(); + GuiDraw.drawText("Shift-Click Priority: " + (allowShiftTransfer ? slotGroup.getShiftClickPriority() : "DISABLED"), 5, lineY, scale, textColor, true); + } + } else if (hovered instanceof RichTextWidget richTextWidget) { + drawSegmentLine(lineY -= 4, scale, outlineColor); + lineY -= 10; + locatedHovered.applyMatrix(context); + Object hoveredElement = richTextWidget.getHoveredElement(); + locatedHovered.unapplyMatrix(context); + GuiDraw.drawText("Hovered: " + hoveredElement, 5, lineY, scale, textColor, true); } - } else if (hovered instanceof RichTextWidget richTextWidget) { - drawSegmentLine(lineY -= 4, scale, color); - lineY -= 10; - locatedHovered.applyMatrix(context); - Object hoveredElement = richTextWidget.getHoveredElement(); - locatedHovered.unapplyMatrix(context); - GuiDraw.drawText("Hovered: " + hoveredElement, 5, lineY, scale, color, true); } } // dot at mouse pos - GuiDraw.drawRect(mouseX, mouseY, 1, 1, Color.withAlpha(Color.GREEN.main, 0.8f)); + GuiDraw.drawRect(mouseX, mouseY, 1, 1, DebugOptions.INSTANCE.cursorColor.getIntValue()); GlStateManager.color(1f, 1f, 1f, 1f); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java index 4eafe911b..a191c3146 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -28,8 +28,6 @@ public void postResize() { area.set(childArea); area.rx = childArea.rx; area.ry = childArea.ry; - //childArea.x = 0; - //childArea.y = 0; childArea.rx = 0; childArea.ry = 0; } From 27c27851a98478f902d08b62f3fdd0ab84eefd68 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 28 Jan 2026 18:59:53 +0100 Subject: [PATCH 07/20] new delegating widget, remove obsolete ContextMenuOption --- .../modularui/api/widget/IGuiElement.java | 8 ----- .../modularui/api/widget/IWidget.java | 26 ++++----------- .../modularui/overlay/DebugOverlay.java | 33 +++++++++---------- .../cleanroommc/modularui/test/TestGuis.java | 3 +- ...ChildWidget.java => DelegatingWidget.java} | 30 ++++++++++++++--- .../modularui/widget/InternalWidgetTree.java | 6 ---- .../modularui/widget/sizer/ResizeNode.java | 1 + .../modularui/widgets/TransformWidget.java | 8 ++--- .../widgets/menu/ContextMenuButton.java | 9 ++--- .../widgets/menu/ContextMenuOption.java | 31 ----------------- .../widgets/menu/IContextMenuOption.java | 9 ----- 11 files changed, 53 insertions(+), 111 deletions(-) rename src/main/java/com/cleanroommc/modularui/widget/{DelegatingSingleChildWidget.java => DelegatingWidget.java} (69%) delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java 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 8f9c64691..5a461e45b 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IGuiElement.java @@ -1,7 +1,6 @@ package com.cleanroommc.modularui.api.widget; 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; @@ -47,13 +46,6 @@ default Area getParentArea() { return getParent().getArea(); } - /** - * Draws this element - * - * @param context gui context - */ - void draw(ModularGuiContext context); - /** * Called when the mouse hovers this element. This means this element is directly below the mouse or there are widgets in between which * all allow to pass hover through. This is not called when the element is at any point below the mouse. 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 2c0d031e8..bda1991f9 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -50,21 +50,7 @@ public interface IWidget extends IGuiElement, ITreeNode { * @param context gui context * @param widgetTheme widget theme of this widget */ - void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme); - - /** - * Draws additional stuff in this widget. - * x = 0 and y = 0 is now in the top left corner of this widget. - * Do NOT override this method, it is never called. Use {@link #draw(ModularGuiContext, WidgetThemeEntry)} instead. - * - * @param context gui context - */ - @ApiStatus.NonExtendable - @Deprecated - @Override - default void draw(ModularGuiContext context) { - draw(context, getWidgetTheme(context.getTheme())); - } + default void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} /** * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetThemeEntry)} and before @@ -73,7 +59,7 @@ default void draw(ModularGuiContext context) { * @param context gui context * @param widgetTheme widget theme */ - void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme); + default void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} /** * Draws the overlay of this theme. @@ -81,7 +67,7 @@ default void draw(ModularGuiContext context) { * @param context gui context * @param widgetTheme widget theme */ - void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme); + default void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} /** * Draws foreground elements of this widget. For example tooltips. @@ -89,7 +75,7 @@ default void draw(ModularGuiContext context) { * * @param context gui context */ - void drawForeground(ModularGuiContext context); + default void drawForeground(ModularGuiContext context) {} default void transform(IViewportStack stack) { stack.translate(getArea().rx, getArea().ry, 0); @@ -106,7 +92,7 @@ default WidgetThemeEntry getWidgetTheme(ITheme theme) { /** * Called 20 times per second. */ - void onUpdate(); + default void onUpdate() {} /** * @return the area this widget occupies @@ -226,7 +212,7 @@ default boolean canHoverThrough() { /** * Marks tooltip for this widget as dirty. */ - void markTooltipDirty(); + default void markTooltipDirty() {} /** * @return the parent of this widget diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index b1366df46..e4e43b0d5 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -5,6 +5,7 @@ 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.api.widget.IWidget; import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.drawable.NamedDrawableRow; import com.cleanroommc.modularui.drawable.Rectangle; @@ -19,7 +20,6 @@ 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; @@ -52,11 +52,10 @@ public DebugOverlay(IMuiScreen screen) { .menuList(new ContextMenuList<>("debug_options_ctx_ml1") .maxSize(100) .widthRel(1f) - .child(new ContextMenuOption<>().name("ctx_mo") - .child(new ButtonWidget<>().name("ctx_b") - .invisible() - .overlay(IKey.str("Print widget trees")) - .onMousePressed(this::logWidgetTrees))) + .child(new ButtonWidget<>().name("ctx_b") + .invisible() + .overlay(IKey.str("Print widget trees")) + .onMousePressed(this::logWidgetTrees)) .child(new ContextMenuButton<>() .name("menu_button_hover_info") .height(10) @@ -86,18 +85,16 @@ public DebugOverlay(IMuiScreen screen) { )))); } - public static ContextMenuOption toggleOption(int i, String name, IBoolValue boolValue) { - return new ContextMenuOption<>() - .name("hover_info_option" + i) - .child(new ToggleButton() - .name("hover_info_toggle" + i) - .invisible() - .value(boolValue) - .overlay(true, new NamedDrawableRow() - .name(IKey.str(name)) - .drawable(CHECKMARK)) - .overlay(false, new NamedDrawableRow() - .name(IKey.str(name)))); + public static IWidget toggleOption(int i, String name, IBoolValue boolValue) { + return new ToggleButton() + .name("hover_info_toggle" + i) + .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) { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index 93a23fffa..711a56819 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -198,8 +198,7 @@ public void onInit() { animator.reset(true); animator.animate(true); return ModularPanel.defaultPanel("main").size(150) - .child(new TransformWidget() - .child(widget) + .child(new TransformWidget(widget) .transform(stack -> { double angle = Math.PI; float x = (float) (55 * Math.cos(animator.getValue() * angle)); diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java similarity index 69% rename from src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java rename to src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java index a191c3146..9df60bc21 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java @@ -8,14 +8,34 @@ import org.jetbrains.annotations.NotNull; -public class DelegatingSingleChildWidget> extends SingleChildWidget implements IDelegatingWidget { +public class DelegatingWidget extends AbstractWidget implements IDelegatingWidget { + + private IWidget delegate; + + public DelegatingWidget(IWidget delegate) { + this.delegate = delegate; + } + + protected void setDelegate(IWidget delegate) { + if (this.delegate != null) { + this.delegate.dispose(); + } + this.delegate = delegate; + if (this.delegate != null && isValid()) { + initialise(getParent(), true); + delegate.scheduleResize(); + } + onChangeDelegate(delegate); + } + + protected void onChangeDelegate(IWidget delegate) {} @Override public void afterInit() { super.resizer().setDefaultParent(null); // remove this widget from the resize node tree if (hasChildren()) { - getChild().resizer().setDefaultParentIsDelegating(true); - getChild().resizer().relative(getParent()); // add the delegated widget at the place of this widget on the resize node tree + getDelegate().resizer().setDefaultParentIsDelegating(true); + getDelegate().resizer().relative(getParent()); // add the delegated widget at the place of this widget on the resize node tree } } @@ -23,7 +43,7 @@ public void afterInit() { public void postResize() { super.postResize(); if (getDelegate() != null) { - Area childArea = getChild().getArea(); + Area childArea = getDelegate().getArea(); Area area = super.getArea(); area.set(childArea); area.rx = childArea.rx; @@ -75,6 +95,6 @@ public int getDefaultHeight() { @Override public IWidget getDelegate() { - return getChild(); + return delegate; } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java index b879e9b46..d192fceeb 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java @@ -52,12 +52,6 @@ static T findChildAt(IWidget parent, Class type, String[] } static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEnabled, boolean drawBackground) { - // skip delegating widgets and draw the delegates directly - /*while (parent instanceof IDelegatingWidget dw) { - if (dw.getDelegate() == null || (!parent.isEnabled() && !ignoreEnabled)) return; - parent = dw.getDelegate(); - }*/ - if (!parent.isEnabled() && !ignoreEnabled) return; if (parent.requiresResize()) { WidgetTree.resizeInternal(parent.resizer(), false); diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java index c2c90a781..9f50ff329 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -96,6 +96,7 @@ private boolean removeFromParent(ResizeNode parent, ResizeNode parent2, ResizeNo return false; } + @ApiStatus.Internal public void setDefaultParent(ResizeNode resizeNode) { //ModularUI.LOGGER.info("Set default parent of {} to {}. Current: default: {}, override: {}", this, resizeNode, this.defaultParent, this.parentOverride); if (resizeNode == this) throw new IllegalArgumentException("Tried to set itself as default parent in " + this); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java index 8b47f718e..d6d0901da 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/TransformWidget.java @@ -2,14 +2,14 @@ import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.widget.DelegatingSingleChildWidget; +import com.cleanroommc.modularui.widget.DelegatingWidget; import org.joml.Matrix4f; import org.joml.Vector3f; import java.util.function.Consumer; -public class TransformWidget extends DelegatingSingleChildWidget { +public class TransformWidget extends DelegatingWidget { private static final Vector3f sharedVec = new Vector3f(); @@ -17,10 +17,8 @@ public class TransformWidget extends DelegatingSingleChildWidget transform; - public TransformWidget() {} - public TransformWidget(IWidget child) { - child(child); + super(child); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java index 81cc3fad6..57b8ce2f3 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -85,8 +85,8 @@ private void initMenuList() { this.menuList = new ContextMenuList<>("no_list") .width(50) .maxSize(30) - .child(new ContextMenuOption<>() - .widthRel(50) + .child(new Widget<>() + .widthRel(1f) .height(12) .overlay(IKey.str("No options supplied"))); } @@ -131,11 +131,6 @@ public void checkClose() { } } - @Override - public void closeParent() { - closeMenu(false); - } - @Override public boolean isSelfOrChildHovered() { if (IContextMenuOption.super.isSelfOrChildHovered()) return true; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java deleted file mode 100644 index dbba2a8d4..000000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java +++ /dev/null @@ -1,31 +0,0 @@ -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.resizer().hasHeight()) { - child.resizer().height(12); - } - if (!child.resizer().hasWidth()) { - child.resizer().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 index 0c577c703..0f5c2610e 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java @@ -1,21 +1,12 @@ 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(); } From 62d8bba9a52042dc5b0f0b92ee3a56b6d21e967c Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 28 Jan 2026 21:03:39 +0100 Subject: [PATCH 08/20] panels can have their own theme --- .../cleanroommc/modularui/api/IThemeApi.java | 38 ++++++++++++++----- .../modularui/screen/ModularPanel.java | 16 ++++++++ .../modularui/screen/ModularScreen.java | 11 +++++- .../cleanroommc/modularui/theme/ThemeAPI.java | 20 ++++++++-- .../value/sync/DynamicSyncHandler.java | 1 + .../widget/AbstractParentWidget.java | 2 +- .../widget/AbstractScrollWidget.java | 4 +- .../modularui/widget/InternalWidgetTree.java | 5 ++- .../cleanroommc/modularui/widget/Widget.java | 8 ++-- .../modularui/widget/WidgetTree.java | 2 +- .../modularui/widgets/SliderWidget.java | 2 +- .../widgets/menu/ContextMenuButton.java | 3 +- .../widgets/menu/ContextMenuList.java | 2 +- .../modularui/widgets/menu/MenuPanel.java | 1 + .../modularui/widgets/slot/FluidSlot.java | 3 +- .../modularui/widgets/slot/ItemSlot.java | 2 +- .../textfield/BaseTextFieldWidget.java | 2 +- .../resources/assets/modularui/themes.json | 3 +- .../assets/modularui/themes/context_menu.json | 12 ++++++ 19 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 src/main/resources/assets/modularui/themes/context_menu.json diff --git a/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java b/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java index a8a363f5f..d20af1e90 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java +++ b/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java @@ -3,6 +3,7 @@ import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.drawable.Scrollbar; +import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.theme.SelectableTheme; import com.cleanroommc.modularui.theme.SlotTheme; @@ -70,14 +71,6 @@ 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"); @@ -175,7 +168,32 @@ default void registerTheme(ThemeBuilder themeBuilder) { * @param defaultTheme default theme if no theme was found * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} */ - ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme); + default ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme, @Nullable String fallbackTheme) { + return getThemeForScreen(owner, name, null, defaultTheme, fallbackTheme); + } + + /** + * Gets the appropriate theme for a screen. + * + * @param owner owner of the screen + * @param name name of the screen + * @param panel the name + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + ITheme getThemeForScreen(String owner, String name, @Nullable String panel, @Nullable String defaultTheme, @Nullable String fallbackTheme); + + /** + * Gets the appropriate theme for a specific panel. + * + * @param panel the panel to find a theme for + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + default ITheme getThemeForScreen(ModularPanel panel, @Nullable String defaultTheme) { + ModularScreen screen = panel.getScreen(); + return getThemeForScreen(screen.getOwner(), screen.getName(), panel.getName(), defaultTheme, screen.getThemeOverride()); + } /** * Gets the appropriate theme for a screen. @@ -185,7 +203,7 @@ default void registerTheme(ThemeBuilder themeBuilder) { * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} */ default ITheme getThemeForScreen(ModularScreen screen, @Nullable String defaultTheme) { - return getThemeForScreen(screen.getOwner(), screen.getName(), defaultTheme); + return getThemeForScreen(screen.getOwner(), screen.getName(), defaultTheme, null); } /** diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index 33cd28369..e320c313b 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -4,6 +4,7 @@ import com.cleanroommc.modularui.animation.Animator; import com.cleanroommc.modularui.api.IPanelHandler; import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; import com.cleanroommc.modularui.api.MCHelper; import com.cleanroommc.modularui.api.UpOrDown; import com.cleanroommc.modularui.api.layout.IViewport; @@ -89,6 +90,8 @@ public static ModularPanel defaultPanel(@NotNull String name, int width, int hei private Animator animator; private boolean resizable = false; + private String themeOverride; + private ITheme theme; private Runnable onCloseAction; @@ -786,6 +789,13 @@ void closeClientSubPanels() { } } + public ITheme getTheme() { + if (this.theme == null) { + this.theme = IThemeApi.get().getThemeForScreen(this, this.themeOverride); + } + return this.theme; + } + @Override public boolean isExcludeAreaInRecipeViewer() { return super.isExcludeAreaInRecipeViewer() || (!getScreen().isOverlay() && !this.invisible && !resizer().isFullSize()); @@ -819,6 +829,12 @@ public ModularPanel onCloseAction(Runnable onCloseAction) { return this; } + public ModularPanel themeOverride(String id) { + this.themeOverride = id; + this.theme = null; + return this; + } + @Deprecated @Override public ModularPanel name(String name) { diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index 2b94c3f2f..658212712 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -84,6 +84,7 @@ public static ModularScreen getCurrent() { private boolean pausesGame = false; private boolean openParentOnClose = false; + private String themeOverride; private ITheme currentTheme; private IMuiScreen screenWrapper; private boolean overlay = false; @@ -713,9 +714,14 @@ private static Class getGuiActionClass(IGuiAction action) { throw new IllegalArgumentException(); } + @Nullable + public String getThemeOverride() { + return themeOverride; + } + public ITheme getCurrentTheme() { if (this.currentTheme == null) { - useTheme(null); + useTheme(this.themeOverride); } return this.currentTheme; } @@ -728,7 +734,8 @@ public ITheme getCurrentTheme() { * @return this for builder like usage */ public ModularScreen useTheme(String theme) { - this.currentTheme = IThemeApi.get().getThemeForScreen(this, theme); + this.themeOverride = theme; + this.currentTheme = IThemeApi.get().getThemeForScreen(this, this.themeOverride); return this; } diff --git a/src/main/java/com/cleanroommc/modularui/theme/ThemeAPI.java b/src/main/java/com/cleanroommc/modularui/theme/ThemeAPI.java index 8b3d778d2..14c25ca73 100644 --- a/src/main/java/com/cleanroommc/modularui/theme/ThemeAPI.java +++ b/src/main/java/com/cleanroommc/modularui/theme/ThemeAPI.java @@ -61,19 +61,31 @@ public List getJavaDefaultThemes(String id) { } @Override - public ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme) { - String theme = getThemeIdForScreen(owner, name); + public ITheme getThemeForScreen(String owner, String name, @Nullable String panel, @Nullable String defaultTheme, @Nullable String fallbackTheme) { + String theme = getThemeIdForScreen(owner, name, panel); if (theme != null) return getTheme(theme); if (defaultTheme != null) return getTheme(defaultTheme); + if (fallbackTheme != null) return getTheme(fallbackTheme); return getTheme(ModularUIConfig.useDarkThemeByDefault ? "vanilla_dark" : "vanilla"); } - private String getThemeIdForScreen(String mod, String name) { + private String getThemeIdForScreen(String mod, String name, String panelName) { String fullName = mod + ":" + name; - String theme = this.jsonScreenThemes.get(fullName); + String fullPanelName = null; + if (panelName != null) fullPanelName = fullName + ":" + panelName; + String theme = null; + if (fullPanelName != null) { + theme = this.jsonScreenThemes.get(fullPanelName); + if (theme != null) return theme; + } + theme = this.jsonScreenThemes.get(fullName); if (theme != null) return theme; theme = this.jsonScreenThemes.get(mod); if (theme != null) return theme; + if (fullPanelName != null) { + theme = this.screenThemes.get(fullPanelName); + if (theme != null) return theme; + } theme = this.screenThemes.get(fullName); return theme != null ? theme : this.screenThemes.get(mod); } diff --git a/src/main/java/com/cleanroommc/modularui/value/sync/DynamicSyncHandler.java b/src/main/java/com/cleanroommc/modularui/value/sync/DynamicSyncHandler.java index e371a84c4..ce963e69e 100644 --- a/src/main/java/com/cleanroommc/modularui/value/sync/DynamicSyncHandler.java +++ b/src/main/java/com/cleanroommc/modularui/value/sync/DynamicSyncHandler.java @@ -21,6 +21,7 @@ * The widget provider as ran on both sides. Inside the provider sync handlers can be registered with variants of * {@link ISyncRegistrar#getOrCreateSyncHandler(String, int, Class, Supplier)}. */ +@ApiStatus.Obsolete public class DynamicSyncHandler extends SyncHandler implements IDynamicSyncNotifiable { private IWidgetProvider widgetProvider; diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java index 1e2ecf821..36af16143 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractParentWidget.java @@ -56,7 +56,7 @@ public boolean canHover() { IDrawable.isVisible(getHoverBackground()) || IDrawable.isVisible(getHoverOverlay()) || getTooltip() != null) return true; - WidgetThemeEntry widgetTheme = getWidgetTheme(getContext().getTheme()); + WidgetThemeEntry widgetTheme = getWidgetTheme(getPanel().getTheme()); if (getBackground() == null && IDrawable.isVisible(widgetTheme.getTheme().getBackground())) return true; return getHoverBackground() == null && IDrawable.isVisible(widgetTheme.getHoverTheme().getBackground()); } diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java index 479388bc3..42bfe2377 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractScrollWidget.java @@ -67,7 +67,7 @@ public void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, @Override public void beforeResize(boolean onOpen) { super.beforeResize(onOpen); - this.scroll.applyWidgetTheme(getContext().getTheme().getScrollbarTheme().getTheme(isHovering())); + this.scroll.applyWidgetTheme(getPanel().getTheme().getScrollbarTheme().getTheme(isHovering())); if (onOpen) checkScrollbarActive(true); getScrollArea().getScrollPadding().scrollPaddingAll(0); applyAdditionalOffset(this.scroll.getScrollX()); @@ -137,7 +137,7 @@ public void preDraw(ModularGuiContext context, boolean transformed) { public void postDraw(ModularGuiContext context, boolean transformed) { if (!transformed) { Stencil.remove(); - WidgetThemeEntry scrollbarTheme = context.getTheme().getScrollbarTheme(); + WidgetThemeEntry scrollbarTheme = getPanel().getTheme().getScrollbarTheme(); this.scroll.drawScrollbar(context, scrollbarTheme.getTheme(isHovering()), scrollbarTheme.getTheme().getBackground()); } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java index d192fceeb..cdc1b300e 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java @@ -57,6 +57,7 @@ static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEn WidgetTree.resizeInternal(parent.resizer(), false); } + float alpha = parent.getPanel().getAlpha(); IViewport viewport = parent instanceof IViewport ? (IViewport) parent : null; @@ -73,7 +74,7 @@ static void drawTree(IWidget parent, ModularGuiContext context, boolean ignoreEn if (canBeSeen) { // draw widget GlStateManager.color(1f, 1f, 1f, alpha); - WidgetThemeEntry widgetTheme = parent.getWidgetTheme(context.getTheme()); + WidgetThemeEntry widgetTheme = parent.getWidgetTheme(parent.getPanel().getTheme()); if (drawBackground) parent.drawBackground(context, widgetTheme); parent.draw(context, widgetTheme); parent.drawOverlay(context, widgetTheme); @@ -160,7 +161,7 @@ static void drawBackground(IWidget parent, ModularGuiContext context, boolean ig // draw widget GlStateManager.color(1f, 1f, 1f, alpha); - WidgetThemeEntry widgetTheme = parent.getWidgetTheme(context.getTheme()); + WidgetThemeEntry widgetTheme = parent.getWidgetTheme(parent.getPanel().getTheme()); parent.drawBackground(context, widgetTheme); GlStateManager.popMatrix(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index fa4a0ed00..8974e35f5 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -151,7 +151,7 @@ public void drawBackground(ModularGuiContext context, WidgetThemeEntry widget if (this.shadow != null) { this.shadow.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); } - IDrawable bg = getCurrentBackground(context.getTheme(), widgetTheme); + IDrawable bg = getCurrentBackground(getPanel().getTheme(), widgetTheme); if (bg != null) { bg.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); } @@ -179,7 +179,7 @@ public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} */ @Override public void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) { - IDrawable bg = getCurrentOverlay(context.getTheme(), widgetTheme); + IDrawable bg = getCurrentOverlay(getPanel().getTheme(), widgetTheme); if (bg != null) { bg.drawAtZeroPadded(context, getArea(), getActiveWidgetTheme(widgetTheme, isHovering())); } @@ -574,12 +574,12 @@ public void estimateSize(Bounds bounds) { @Override public int getDefaultWidth() { - return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultWidth() : 18; + return isValid() ? getWidgetTheme(getPanel().getTheme()).getTheme().getDefaultWidth() : 18; } @Override public int getDefaultHeight() { - return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultHeight() : 18; + return isValid() ? getWidgetTheme(getPanel().getTheme()).getTheme().getDefaultHeight() : 18; } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index 45d26ca66..b9feebd47 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -45,7 +45,7 @@ public class WidgetTree extends TreeUtil { .append(widget.getArea().height); public static final WidgetInfo INFO_ENABLED = (root, widget, builder) -> builder.append("Enabled: ").append(widget.isEnabled()); public static final WidgetInfo INFO_WIDGET_THEME = (root, widget, builder) -> builder.append("Widget theme: ") - .append(widget.getWidgetTheme(widget.getContext().getTheme()).getKey().getFullName()); + .append(widget.getWidgetTheme(widget.getPanel().getTheme()).getKey().getFullName()); private WidgetTree() {} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java index 9ee469d4d..ab7ff1f77 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SliderWidget.java @@ -94,7 +94,7 @@ public void drawBackground(ModularGuiContext context, WidgetThemeEntry widget @Override public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { if (this.handleDrawable != null) { - this.handleDrawable.draw(context, this.sliderArea, context.getTheme().getButtonTheme().getTheme()); + this.handleDrawable.draw(context, this.sliderArea, getPanel().getTheme().getButtonTheme().getTheme()); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java index 57b8ce2f3..83fee8396 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -2,7 +2,6 @@ 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; @@ -140,7 +139,7 @@ public boolean isSelfOrChildHovered() { @Override protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { - return isValid() && getPanel() instanceof MenuPanel ? theme.getWidgetTheme(IThemeApi.MENU_OPTION) : theme.getButtonTheme(); + return theme.getButtonTheme(); } public W menuList(ContextMenuList menuList) { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java index c67c006f5..77272d375 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java @@ -53,7 +53,7 @@ protected void onChildAdd(IWidget child) { @Override protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { - return theme.getWidgetTheme(IThemeApi.CONTEXT_MENU); + return theme.getWidgetTheme(IThemeApi.PANEL); } public void checkClose() { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java index d93d9852f..392b1dbdb 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java @@ -12,6 +12,7 @@ public MenuPanel(ContextMenuList menuList) { super(menuList.getName()); fullScreenInvisible(); child(menuList); + themeOverride("modularui.context_menu"); } public void openSubMenu(ContextMenuList menuList) { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/slot/FluidSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/slot/FluidSlot.java index 71d00e7cf..2f8056aad 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/slot/FluidSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/slot/FluidSlot.java @@ -20,7 +20,6 @@ import com.cleanroommc.modularui.value.sync.FluidSlotSyncHandler; import com.cleanroommc.modularui.widgets.AbstractFluidDisplayWidget; -import net.minecraft.client.renderer.GlStateManager; import net.minecraft.item.ItemStack; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTank; @@ -144,7 +143,7 @@ public WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { } public int getSlotHoverColor() { - WidgetThemeEntry theme = getWidgetTheme(getContext().getTheme(), SlotTheme.class); + WidgetThemeEntry theme = getWidgetTheme(getPanel().getTheme(), SlotTheme.class); return theme.getTheme().getSlotHoverColor(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java b/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java index 426ca58c4..2aed4373a 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/slot/ItemSlot.java @@ -135,7 +135,7 @@ public WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { } public int getSlotHoverColor() { - WidgetThemeEntry theme = getWidgetTheme(getContext().getTheme(), SlotTheme.class); + WidgetThemeEntry theme = getWidgetTheme(getPanel().getTheme(), SlotTheme.class); return theme.getTheme().getSlotHoverColor(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java index 5d217adca..576eb37ed 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/textfield/BaseTextFieldWidget.java @@ -105,7 +105,7 @@ public void onUpdate() { @Override public void preDraw(ModularGuiContext context, boolean transformed) { if (transformed) { - WidgetThemeEntry entry = getWidgetTheme(context.getTheme(), TextFieldTheme.class); + WidgetThemeEntry entry = getWidgetTheme(getPanel().getTheme(), TextFieldTheme.class); TextFieldTheme widgetTheme = entry.getTheme(); this.renderer.setColor(this.textColor != null ? this.textColor : widgetTheme.getTextColor()); this.renderer.setCursorColor(this.textColor != null ? this.textColor : widgetTheme.getTextColor()); diff --git a/src/main/resources/assets/modularui/themes.json b/src/main/resources/assets/modularui/themes.json index 33518a5a9..568314f6b 100644 --- a/src/main/resources/assets/modularui/themes.json +++ b/src/main/resources/assets/modularui/themes.json @@ -1,7 +1,8 @@ { "vanilla": "modularui:vanilla", "vanilla_dark": "modularui:vanilla_dark", + "modularui.context_menu": "modularui:context_menu", "screens": { "modularui:test": "vanilla_dark" } -} \ No newline at end of file +} diff --git a/src/main/resources/assets/modularui/themes/context_menu.json b/src/main/resources/assets/modularui/themes/context_menu.json new file mode 100644 index 000000000..83034b35b --- /dev/null +++ b/src/main/resources/assets/modularui/themes/context_menu.json @@ -0,0 +1,12 @@ +{ + "parent": "DEFAULT", + "panel": { + "background": { + "type": "texture", + "id": "menu" + } + }, + "button": { + "background": "empty" + } +} From 542437c27c42d9ec4dec40c36d6b3d052f37ad1b Mon Sep 17 00:00:00 2001 From: brachy84 Date: Wed, 28 Jan 2026 21:33:21 +0100 Subject: [PATCH 09/20] improve context menu theme --- .../modularui/overlay/DebugOverlay.java | 2 ++ .../assets/modularui/themes/context_menu.json | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index e4e43b0d5..e2424f68d 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -63,6 +63,7 @@ public DebugOverlay(IMuiScreen screen) { .openRightUp() .menuList(new ContextMenuList<>("menu_list_hover_info") .maxSize(100) + .width(100) .child(toggleOption(0, "Any", DebugOptions.INSTANCE.showHovered)) .child(toggleOption(1, "Pos", DebugOptions.INSTANCE.showPos)) .child(toggleOption(2, "Size", DebugOptions.INSTANCE.showSize)) @@ -77,6 +78,7 @@ public DebugOverlay(IMuiScreen screen) { .openRightUp() .menuList(new ContextMenuList<>("menu_list_parent_hover_info") .maxSize(100) + .width(100) .child(toggleOption(10, "Any", DebugOptions.INSTANCE.showParent)) .child(toggleOption(11, "Pos", DebugOptions.INSTANCE.showParentPos)) .child(toggleOption(12, "Size", DebugOptions.INSTANCE.showParentSize)) diff --git a/src/main/resources/assets/modularui/themes/context_menu.json b/src/main/resources/assets/modularui/themes/context_menu.json index 83034b35b..b72b97f45 100644 --- a/src/main/resources/assets/modularui/themes/context_menu.json +++ b/src/main/resources/assets/modularui/themes/context_menu.json @@ -7,6 +7,21 @@ } }, "button": { - "background": "empty" + "background": "empty", + "textColor": "#404040", + "iconColor": "#404040", + "textShadow": false + }, + "toggleButton": { + "background": "empty", + "color": "#FFFFFF", + "textColor": "#404040", + "iconColor": "#404040", + "textShadow": false, + "selectedBackground": "empty", + "selectedColor": "#BBBBBB", + "selectedIconColor": "#404040", + "selectedTextColor": "#404040", + "selectedTextShadow": false } } From b816281f0d6d4034db0ecbb41f55731b4a39f7b6 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 30 Jan 2026 12:15:28 +0100 Subject: [PATCH 10/20] deprecate single arg ModularScreen ctor --- .../cleanroommc/modularui/api/IGuiHolder.java | 4 ++- .../cleanroommc/modularui/config/Config.java | 3 +- .../modularui/screen/ModularScreen.java | 7 ++-- .../modularui/test/EventHandler.java | 2 ++ .../modularui/test/OverlayTest.java | 34 ++++++++++--------- .../cleanroommc/modularui/test/TestGuis.java | 2 +- .../com/cleanroommc/modularui/SizerTest.java | 2 +- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/api/IGuiHolder.java b/src/main/java/com/cleanroommc/modularui/api/IGuiHolder.java index b189e6ec4..79570413e 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IGuiHolder.java +++ b/src/main/java/com/cleanroommc/modularui/api/IGuiHolder.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.api; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.factory.GuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; @@ -24,7 +25,8 @@ public interface IGuiHolder { */ @SideOnly(Side.CLIENT) default ModularScreen createScreen(T data, ModularPanel mainPanel) { - return new ModularScreen(mainPanel); + ModularUI.LOGGER.warn("IGuiHolder.createScreen() should be overridden to pass you own mod id to the ModularScreen. In future versions this method must be overridden or else it will crash!"); + return new ModularScreen(ModularUI.ID, mainPanel); } /** diff --git a/src/main/java/com/cleanroommc/modularui/config/Config.java b/src/main/java/com/cleanroommc/modularui/config/Config.java index a7ad29954..2b99842b3 100644 --- a/src/main/java/com/cleanroommc/modularui/config/Config.java +++ b/src/main/java/com/cleanroommc/modularui/config/Config.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.config; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.network.NetworkHandler; import com.cleanroommc.modularui.network.packets.SyncConfig; import com.cleanroommc.modularui.screen.ModularScreen; @@ -80,7 +81,7 @@ private boolean determineSynced() { } public ModularScreen createScreen() { - return new ModularScreen(new ConfigPanel(this)); + return new ModularScreen(ModularUI.ID, new ConfigPanel(this)); } public JsonObject serialize() { diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index 658212712..021f12de9 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -90,12 +90,13 @@ public static ModularScreen getCurrent() { private boolean overlay = false; /** - * Creates a new screen with a ModularUI as its owner and a given {@link ModularPanel}. - * - * @param mainPanel main panel of this screen + * @deprecated use the other constructor */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public ModularScreen(@NotNull ModularPanel mainPanel) { this(ModularUI.ID, mainPanel); + ModularUI.LOGGER.error("The single arg ModularScreen constructor should not be used. Use the other one and pass in your mod id."); } /** diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index f31c68b2a..4e238bd6a 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.IThemeApi; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.drawable.IIcon; @@ -57,6 +58,7 @@ public void onItemUse(PlayerInteractEvent.RightClickItem event) { if (itemStack.getItem() == Items.DIAMOND) { //ClientGUI.open(new TestGuis()); ClientGUI.open(new ModularScreen( + ModularUI.ID, new ModularPanel("test") .size(150) .child(new ButtonWidget<>() diff --git a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java index 7adcbb56e..870b03264 100644 --- a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java +++ b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.overlay.OverlayHandler; @@ -28,22 +29,23 @@ public static void init() { TextWidget title = new TextWidget<>(IKey.str("ModularUI")); int[] colors = {Color.WHITE.main, Color.AMBER.main, Color.BLUE.main, Color.GREEN.main, Color.DEEP_PURPLE.main, Color.RED.main}; AtomicInteger k = new AtomicInteger(); - return new ModularScreen(ModularPanel.defaultPanel("overlay") - .fullScreenInvisible() - .child(title.scale(5f) - .shadow(true) - .color(colors[k.get()]) - .leftRel(0.5f).topRel(0.07f)) - .child(new ButtonWidget<>() // test button overlapping - .topRel(0.25f, 59, 0f) - .leftRelOffset(0.5f, 91) - .size(44) - .overlay(IKey.str("Fun Button")) - .onMousePressed(mouseButton -> { - k.set((k.get() + 1) % colors.length); - title.color(colors[k.get()]); - return true; - }))); + return new ModularScreen(ModularUI.ID, + ModularPanel.defaultPanel("overlay") + .fullScreenInvisible() + .child(title.scale(5f) + .shadow(true) + .color(colors[k.get()]) + .leftRel(0.5f).topRel(0.07f)) + .child(new ButtonWidget<>() // test button overlapping + .topRel(0.25f, 59, 0f) + .leftRelOffset(0.5f, 91) + .size(44) + .overlay(IKey.str("Fun Button")) + .onMousePressed(mouseButton -> { + k.set((k.get() + 1) % colors.length); + title.color(colors[k.get()]); + return true; + }))); })); OverlayManager.register(new OverlayHandler(screen -> screen instanceof GuiContainer, screen -> { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index 711a56819..5db39aa54 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -126,7 +126,7 @@ public class TestGuis extends CustomModularScreen { .leftRel(1f) .heightRel(1f)); } - ClientGUI.open(new ModularScreen(panel).openParentOnClose(true)); + ClientGUI.open(new ModularScreen(ModularUI.ID, panel).openParentOnClose(true)); } catch (IllegalAccessException | InvocationTargetException e) { ModularUI.LOGGER.throwing(e); } diff --git a/src/test/java/com/cleanroommc/modularui/SizerTest.java b/src/test/java/com/cleanroommc/modularui/SizerTest.java index 8ce60f8c5..342692b8c 100644 --- a/src/test/java/com/cleanroommc/modularui/SizerTest.java +++ b/src/test/java/com/cleanroommc/modularui/SizerTest.java @@ -41,7 +41,7 @@ void assertArea(Area area, int x, int y, int w, int h) { } ModularScreen testPanel(ModularPanel panel) { - ModularScreen screen = new ModularScreen(panel); + ModularScreen screen = new ModularScreen(ModularUI.ID, panel); screen.getContext().setSettings(new UISettings()); ScreenWrapper wrapper = new ScreenWrapper(null, screen); screen.construct(wrapper); From 83d8c8e3e147035cb70132a40734914c05e1e570 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 30 Jan 2026 13:47:21 +0100 Subject: [PATCH 11/20] open overlays via new event & deprecate OverlayManager & OverlayHandler --- .../cleanroommc/modularui/ClientProxy.java | 6 -- .../modularui/overlay/DebugOverlay.java | 5 -- .../modularui/overlay/OverlayHandler.java | 3 +- .../modularui/overlay/OverlayManager.java | 23 +----- .../modularui/overlay/OverlayStack.java | 34 ++++++++- .../modularui/screen/ClientScreenHandler.java | 3 +- .../modularui/screen/OpenScreenEvent.java | 41 ++++++++++ .../modularui/test/EventHandler.java | 67 +++++++++++++++++ .../modularui/test/OverlayTest.java | 74 ------------------- 9 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/screen/OpenScreenEvent.java delete mode 100644 src/main/java/com/cleanroommc/modularui/test/OverlayTest.java diff --git a/src/main/java/com/cleanroommc/modularui/ClientProxy.java b/src/main/java/com/cleanroommc/modularui/ClientProxy.java index 0088a1ad2..93a2579b6 100644 --- a/src/main/java/com/cleanroommc/modularui/ClientProxy.java +++ b/src/main/java/com/cleanroommc/modularui/ClientProxy.java @@ -8,10 +8,8 @@ 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; import com.cleanroommc.modularui.test.TestItem; import com.cleanroommc.modularui.theme.ThemeManager; import com.cleanroommc.modularui.theme.ThemeReloadCommand; @@ -71,10 +69,6 @@ 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(); - } DrawableSerialization.init(); RenderingRegistry.registerEntityRenderingHandler(HoloScreenEntity.class, ScreenEntityRender::new); diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index e2424f68d..e00bbd995 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -1,6 +1,5 @@ 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; @@ -25,10 +24,6 @@ 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; diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java index 0b988e740..bb9769ac0 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayHandler.java @@ -10,7 +10,8 @@ import java.util.function.Function; import java.util.function.Predicate; -@ApiStatus.Experimental +@Deprecated +@ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public class OverlayHandler implements Comparable { private final Predicate test; diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java index fed88d840..62aca1a8e 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayManager.java @@ -1,17 +1,12 @@ package com.cleanroommc.modularui.overlay; -import com.cleanroommc.modularui.screen.ModularScreen; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; - import org.jetbrains.annotations.ApiStatus; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -@ApiStatus.Experimental +@Deprecated +@ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public class OverlayManager { public static final List overlays = new ArrayList<>(); @@ -22,18 +17,4 @@ public static void register(OverlayHandler handler) { overlays.sort(OverlayHandler::compareTo); } } - - public static void onGuiOpen(GuiScreen newScreen) { - if (newScreen != Minecraft.getMinecraft().currentScreen) { - OverlayStack.closeAll(); - if (newScreen == null) return; - for (OverlayHandler handler : overlays) { - if (handler.isValidFor(newScreen)) { - ModularScreen overlay = Objects.requireNonNull(handler.createOverlay(newScreen), "Overlays must not be null!"); - overlay.constructOverlay(newScreen); - OverlayStack.open(overlay); - } - } - } - } } diff --git a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java index 2c62c7df8..a25d9bd33 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/OverlayStack.java @@ -1,20 +1,26 @@ package com.cleanroommc.modularui.overlay; +import com.cleanroommc.modularui.ModularUIConfig; +import com.cleanroommc.modularui.api.IMuiScreen; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.OpenScreenEvent; +import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.renderer.GlStateManager; +import net.minecraftforge.common.MinecraftForge; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; -@ApiStatus.Experimental +@ApiStatus.Internal public class OverlayStack { private static final List overlay = new ArrayList<>(); @@ -109,4 +115,30 @@ public static IWidget getHoveredElement() { public static boolean isHoveringOverlay() { return getHoveredElement() != null; } + + public static void onGuiOpen(GuiScreen newScreen) { + closeAll(); + if (newScreen != null) { + // backwards compat + for (OverlayHandler handler : OverlayManager.overlays) { + if (handler.isValidFor(newScreen)) { + ModularScreen overlay = Objects.requireNonNull(handler.createOverlay(newScreen), "Overlays must not be null!"); + overlay.constructOverlay(newScreen); + OverlayStack.open(overlay); + } + } + + OpenScreenEvent event = new OpenScreenEvent(newScreen); + MinecraftForge.EVENT_BUS.post(event); + for (ModularScreen overlay : event.getOverlays()) { + overlay.constructOverlay(newScreen); + open(overlay); + } + if (ModularUIConfig.guiDebugMode && newScreen instanceof IMuiScreen muiScreen) { + ModularScreen overlay = new DebugOverlay(muiScreen); + overlay.constructOverlay(newScreen); + open(overlay); + } + } + } } diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index d761f95b7..9949c5152 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -16,7 +16,6 @@ import com.cleanroommc.modularui.integration.jei.ModularUIJeiPlugin; import com.cleanroommc.modularui.network.ModularNetwork; 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; import com.cleanroommc.modularui.screen.viewport.LocatedWidget; @@ -225,7 +224,7 @@ private static void onGuiChanged(GuiScreen oldScreen, GuiScreen newScreen) { ModularNetwork.CLIENT.closeAll(); } - OverlayManager.onGuiOpen(newScreen); + OverlayStack.onGuiOpen(newScreen); } private static void invalidateCurrentScreen() { diff --git a/src/main/java/com/cleanroommc/modularui/screen/OpenScreenEvent.java b/src/main/java/com/cleanroommc/modularui/screen/OpenScreenEvent.java new file mode 100644 index 000000000..dd633bc98 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/screen/OpenScreenEvent.java @@ -0,0 +1,41 @@ +package com.cleanroommc.modularui.screen; + +import com.cleanroommc.modularui.api.IMuiScreen; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.common.eventhandler.Event; + +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class OpenScreenEvent extends Event { + + private final GuiScreen screen; + private final List overlays = new ArrayList<>(); + + public OpenScreenEvent(GuiScreen screen) { + this.screen = screen; + } + + public GuiScreen getScreen() { + return screen; + } + + public boolean isModularScreen() { + return screen instanceof IMuiScreen; + } + + public @Nullable ModularScreen getModularScreen() { + return screen instanceof IMuiScreen muiScreen ? muiScreen.getScreen() : null; + } + + public List getOverlays() { + return overlays; + } + + public void addOverlay(ModularScreen screen) { + this.overlays.add(screen); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index 4e238bd6a..120edfa4b 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -1,6 +1,7 @@ package com.cleanroommc.modularui.test; import com.cleanroommc.modularui.ModularUI; +import com.cleanroommc.modularui.ModularUIConfig; import com.cleanroommc.modularui.api.IThemeApi; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.drawable.IIcon; @@ -9,10 +10,13 @@ import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.factory.ClientGUI; import com.cleanroommc.modularui.holoui.HoloUI; +import com.cleanroommc.modularui.screen.CustomModularScreen; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.screen.OpenScreenEvent; import com.cleanroommc.modularui.screen.RichTooltipEvent; import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.ReloadThemeEvent; import com.cleanroommc.modularui.theme.SelectableTheme; import com.cleanroommc.modularui.theme.ThemeBuilder; @@ -20,13 +24,20 @@ import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.utils.Platform; import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.TextWidget; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.init.Items; import net.minecraft.item.ItemStack; import net.minecraft.util.text.TextFormatting; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.atomic.AtomicInteger; + public class EventHandler { public static boolean enabledRichTooltipEventTest = false; @@ -94,4 +105,60 @@ public void onRichTooltip(RichTooltipEvent.Pre event) { public void onThemeReload(ReloadThemeEvent.Pre event) { IThemeApi.get().registerTheme(testTheme); } + + @SubscribeEvent + public void onOpenScreen(OpenScreenEvent event) { + if (ModularUIConfig.enableTestOverlays) { + if (event.getScreen() instanceof GuiMainMenu gui) { + event.addOverlay(getMainMenuOverlayTest(gui)); + } else if (event.getScreen() instanceof GuiContainer gui) { + event.addOverlay(getContainerOverlayTest(gui)); + } + } + } + + private ModularScreen getMainMenuOverlayTest(GuiMainMenu gui) { + TextWidget title = new TextWidget<>(IKey.str("ModularUI")); + int[] colors = {Color.WHITE.main, Color.AMBER.main, Color.BLUE.main, Color.GREEN.main, Color.DEEP_PURPLE.main, Color.RED.main}; + AtomicInteger k = new AtomicInteger(); + return new ModularScreen(ModularUI.ID, + ModularPanel.defaultPanel("overlay") + .fullScreenInvisible() + .child(title.scale(5f) + .shadow(true) + .color(colors[k.get()]) + .leftRel(0.5f).topRel(0.07f)) + .child(new ButtonWidget<>() // test button overlapping + .topRel(0.25f, 59, 0f) + .leftRelOffset(0.5f, 91) + .size(44) + .overlay(IKey.str("Fun Button")) + .onMousePressed(mouseButton -> { + k.set((k.get() + 1) % colors.length); + title.color(colors[k.get()]); + return true; + }))); + } + + private ModularScreen getContainerOverlayTest(GuiContainer gui) { + return new CustomModularScreen() { + + @Override + public @NotNull ModularPanel buildUI(ModularGuiContext context) { + return ModularPanel.defaultPanel("watermark_overlay", gui.getXSize(), gui.getYSize()) + .pos(gui.getGuiLeft(), gui.getGuiTop()) + .invisible() + .child(GuiTextures.MUI_LOGO.asIcon().asWidget() + .top(5).right(5) + .size(18)); + } + + @Override + public void onResize(int width, int height) { + getMainPanel().pos(gui.getGuiLeft(), gui.getGuiTop()) + .size(gui.getXSize(), gui.getYSize()); + super.onResize(width, height); + } + }; + } } diff --git a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java b/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java deleted file mode 100644 index 870b03264..000000000 --- a/src/main/java/com/cleanroommc/modularui/test/OverlayTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.cleanroommc.modularui.test; - -import com.cleanroommc.modularui.ModularUI; -import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.drawable.GuiTextures; -import com.cleanroommc.modularui.overlay.OverlayHandler; -import com.cleanroommc.modularui.overlay.OverlayManager; -import com.cleanroommc.modularui.screen.CustomModularScreen; -import com.cleanroommc.modularui.screen.ModularPanel; -import com.cleanroommc.modularui.screen.ModularScreen; -import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; -import com.cleanroommc.modularui.utils.Color; -import com.cleanroommc.modularui.widgets.ButtonWidget; -import com.cleanroommc.modularui.widgets.TextWidget; - -import net.minecraft.client.gui.GuiMainMenu; -import net.minecraft.client.gui.inventory.GuiContainer; - -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.atomic.AtomicInteger; - -public class OverlayTest { - - public static void init() { - - OverlayManager.register(new OverlayHandler(screen -> screen instanceof GuiMainMenu, screen -> { - GuiMainMenu gui = (GuiMainMenu) screen; - TextWidget title = new TextWidget<>(IKey.str("ModularUI")); - int[] colors = {Color.WHITE.main, Color.AMBER.main, Color.BLUE.main, Color.GREEN.main, Color.DEEP_PURPLE.main, Color.RED.main}; - AtomicInteger k = new AtomicInteger(); - return new ModularScreen(ModularUI.ID, - ModularPanel.defaultPanel("overlay") - .fullScreenInvisible() - .child(title.scale(5f) - .shadow(true) - .color(colors[k.get()]) - .leftRel(0.5f).topRel(0.07f)) - .child(new ButtonWidget<>() // test button overlapping - .topRel(0.25f, 59, 0f) - .leftRelOffset(0.5f, 91) - .size(44) - .overlay(IKey.str("Fun Button")) - .onMousePressed(mouseButton -> { - k.set((k.get() + 1) % colors.length); - title.color(colors[k.get()]); - return true; - }))); - })); - - OverlayManager.register(new OverlayHandler(screen -> screen instanceof GuiContainer, screen -> { - GuiContainer gui = (GuiContainer) screen; - return new CustomModularScreen() { - - @Override - public @NotNull ModularPanel buildUI(ModularGuiContext context) { - return ModularPanel.defaultPanel("watermark_overlay", gui.getXSize(), gui.getYSize()) - .pos(gui.getGuiLeft(), gui.getGuiTop()) - .invisible() - .child(GuiTextures.MUI_LOGO.asIcon().asWidget() - .top(5).right(5) - .size(18)); - } - - @Override - public void onResize(int width, int height) { - getMainPanel().pos(gui.getGuiLeft(), gui.getGuiTop()) - .size(gui.getXSize(), gui.getYSize()); - super.onResize(width, height); - } - }; - })); - } -} From 9fe6b266f0ce9a2f39ff48107cfc7d60afdbfbb3 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 30 Jan 2026 14:05:45 +0100 Subject: [PATCH 12/20] clean up --- .../modularui/api/layout/IResizeable.java | 16 +- .../modularui/api/layout/IResizeable2.java | 120 ---- .../modularui/widget/sizer/Area.java | 7 +- ...ndardResizer.java => ExpanderResizer.java} | 4 +- .../modularui/widget/sizer/Flex.java | 648 ------------------ .../modularui/widget/sizer/IUnResizeable.java | 60 -- .../modularui/widget/sizer/ResizeNode.java | 4 +- .../widget/sizer/StandardResizer.java | 4 +- .../modularui/widgets/layout/Flow.java | 4 +- 9 files changed, 14 insertions(+), 853 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java rename src/main/java/com/cleanroommc/modularui/widget/sizer/{ExpanderStandardResizer.java => ExpanderResizer.java} (71%) delete mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java 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 5b18bdd14..5313f309f 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java @@ -1,8 +1,6 @@ package com.cleanroommc.modularui.api.layout; import com.cleanroommc.modularui.api.GuiAxis; -import com.cleanroommc.modularui.api.widget.IGuiElement; -import com.cleanroommc.modularui.widget.sizer.Area; /** * An interface that handles resizing of widgets. @@ -13,32 +11,28 @@ public interface IResizeable extends IResizeParent { /** * Called once before resizing */ - void initResizing(); + void initResizing(boolean onOpen); /** * Resizes the given element * - * @param guiElement element to resize * @param isParentLayout if the parent is a layout widget * @return true if element is fully resized */ - boolean resize(IGuiElement guiElement, boolean isParentLayout); + boolean resize(boolean isParentLayout); /** - * Called if {@link #resize(IGuiElement, boolean)} returned false after children have been resized. + * Called if {@link #resize(boolean)} returned false after children have been resized. * - * @param guiElement element to resize * @return if element is fully resized */ - boolean postResize(IGuiElement guiElement); + boolean postResize(); /** * Called after all elements in the tree are resized and the absolute positions needs to be calculated from the * relative postion. - * - * @param guiElement element that was resized */ - default void applyPos(IGuiElement guiElement) {} + default void applyPos() {} void setChildrenResized(boolean resized); diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java deleted file mode 100644 index 0929cdb08..000000000 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable2.java +++ /dev/null @@ -1,120 +0,0 @@ -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(boolean onOpen); - - /** - * 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/widget/sizer/Area.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java index 7a2f86591..316e5eac1 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Area.java @@ -17,7 +17,7 @@ * A rectangular widget area, composed of a position and a size. * Also has fields for a relative position, a layer and margin & padding. */ -public class Area extends Rectangle implements IUnResizeable, IAnimatable { +public class Area extends Rectangle implements IAnimatable { public static boolean isInside(int x, int y, int w, int h, int px, int py) { SHARED.set(x, y, w, h); @@ -502,11 +502,6 @@ public Box getPadding() { return this.padding; } - @Override - public Area getArea() { - return this; - } - /** * This creates a copy with size, pos, margin padding and z layer. * diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderResizer.java similarity index 71% rename from src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java rename to src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderResizer.java index 074115f87..29de66572 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderStandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ExpanderResizer.java @@ -4,11 +4,11 @@ import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.widgets.layout.IExpander; -public class ExpanderStandardResizer extends StandardResizer implements IExpander { +public class ExpanderResizer extends StandardResizer implements IExpander { private final GuiAxis axis; - public ExpanderStandardResizer(IWidget widget, GuiAxis axis) { + public ExpanderResizer(IWidget widget, GuiAxis axis) { super(widget); this.axis = axis; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java deleted file mode 100644 index fac508632..000000000 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/Flex.java +++ /dev/null @@ -1,648 +0,0 @@ -package com.cleanroommc.modularui.widget.sizer; - -/** - * This class handles resizing and positioning of widgets. - */ -/* -public class Flex implements IResizeable, IPositioned { - - private final DimensionSizer x; - private final DimensionSizer y; - private boolean expanded = false; - private final IGuiElement parent; - private Area relativeTo; - private boolean relativeToParent = true; - private boolean bypassLayerRestriction = false; - - private boolean childrenCalculated = false; - private boolean layoutDone = true; - - public Flex(IGuiElement parent) { - this.parent = parent; - this.x = createDimensionSizer(GuiAxis.X); - this.y = createDimensionSizer(GuiAxis.Y); - } - - protected DimensionSizer createDimensionSizer(GuiAxis axis) { - return new DimensionSizer(axis); - } - - public void reset() { - this.x.reset(); - this.y.reset(); - } - - public void resetPosition() { - this.x.resetPosition(); - this.y.resetPosition(); - } - - @Override - public Flex flex() { - return this; - } - - @Override - public Area getArea() { - return this.parent.getArea(); - } - - @Override - public boolean requiresResize() { - return this.parent.requiresResize(); - } - - @Override - public void scheduleResize() { - this.parent.scheduleResize(); - } - - @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.childrenCalculated; - } - - @Override - public boolean isLayoutDone() { - return this.layoutDone; - } - - @Override - public boolean canRelayout(boolean isParentLayout) { - return isParentLayout && (this.x.canRelayout() || this.y.canRelayout()); - } - - @Override - public void setChildrenResized(boolean resized) { - this.childrenCalculated = resized; - } - - @Override - public void setLayoutDone(boolean done) { - this.layoutDone = done; - } - - public Flex coverChildrenWidth() { - this.x.setCoverChildren(true, this.parent); - scheduleResize(); - return this; - } - - public Flex coverChildrenHeight() { - this.y.setCoverChildren(true, this.parent); - scheduleResize(); - return this; - } - - public Flex cancelMovementX() { - this.x.setCancelAutoMovement(true); - scheduleResize(); - return this; - } - - public Flex cancelMovementY() { - this.y.setCancelAutoMovement(true); - scheduleResize(); - return this; - } - - public Flex expanded() { - this.expanded = true; - scheduleResize(); - return this; - } - - public Flex relative(Area guiElement) { - this.relativeTo = guiElement; - this.relativeToParent = false; - scheduleResize(); - return this; - } - - public Flex relativeToScreen() { - this.relativeTo = null; - this.relativeToParent = false; - scheduleResize(); - return this; - } - - public Flex relativeToParent() { - this.relativeToParent = true; - scheduleResize(); - return this; - } - - @Override - public Flex bypassLayerRestriction() { - this.bypassLayerRestriction = true; - scheduleResize(); - return this; - } - - @ApiStatus.Internal - public Flex left(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getLeft(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex left(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getLeft(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex right(float x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getRight(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex right(DoubleSupplier x, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getRight(), x, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex top(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getTop(), y, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex top(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getTop(), y, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex bottom(float y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getBottom(), y, offset, anchor, measure, autoAnchor); - } - - @ApiStatus.Internal - public Flex bottom(DoubleSupplier y, int offset, float anchor, Unit.Measure measure, boolean autoAnchor) { - return unit(getBottom(), y, offset, anchor, measure, autoAnchor); - } - - private Flex 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 Flex 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 Flex width(float val, int offset, Unit.Measure measure) { - return unitSize(getWidth(), val, offset, measure); - } - - @ApiStatus.Internal - public Flex width(DoubleSupplier val, int offset, Unit.Measure measure) { - return unitSize(getWidth(), val, offset, measure); - } - - @ApiStatus.Internal - public Flex height(float val, int offset, Unit.Measure measure) { - return unitSize(getHeight(), val, offset, measure); - } - - @ApiStatus.Internal - public Flex height(DoubleSupplier val, int offset, Unit.Measure measure) { - return unitSize(getHeight(), val, offset, measure); - } - - private Flex unitSize(Unit u, float val, int offset, Unit.Measure measure) { - u.setValue(val); - u.setMeasure(measure); - u.setOffset(offset); - scheduleResize(); - return this; - } - - private Flex unitSize(Unit u, DoubleSupplier val, int offset, Unit.Measure measure) { - u.setValue(val); - u.setMeasure(measure); - u.setOffset(offset); - scheduleResize(); - return this; - } - - public Flex anchorLeft(float val) { - getLeft().setAnchor(val); - getLeft().setAutoAnchor(false); - scheduleResize(); - return this; - } - - public Flex anchorRight(float val) { - getRight().setAnchor(1 - val); - getRight().setAutoAnchor(false); - scheduleResize(); - return this; - } - - public Flex anchorTop(float val) { - getTop().setAnchor(val); - getTop().setAutoAnchor(false); - scheduleResize(); - return this; - } - - public Flex anchorBottom(float val) { - getBottom().setAnchor(1 - val); - getBottom().setAutoAnchor(false); - scheduleResize(); - return this; - } - - public Flex 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 IResizeable getRelativeTo() { - IGuiElement parent = this.parent.getParent(); - IResizeable relativeTo = this.relativeToParent && parent != null ? parent.resizer() : this.relativeTo; - return relativeTo != null ? relativeTo : this.parent.getScreen().getScreenArea(); - } - - public boolean isExpanded() { - return this.expanded; - } - - 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(); - } - - public boolean xAxisDependsOnChildren() { - return this.x.dependsOnChildren(); - } - - public boolean yAxisDependsOnChildren() { - return this.y.dependsOnChildren(); - } - - public boolean dependsOnChildren(GuiAxis axis) { - return axis.isHorizontal() ? xAxisDependsOnChildren() : yAxisDependsOnChildren(); - } - - public boolean dependsOnChildren() { - return xAxisDependsOnChildren() || yAxisDependsOnChildren(); - } - - public boolean hasFixedSize() { - return this.x.hasFixedSize() && this.y.hasFixedSize(); - } - - public boolean isFullSize() { - if (!hasHeight() || !hasWidth()) return false; - return this.x.isFullSize() && this.y.isFullSize(); - } - - @ApiStatus.Internal - public void checkExpanded(@Nullable GuiAxis axis) { - this.x.setExpanded(false); - this.y.setExpanded(false); - if (this.expanded && axis != null) { - if (axis.isHorizontal()) this.x.setExpanded(true); - else this.y.setExpanded(true); - } - } - - @Override - public void initResizing() { - setMarginPaddingApplied(false); - setResized(false); - this.childrenCalculated = false; - this.layoutDone = false; - } - - @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); - } - - @Override - public boolean isXMarginPaddingApplied() { - return this.x.isMarginPaddingApplied(); - } - - @Override - public boolean isYMarginPaddingApplied() { - return this.y.isMarginPaddingApplied(); - } - - @Override - public boolean resize(IGuiElement guiElement, boolean isParentLayout) { - IResizeable relativeTo = getRelativeTo(); - Area relativeArea = relativeTo.getArea(); - - /*if (!this.bypassLayerRestriction && (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(guiElement.getArea(), relativeTo, guiElement::getDefaultWidth); - this.y.apply(guiElement.getArea(), relativeTo, guiElement::getDefaultHeight); - return isFullyCalculated(isParentLayout); - } - - @Override - public boolean postResize(IGuiElement guiElement) { - boolean coverWidth = this.x.dependsOnChildren(); - boolean coverHeight = this.y.dependsOnChildren(); - if (!coverWidth && !coverHeight) return isSelfFullyCalculated(); - if (!(this.parent instanceof IWidget widget) || !widget.hasChildren()) { - coverChildrenForEmpty(); - return isSelfFullyCalculated(); - } - if (this.parent 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 = this.parent.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(); - IResizeable resizeable = child.resizer(); - Area area = child.getArea(); - if (coverWidth) { - if (!child.flex().x.dependsOnParent()) { - 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 (!child.flex().y.dependsOnParent()) { - 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(this.parent, 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 = getRelativeTo().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(this.parent.getArea(), relativeTo, x0, x1); - } - if (coverHeight) { - moveChildrenY = this.y.postApply(this.parent.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(); - IResizeable 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 = this.parent.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(); - IResizeable resizeable = child.resizer(); - if (coverWidth) { - if (!child.flex().x.dependsOnParent()) { - 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.flex().y.dependsOnParent()) { - 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(this.parent, 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 = getRelativeTo().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(this.parent.getArea(), getRelativeTo().getArea()); - } - if (this.y.dependsOnChildren()) { - this.y.coverChildrenForEmpty(this.parent.getArea(), getRelativeTo().getArea()); - } - } - - @Override - public void applyPos(IGuiElement parent) { - Area relativeTo = getRelativeTo().getArea(); - Area area = parent.getArea(); - // apply margin and padding if not done yet - this.x.applyMarginAndPaddingToPos(parent, area, relativeTo); - this.y.applyMarginAndPaddingToPos(parent, 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 = parent.getParentArea(); - area.rx = area.x - parentArea.x; - area.ry = area.y - parentArea.y; - if (parent instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot()) { - // special treatment for minecraft slots - Slot slot = vanillaSlot.getVanillaSlot(); - Area mainArea = parent.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 = parent.getArea().x - mainArea.x + 1; - slot.yPos = parent.getArea().y - mainArea.y + 1; - } - } - - private Unit getLeft() { - return this.x.getStart(this.parent); - } - - private Unit getRight() { - return this.x.getEnd(this.parent); - } - - private Unit getTop() { - return this.y.getStart(this.parent); - } - - private Unit getBottom() { - return this.y.getEnd(this.parent); - } - - private Unit getWidth() { - return this.x.getSize(this.parent); - } - - private Unit getHeight() { - return this.y.getSize(this.parent); - } -}*/ diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java deleted file mode 100644 index d4ffa32de..000000000 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/IUnResizeable.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.cleanroommc.modularui.widget.sizer; - -import com.cleanroommc.modularui.api.layout.IResizeParent; -import com.cleanroommc.modularui.api.layout.IResizeable; - -/** - * A variation of {@link IResizeable} with default implementations which don't do anything - */ -public interface IUnResizeable extends IResizeParent { - - IUnResizeable INSTANCE = () -> { - Area.SHARED.set(0, 0, 0, 0); - return Area.SHARED; - }; - - @Override - default boolean isXCalculated() { - return true; - } - - @Override - default boolean isYCalculated() { - return true; - } - - @Override - default boolean isWidthCalculated() { - return true; - } - - @Override - default boolean isHeightCalculated() { - return true; - } - - @Override - default boolean areChildrenCalculated() { - return true; - } - - @Override - default boolean isLayoutDone() { - return true; - } - - @Override - default boolean canRelayout(boolean isParentLayout) { - return false; - } - - @Override - default boolean isXMarginPaddingApplied() { - return true; - } - - @Override - default boolean isYMarginPaddingApplied() { - 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 index 9f50ff329..68c4449aa 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -2,7 +2,7 @@ import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.api.ITreeNode; -import com.cleanroommc.modularui.api.layout.IResizeable2; +import com.cleanroommc.modularui.api.layout.IResizeable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.List; -public abstract class ResizeNode implements IResizeable2, ITreeNode { +public abstract class ResizeNode implements IResizeable, ITreeNode { private ResizeNode defaultParent; private ResizeNode parentOverride; diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 76c977556..418adec2b 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -3,7 +3,7 @@ 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.layout.IResizeable; import com.cleanroommc.modularui.api.widget.IDelegatingWidget; import com.cleanroommc.modularui.api.widget.IPositioned; import com.cleanroommc.modularui.api.widget.IVanillaSlot; @@ -255,7 +255,7 @@ private void coverChildrenForLayout(ILayoutWidget layout, IWidget widget) { if (layout.shouldIgnoreChildSize(child)) continue; Area area = child.getArea(); Box margin = area.getMargin(); - IResizeable2 resizeable = child.resizer(); + IResizeable resizeable = child.resizer(); if (coverWidth) { if (!child.resizer().dependsOnParentX()) { hasIndependentChildX = true; diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index fa55869ec..95fa4948e 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -7,7 +7,7 @@ import com.cleanroommc.modularui.utils.ReversedList; import com.cleanroommc.modularui.widget.ParentWidget; import com.cleanroommc.modularui.widget.sizer.Box; -import com.cleanroommc.modularui.widget.sizer.ExpanderStandardResizer; +import com.cleanroommc.modularui.widget.sizer.ExpanderResizer; import java.util.List; import java.util.function.IntFunction; @@ -50,7 +50,7 @@ public static Flow column() { public Flow(GuiAxis axis) { this.axis = axis; - resizer(new ExpanderStandardResizer(this, axis)); + resizer(new ExpanderResizer(this, axis)); sizeRel(1f, 1f); } From 0c5ebfdac3e6a0ad85550095e813d03338e82467 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 30 Jan 2026 14:21:28 +0100 Subject: [PATCH 13/20] more clean up --- .../modularui/api/layout/IResizeParent.java | 1 - .../modularui/api/widget/IDragResizeable.java | 13 ++++--- .../modularui/api/widget/IWidget.java | 35 ++++++++++--------- .../screen/DraggablePanelWrapper.java | 8 ++--- .../cleanroommc/modularui/test/TestTile.java | 2 +- .../modularui/widget/AbstractWidget.java | 18 ---------- .../modularui/widget/DelegatingWidget.java | 5 --- .../modularui/widget/DraggableWidget.java | 2 +- .../modularui/widget/EmptyWidget.java | 14 ++------ .../modularui/widgets/ListWidget.java | 2 +- .../modularui/widgets/SlotGroupWidget.java | 2 +- .../modularui/widgets/SortableListWidget.java | 2 +- .../modularui/widgets/layout/Flow.java | 2 +- .../widgets/menu/ContextMenuButton.java | 2 +- 14 files changed, 37 insertions(+), 71 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java index e34b50421..b4ad84519 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeParent.java @@ -8,7 +8,6 @@ public interface IResizeParent { /** * @return area of the element */ - // TODO doesnt fit with the other api methods in this interface Area getArea(); /** diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java b/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java index ceb7f68e5..4a03cf0f6 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IDragResizeable.java @@ -84,18 +84,17 @@ static ResizeDragArea getDragResizeCorner(IDragResizeable widget, Area area, IVi */ static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea dragArea, Area startArea, int dx, int dy) { int keepPosFactor = resizeable.keepPosOnDragResize() || GuiScreen.isShiftKeyDown() ? 2 : 1; - // TODO - /*if (dx != 0) { + if (dx != 0) { if (dragArea.left) { int s = startArea.width - dx * keepPosFactor; if (s >= resizeable.getMinDragWidth()) { - widget.left(startArea.rx + dx); + widget.resizer().left(startArea.rx + dx); widget.resizer().width(s); } } else if (dragArea.right) { int s = startArea.width + dx * keepPosFactor; if (s >= resizeable.getMinDragWidth()) { - widget.flex().left(startArea.rx - dx * (keepPosFactor - 1)); + widget.resizer().left(startArea.rx - dx * (keepPosFactor - 1)); widget.resizer().width(s); } } @@ -104,17 +103,17 @@ static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea if (dragArea.top) { int s = startArea.height - dy * keepPosFactor; if (s >= resizeable.getMinDragHeight()) { - widget.flex().top(startArea.ry + dy); + widget.resizer().top(startArea.ry + dy); widget.resizer().height(s); } } else if (dragArea.bottom) { int s = startArea.height + dy * keepPosFactor; if (s >= resizeable.getMinDragHeight()) { - widget.flex().top(startArea.ry - dy * (keepPosFactor - 1)); + widget.resizer().top(startArea.ry - dy * (keepPosFactor - 1)); widget.resizer().height(s); } } - }*/ + } resizeable.onDragResize(); } 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 bda1991f9..451a272dd 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -237,13 +237,25 @@ default boolean hasParent() { @NotNull ModularPanel getPanel(); + /** + * @return flex of this widget + */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + @Nullable + default StandardResizer getFlex() { + return resizer(); + } + /** * @return flex of this widget. Creates a new one if it doesn't already have one. */ @NotNull @Deprecated @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") - StandardResizer flex(); + default StandardResizer flex() { + return resizer(); + } /** * Does the same as {@link IPositioned#flex(Consumer)} @@ -254,8 +266,7 @@ default boolean hasParent() { @Deprecated @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") default IWidget flexBuilder(Consumer builder) { - builder.accept(flex()); - return this; + return resizerBuilder(builder); } /** @@ -265,6 +276,11 @@ default IWidget flexBuilder(Consumer builder) { @Override StandardResizer resizer(); + default IWidget resizerBuilder(Consumer builder) { + builder.accept(resizer()); + return this; + } + /** * Called before a widget is resized. */ @@ -280,19 +296,6 @@ default void onResized() {} */ default void postResize() {} - /** - * @return flex of this widget - */ - @Deprecated - @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") - @Nullable - StandardResizer getFlex(); - - default boolean isExpanded() { - StandardResizer flex = getFlex(); - return flex != null && flex.isExpanded(); - } - @Nullable String getName(); default boolean isName(String name) { diff --git a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java index 53fb17817..ad7a313a1 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java +++ b/src/main/java/com/cleanroommc/modularui/screen/DraggablePanelWrapper.java @@ -48,11 +48,9 @@ public void onDragEnd(boolean successful) { float x = this.panel.getContext().getAbsMouseX() - this.relativeClickX; y = y / (this.panel.getScreen().getScreenArea().height - this.panel.getArea().height); x = x / (this.panel.getScreen().getScreenArea().width - this.panel.getArea().width); - // TODO - //this.panel.flex().resetPosition(); - //this.panel.flex().relativeToScreen(); - //this.panel.flex().topRelAnchor(y, y) - // .leftRelAnchor(x, x); + this.panel.resizer().resetPosition(); + this.panel.resizer().relativeToScreen(); + this.panel.resizer().topRelAnchor(y, y).leftRelAnchor(x, x); this.panel.scheduleResize(); } } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index 62f6fe33d..eadcd5fca 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -192,7 +192,7 @@ public ModularPanel buildUI(PosGuiData guiData, PanelSyncManager syncManager, UI .top(0) .rightRel(1f), true); PagedWidget.Controller tabController = new PagedWidget.Controller(); - panel.flex() // returns object which is responsible for sizing + panel.resizer() // returns object which is responsible for sizing .size(176, 220) // set a static size for the main panel .align(Alignment.Center); // center the panel in the screen panel diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java index 36b64bc90..237e4e50a 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -311,24 +311,6 @@ protected void resizer(StandardResizer resizer) { this.resizer = 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 - */ - @Nullable - @Override - public StandardResizer getFlex() { - return resizer; - } - - @Override - public @NotNull StandardResizer flex() { - return resizer(); - } - @Override public @Nullable String getName() { return name; diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java index 9df60bc21..d7c03729f 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java @@ -53,11 +53,6 @@ public void postResize() { } } - @Override - public StandardResizer getFlex() { - return getDelegate() != null ? getDelegate().getFlex() : super.getFlex(); - } - @Override public @NotNull StandardResizer resizer() { return getDelegate() != null ? getDelegate().resizer() : super.resizer(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java index 3772c7b81..39f4f55da 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DraggableWidget.java @@ -46,7 +46,7 @@ public boolean onDragStart(int mouseButton) { @Override public void onDragEnd(boolean successful) { if (successful) { - flex().top(getContext().getAbsMouseY() - this.relativeClickY) + resizer().top(getContext().getAbsMouseY() - this.relativeClickY) .left(getContext().getAbsMouseX() - this.relativeClickX); this.movingArea.x = getArea().x; this.movingArea.y = getArea().y; diff --git a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java index aaa5d8e75..79db712ee 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java @@ -15,7 +15,7 @@ public class EmptyWidget implements IWidget { private final Area area = new Area(); - private final StandardResizer flex = new StandardResizer(this); + private final StandardResizer resizer = new StandardResizer(this); private boolean requiresResize = false; private boolean enabled = true; private IWidget parent; @@ -119,19 +119,9 @@ public ModularGuiContext getContext() { return this.parent.getContext(); } - @Override - public StandardResizer flex() { - return this.flex; - } - @Override public @NotNull StandardResizer resizer() { - return this.flex; - } - - @Override - public StandardResizer getFlex() { - return this.flex; + return this.resizer; } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java index e16a056cc..cf7c8edb3 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ListWidget.java @@ -58,7 +58,7 @@ public void onInit() { public void beforeResize(boolean onOpen) { super.beforeResize(onOpen); if (this.mainAxisMaxSize != null) { - flex().setUnit(this.mainAxisMaxSize, getAxis(), Unit.State.SIZE); + resizer().setUnit(this.mainAxisMaxSize, getAxis(), Unit.State.SIZE); } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java index add102bc1..0de7d5f8d 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SlotGroupWidget.java @@ -259,7 +259,7 @@ public SlotGroupWidget build() { y += 18; x = 0; } - slotGroupWidget.flex().size(maxWidth, this.matrix.size() * 18); + slotGroupWidget.resizer().size(maxWidth, this.matrix.size() * 18); return slotGroupWidget; } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java index 976058c59..35cebee46 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java @@ -163,7 +163,7 @@ public static class Item extends DraggableWidget> implements IValueWi public Item(T value) { this.value = value; - flex().widthRel(1f).height(18); + resizer().widthRel(1f).height(18); background(GuiTextures.BUTTON_CLEAN); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java index 95fa4948e..ace8e1f55 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/layout/Flow.java @@ -105,7 +105,7 @@ public boolean layoutWidgets() { final int size = getArea().getSize(axis) - padding.getTotal(this.axis); Alignment.MainAxis maa = this.maa; if (!hasSize && maa != Alignment.MainAxis.START) { - if (flex().dependsOnChildren(this.axis)) { + if (resizer().dependsOnChildren(this.axis)) { // if this flow covers the children, we can assume start maa = Alignment.MainAxis.START; } else { diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java index 83fee8396..e20fe0415 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -91,7 +91,7 @@ private void initMenuList() { } this.menuList.setSource(this); this.menuList.relative(this); - this.direction.positioner.accept(this.menuList.flex()); + this.direction.positioner.accept(this.menuList.resizer()); } private IPanelHandler getPanelHandler() { From e8725ed53d87c24bed00b9463d969135382547ce Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 30 Jan 2026 23:19:32 +0100 Subject: [PATCH 14/20] dropdown widget --- .../modularui/api/widget/IPositioned.java | 132 ++++---- .../modularui/api/widget/IWidget.java | 2 +- .../modularui/overlay/DebugOverlay.java | 60 ++-- .../modularui/screen/CustomModularScreen.java | 3 + .../modularui/screen/ModularScreen.java | 2 +- .../modularui/test/EventHandler.java | 13 +- .../cleanroommc/modularui/test/GLTestGui.java | 5 + .../modularui/test/ItemEditorGui.java | 9 +- .../cleanroommc/modularui/test/TestGui.java | 4 + .../cleanroommc/modularui/test/TestGuis.java | 62 ++-- .../cleanroommc/modularui/test/TestItem.java | 6 + .../cleanroommc/modularui/test/TestTile.java | 4 +- .../cleanroommc/modularui/test/TestTile2.java | 7 + .../modularui/utils/MutableSingletonList.java | 281 ++++++++++++++++++ .../cleanroommc/modularui/utils/TreeUtil.java | 5 - .../widget/sizer/DimensionSizer.java | 7 +- .../modularui/widget/sizer/ResizeNode.java | 5 + .../widget/sizer/StandardResizer.java | 16 +- .../modularui/widgets/DropdownWidget.java | 132 ++++++++ .../widgets/menu/AbstractMenuButton.java | 179 +++++++++++ .../widgets/menu/ContextMenuButton.java | 166 ++--------- .../widgets/menu/ContextMenuList.java | 73 ----- .../widgets/menu/IContextMenuOption.java | 13 - .../modularui/widgets/menu/IMenuPart.java | 13 + .../modularui/widgets/menu/Menu.java | 45 +++ .../modularui/widgets/menu/MenuPanel.java | 8 +- 26 files changed, 860 insertions(+), 392 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/IMenuPart.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/Menu.java 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 6b12d5073..51632ab21 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IPositioned.java @@ -20,7 +20,7 @@ @SuppressWarnings({"unused", "UnusedReturnValue"}) public interface IPositioned> { - StandardResizer flex(); + StandardResizer resizer(); Area getArea(); @@ -34,12 +34,12 @@ default W getThis() { } default W coverChildrenWidth() { - flex().coverChildrenWidth(); + resizer().coverChildrenWidth(); return getThis(); } default W coverChildrenHeight() { - flex().coverChildrenHeight(); + resizer().coverChildrenHeight(); return getThis(); } @@ -48,7 +48,7 @@ default W coverChildren() { } default W expanded() { - flex().expanded(); + resizer().expanded(); return getThis(); } @@ -64,7 +64,7 @@ default W relative(Area area) { } default W relative(ResizeNode resizeNode) { - flex().relative(resizeNode); + resizer().relative(resizeNode); return getThis(); } @@ -73,273 +73,273 @@ default W relative(IWidget widget) { } default W relativeToScreen() { - flex().relativeToScreen(); + resizer().relativeToScreen(); return getThis(); } default W relativeToParent() { - flex().relativeToParent(); + resizer().relativeToParent(); return getThis(); } default W left(int val) { - flex().left(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().left(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W leftRel(float val) { - flex().left(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().left(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W leftRelOffset(float val, int offset) { - flex().left(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().left(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W leftRelAnchor(float val, float anchor) { - flex().left(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W leftRel(float val, int offset, float anchor) { - flex().left(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W left(float val, int offset, float anchor, Unit.Measure measure) { - flex().left(val, offset, anchor, measure, false); + resizer().left(val, offset, anchor, measure, false); return getThis(); } default W left(DoubleSupplier val, Unit.Measure measure) { - flex().left(val, 0, 0, measure, true); + resizer().left(val, 0, 0, measure, true); return getThis(); } default W leftRelOffset(DoubleSupplier val, int offset) { - flex().left(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().left(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W leftRelAnchor(DoubleSupplier val, float anchor) { - flex().left(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W leftRel(DoubleSupplier val, int offset, float anchor) { - flex().left(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().left(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W right(int val) { - flex().right(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().right(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W rightRel(float val) { - flex().right(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().right(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W rightRelOffset(float val, int offset) { - flex().right(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().right(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W rightRelAnchor(float val, float anchor) { - flex().right(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W rightRel(float val, int offset, float anchor) { - flex().right(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W right(float val, int offset, float anchor, Unit.Measure measure) { - flex().right(val, offset, anchor, measure, false); + resizer().right(val, offset, anchor, measure, false); return getThis(); } default W right(DoubleSupplier val, Unit.Measure measure) { - flex().right(val, 0, 0, measure, true); + resizer().right(val, 0, 0, measure, true); return getThis(); } default W rightRelOffset(DoubleSupplier val, int offset) { - flex().right(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().right(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W rightRelAnchor(DoubleSupplier val, float anchor) { - flex().right(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W rightRel(DoubleSupplier val, int offset, float anchor) { - flex().right(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().right(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W top(int val) { - flex().top(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().top(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W topRel(float val) { - flex().top(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().top(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W topRelOffset(float val, int offset) { - flex().top(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().top(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W topRelAnchor(float val, float anchor) { - flex().top(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W topRel(float val, int offset, float anchor) { - flex().top(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W top(float val, int offset, float anchor, Unit.Measure measure) { - flex().top(val, offset, anchor, measure, false); + resizer().top(val, offset, anchor, measure, false); return getThis(); } default W top(DoubleSupplier val, Unit.Measure measure) { - flex().top(val, 0, 0, measure, true); + resizer().top(val, 0, 0, measure, true); return getThis(); } default W topRelOffset(DoubleSupplier val, int offset) { - flex().top(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().top(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W topRelAnchor(DoubleSupplier val, float anchor) { - flex().top(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W topRel(DoubleSupplier val, int offset, float anchor) { - flex().top(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().top(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottom(int val) { - flex().bottom(val, 0, 0, Unit.Measure.PIXEL, true); + resizer().bottom(val, 0, 0, Unit.Measure.PIXEL, true); return getThis(); } default W bottomRel(float val) { - flex().bottom(val, 0, 0, Unit.Measure.RELATIVE, true); + resizer().bottom(val, 0, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W bottomRelOffset(float val, int offset) { - flex().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W bottomRelAnchor(float val, float anchor) { - flex().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottomRel(float val, int offset, float anchor) { - flex().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottom(float val, int offset, float anchor, Unit.Measure measure) { - flex().bottom(val, offset, anchor, measure, false); + resizer().bottom(val, offset, anchor, measure, false); return getThis(); } default W bottom(DoubleSupplier val, Unit.Measure measure) { - flex().bottom(val, 0, 0, measure, true); + resizer().bottom(val, 0, 0, measure, true); return getThis(); } default W bottomRelOffset(DoubleSupplier val, int offset) { - flex().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); + resizer().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); return getThis(); } default W bottomRelAnchor(DoubleSupplier val, float anchor) { - flex().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W bottomRel(DoubleSupplier val, int offset, float anchor) { - flex().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); + resizer().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); return getThis(); } default W width(int val) { - flex().width(val, 0, Unit.Measure.PIXEL); + resizer().width(val, 0, Unit.Measure.PIXEL); return getThis(); } default W widthRel(float val) { - flex().width(val, 0, Unit.Measure.RELATIVE); + resizer().width(val, 0, Unit.Measure.RELATIVE); return getThis(); } default W widthRelOffset(float val, int offset) { - flex().width(val, offset, Unit.Measure.RELATIVE); + resizer().width(val, offset, Unit.Measure.RELATIVE); return getThis(); } default W width(float val, Unit.Measure measure) { - flex().width(val, 0, measure); + resizer().width(val, 0, measure); return getThis(); } default W width(DoubleSupplier val, Unit.Measure measure) { - flex().width(val, 0, measure); + resizer().width(val, 0, measure); return getThis(); } default W widthRelOffset(DoubleSupplier val, int offset) { - flex().width(val, offset, Unit.Measure.RELATIVE); + resizer().width(val, offset, Unit.Measure.RELATIVE); return getThis(); } default W height(int val) { - flex().height(val, 0, Unit.Measure.PIXEL); + resizer().height(val, 0, Unit.Measure.PIXEL); return getThis(); } default W heightRel(float val) { - flex().height(val, 0, Unit.Measure.RELATIVE); + resizer().height(val, 0, Unit.Measure.RELATIVE); return getThis(); } default W heightRelOffset(float val, int offset) { - flex().height(val, offset, Unit.Measure.RELATIVE); + resizer().height(val, offset, Unit.Measure.RELATIVE); return getThis(); } default W height(float val, Unit.Measure measure) { - flex().height(val, 0, measure); + resizer().height(val, 0, measure); return getThis(); } default W height(DoubleSupplier val, Unit.Measure measure) { - flex().height(val, 0, measure); + resizer().height(val, 0, measure); return getThis(); } default W heightRelOffset(DoubleSupplier val, int offset) { - flex().height(val, offset, Unit.Measure.RELATIVE); + resizer().height(val, offset, Unit.Measure.RELATIVE); return getThis(); } @@ -384,27 +384,27 @@ default W full() { } default W anchorLeft(float val) { - flex().anchorLeft(val); + resizer().anchorLeft(val); return getThis(); } default W anchorRight(float val) { - flex().anchorRight(val); + resizer().anchorRight(val); return getThis(); } default W anchorTop(float val) { - flex().anchorTop(val); + resizer().anchorTop(val); return getThis(); } default W anchorBottom(float val) { - flex().anchorBottom(val); + resizer().anchorBottom(val); return getThis(); } default W anchor(Alignment alignment) { - flex().anchor(alignment); + resizer().anchor(alignment); return getThis(); } @@ -443,8 +443,8 @@ default W center() { return align(Alignment.Center); } - default W flex(Consumer flexConsumer) { - flexConsumer.accept(flex()); + default W resizer(Consumer flexConsumer) { + flexConsumer.accept(resizer()); return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java index 451a272dd..8a1eb5faf 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -258,7 +258,7 @@ default StandardResizer flex() { } /** - * Does the same as {@link IPositioned#flex(Consumer)} + * Does the same as {@link IPositioned#resizer(Consumer)} * * @param builder function to build flex * @return this diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index e00bbd995..bece3df97 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.overlay; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.IMuiScreen; import com.cleanroommc.modularui.api.drawable.IIcon; import com.cleanroommc.modularui.api.drawable.IKey; @@ -16,9 +17,10 @@ import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.widget.WidgetTree; import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ListWidget; 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.Menu; import org.jetbrains.annotations.NotNull; @@ -29,6 +31,7 @@ public class DebugOverlay extends CustomModularScreen { private final IMuiScreen parent; public DebugOverlay(IMuiScreen screen) { + super(ModularUI.ID); this.parent = screen; } @@ -36,7 +39,7 @@ public DebugOverlay(IMuiScreen screen) { public @NotNull ModularPanel buildUI(ModularGuiContext context) { return new ModularPanel("debug") .fullScreenInvisible() - .child(new ContextMenuButton<>().name("ctx_mb_main") + .child(new ContextMenuButton<>("menu_debug_options") .horizontalCenter() .bottom(0) .height(12) @@ -44,48 +47,59 @@ public DebugOverlay(IMuiScreen screen) { .background(new Rectangle().color(Color.withAlpha(Color.WHITE.main, 0.2f)).cornerRadius(4)) .overlay(IKey.str("Debug Options")) .openUp() - .menuList(new ContextMenuList<>("debug_options_ctx_ml1") + .menuList(l1 -> l1 + .name("menu_list") .maxSize(100) .widthRel(1f) .child(new ButtonWidget<>().name("ctx_b") + .height(12) + .widthRel(1f) .invisible() .overlay(IKey.str("Print widget trees")) .onMousePressed(this::logWidgetTrees)) - .child(new ContextMenuButton<>() - .name("menu_button_hover_info") + .child(new ContextMenuButton<>("menu_hover_info") .height(10) + .widthRel(1f) .overlay(IKey.str("Widget hover info")) .openRightUp() - .menuList(new ContextMenuList<>("menu_list_hover_info") - .maxSize(100) + .menu(new Menu<>() .width(100) - .child(toggleOption(0, "Any", DebugOptions.INSTANCE.showHovered)) - .child(toggleOption(1, "Pos", DebugOptions.INSTANCE.showPos)) - .child(toggleOption(2, "Size", DebugOptions.INSTANCE.showSize)) - .child(toggleOption(3, "Widgettheme", DebugOptions.INSTANCE.showWidgetTheme)) - .child(toggleOption(4, "Extra info", DebugOptions.INSTANCE.showExtra)) - .child(toggleOption(5, "Outline", DebugOptions.INSTANCE.showOutline)) - )) - .child(new ContextMenuButton<>() + .coverChildrenHeight() + .child(new ListWidget<>() + .maxSize(100) + .width(100) + .child(toggleOption(0, "Any", DebugOptions.INSTANCE.showHovered)) + .child(toggleOption(1, "Pos", DebugOptions.INSTANCE.showPos)) + .child(toggleOption(2, "Size", DebugOptions.INSTANCE.showSize)) + .child(toggleOption(3, "Widget Theme", DebugOptions.INSTANCE.showWidgetTheme)) + .child(toggleOption(4, "Extra info", DebugOptions.INSTANCE.showExtra)) + .child(toggleOption(5, "Outline", DebugOptions.INSTANCE.showOutline))))) + .child(new ContextMenuButton<>("menu_parent_hover_info") .name("menu_button_parent_hover_info") .height(10) + .widthRel(1f) .overlay(IKey.str("Parent widget hover info")) .openRightUp() - .menuList(new ContextMenuList<>("menu_list_parent_hover_info") - .maxSize(100) + .menu(new Menu<>() .width(100) - .child(toggleOption(10, "Any", DebugOptions.INSTANCE.showParent)) - .child(toggleOption(11, "Pos", DebugOptions.INSTANCE.showParentPos)) - .child(toggleOption(12, "Size", DebugOptions.INSTANCE.showParentSize)) - .child(toggleOption(13, "Widgettheme", DebugOptions.INSTANCE.showParentWidgetTheme)) - .child(toggleOption(14, "Outline", DebugOptions.INSTANCE.showParentOutline)) - )))); + .coverChildrenHeight() + .child(new ListWidget<>() + .maxSize(100) + .widthRel(1f) + .child(toggleOption(10, "Any", DebugOptions.INSTANCE.showParent)) + .child(toggleOption(11, "Pos", DebugOptions.INSTANCE.showParentPos)) + .child(toggleOption(12, "Size", DebugOptions.INSTANCE.showParentSize)) + .child(toggleOption(13, "Widget Theme", DebugOptions.INSTANCE.showParentWidgetTheme)) + .child(toggleOption(14, "Outline", DebugOptions.INSTANCE.showParentOutline)) + ))))); } public static IWidget toggleOption(int i, String name, IBoolValue boolValue) { return new ToggleButton() .name("hover_info_toggle" + i) .invisible() + .widthRel(1f) + .height(12) .value(boolValue) .overlay(true, new NamedDrawableRow() .name(IKey.str(name)) diff --git a/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java index 647f93474..c9452f51b 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java @@ -18,8 +18,11 @@ public abstract class CustomModularScreen extends ModularScreen { /** * Creates a new screen with ModularUI as its owner. */ + @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") + @Deprecated public CustomModularScreen() { super(ModularUI.ID); + ModularUI.LOGGER.error("The single arg ModularScreen constructor should not be used. Use the other one and pass in your mod id."); } /** diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index 021f12de9..5c4f2ae68 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -96,7 +96,7 @@ public static ModularScreen getCurrent() { @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public ModularScreen(@NotNull ModularPanel mainPanel) { this(ModularUI.ID, mainPanel); - ModularUI.LOGGER.error("The single arg ModularScreen constructor should not be used. Use the other one and pass in your mod id."); + ModularUI.LOGGER.warn("The single arg ModularScreen constructor should not be used. Use the any of the other ones and pass in your mod id."); } /** diff --git a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java index 120edfa4b..dbd7b1636 100644 --- a/src/main/java/com/cleanroommc/modularui/test/EventHandler.java +++ b/src/main/java/com/cleanroommc/modularui/test/EventHandler.java @@ -67,16 +67,7 @@ public void onItemUse(PlayerInteractEvent.RightClickItem event) { if (event.getEntityPlayer().getEntityWorld().isRemote) { ItemStack itemStack = event.getItemStack(); if (itemStack.getItem() == Items.DIAMOND) { - //ClientGUI.open(new TestGuis()); - ClientGUI.open(new ModularScreen( - ModularUI.ID, - new ModularPanel("test") - .size(150) - .child(new ButtonWidget<>() - .size(50) - .center() - .overlay(IKey.str("Button"))) - )); + ClientGUI.open(new TestGuis()); } else if (itemStack.getItem() == Items.EMERALD) { HoloUI.builder() .inFrontOf(Platform.getClientPlayer(), 5, false) @@ -141,7 +132,7 @@ private ModularScreen getMainMenuOverlayTest(GuiMainMenu gui) { } private ModularScreen getContainerOverlayTest(GuiContainer gui) { - return new CustomModularScreen() { + return new CustomModularScreen(ModularUI.ID) { @Override public @NotNull ModularPanel buildUI(ModularGuiContext context) { diff --git a/src/main/java/com/cleanroommc/modularui/test/GLTestGui.java b/src/main/java/com/cleanroommc/modularui/test/GLTestGui.java index 8797fef4c..f2ea42dce 100644 --- a/src/main/java/com/cleanroommc/modularui/test/GLTestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/GLTestGui.java @@ -1,5 +1,6 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.drawable.GuiTextures; @@ -40,6 +41,10 @@ public class GLTestGui extends CustomModularScreen { private RenderObject ro1; private RenderObject ro2; + public GLTestGui() { + super(ModularUI.ID); + } + @Override public @NotNull ModularPanel buildUI(ModularGuiContext context) { this.ro1 = new RenderObject(); diff --git a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java index 2687e5cb4..fc6b99f00 100644 --- a/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/ItemEditorGui.java @@ -1,18 +1,20 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.IGuiHolder; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.factory.GuiData; import com.cleanroommc.modularui.factory.SimpleGuiFactory; import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.UISettings; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.value.sync.IntSyncValue; import com.cleanroommc.modularui.value.sync.PanelSyncManager; import com.cleanroommc.modularui.value.sync.StringSyncValue; -import com.cleanroommc.modularui.widgets.slot.ItemSlot; import com.cleanroommc.modularui.widgets.layout.Column; import com.cleanroommc.modularui.widgets.layout.Row; +import com.cleanroommc.modularui.widgets.slot.ItemSlot; import com.cleanroommc.modularui.widgets.slot.ModularSlot; import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; @@ -43,6 +45,11 @@ private void setStack(ItemStack stack) { this.stackHandler.setStackInSlot(0, stack); } + @Override + public ModularScreen createScreen(GuiData data, ModularPanel mainPanel) { + return new ModularScreen(ModularUI.ID, mainPanel); + } + @Override public ModularPanel buildUI(GuiData data, PanelSyncManager syncManager, UISettings settings) { ItemStack itemStack = data.getPlayer().getHeldItemMainhand(); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGui.java b/src/main/java/com/cleanroommc/modularui/test/TestGui.java index d7c6d64bd..1d7b11ec9 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGui.java @@ -35,6 +35,10 @@ public class TestGui extends CustomModularScreen { private Map availableElements; + public TestGui() { + super(ModularUI.ID); + } + @Override public void onClose() { ModularUI.LOGGER.info("New values: {}", this.configuredOptions); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index 5db39aa54..285b65b92 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -39,11 +39,13 @@ import com.cleanroommc.modularui.utils.fakeworld.ISchema; import com.cleanroommc.modularui.value.BoolValue; import com.cleanroommc.modularui.value.IntValue; +import com.cleanroommc.modularui.value.ObjectValue; import com.cleanroommc.modularui.value.StringValue; import com.cleanroommc.modularui.widget.DraggableWidget; import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widgets.ButtonWidget; import com.cleanroommc.modularui.widgets.ColorPickerDialog; +import com.cleanroommc.modularui.widgets.DropdownWidget; import com.cleanroommc.modularui.widgets.ListWidget; import com.cleanroommc.modularui.widgets.RichTextWidget; import com.cleanroommc.modularui.widgets.SchemaWidget; @@ -55,7 +57,6 @@ 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; @@ -88,6 +89,10 @@ public class TestGuis extends CustomModularScreen { public static boolean withCode = false; + public TestGuis() { + super(ModularUI.ID); + } + @Override public @NotNull ModularPanel buildUI(ModularGuiContext context) { // collect all test from all build methods in this class via reflection @@ -539,47 +544,46 @@ public static ModularPanel buildCollapseDisabledChildrenUI() { 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()); + ObjectValue itemValue = new ObjectValue<>(ItemStack.class, new ItemStack(Items.ACACIA_DOOR)); 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<>() + .child(new ContextMenuButton<>("menu") .top(7) .width(100) .horizontalCenter() .height(16) .overlay(IKey.str("Menu")) - .menuList(new ContextMenuList<>("menu1") - .widthRel(1f) + .menuList(l -> l .maxSize(80) .children(options1, s -> IKey.str(s).asWidget()) - .child(new ContextMenuButton<>() + .child(new ContextMenuButton<>("sub_menu") + .widthRel(1f) + .height(12) .overlay(IKey.str("Sub Menu")) .openRightDown() - .menuList(new ContextMenuList<>("menu2") - .coverChildrenWidth() + .menuList(l1 -> l1 //.width(90) .maxSize(80) - .children(options2, s -> IKey.str(s).asWidget()))))); + .children(options2, s -> IKey.str(s).asWidget()))))) + .child(new DropdownWidget<>("test_dropdown", ItemStack.class) + .top(45) + .width(100) + .horizontalCenter() + .value(itemValue) + .option(new ItemStack(Items.ACACIA_DOOR)) + .option(new ItemStack(Items.GOLD_INGOT)) + .option(new ItemStack(Items.APPLE)) + .option(new ItemStack(Items.FURNACE_MINECART)) + .option(new ItemStack(Items.IRON_SHOVEL)) + .option(new ItemStack(Items.STICK)) + .option(new ItemStack(Items.NETHER_STAR)) + .optionToWidget(i -> Flow.row() + .coverChildrenHeight() + .padding(4, 1) + .mainAxisAlignment(Alignment.MainAxis.SPACE_BETWEEN) + .child(new ItemDrawable(i).asWidget()) + .child(IKey.str(i.getDisplayName()).asWidget())) + ); } public static @NotNull ModularPanel buildGraphUI() { diff --git a/src/main/java/com/cleanroommc/modularui/test/TestItem.java b/src/main/java/com/cleanroommc/modularui/test/TestItem.java index 90417fdcc..152ea00aa 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestItem.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestItem.java @@ -7,6 +7,7 @@ import com.cleanroommc.modularui.factory.PlayerInventoryGuiData; import com.cleanroommc.modularui.factory.inventory.InventoryTypes; import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.UISettings; import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.utils.ItemCapabilityProvider; @@ -49,6 +50,11 @@ public class TestItem extends Item implements IGuiHolder public static final TestItem testItem = new TestItem(); + @Override + public ModularScreen createScreen(PlayerInventoryGuiData data, ModularPanel mainPanel) { + return new ModularScreen(ModularUI.ID, mainPanel); + } + @Override public ModularPanel buildUI(PlayerInventoryGuiData guiData, PanelSyncManager guiSyncManager, UISettings settings) { IItemHandlerModifiable itemHandler = (IItemHandlerModifiable) guiData.getUsedItemStack().getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile.java b/src/main/java/com/cleanroommc/modularui/test/TestTile.java index eadcd5fca..dfc1f22f2 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile.java @@ -594,10 +594,10 @@ public void buildDialog(Dialog dialog) { AtomicReference value = new AtomicReference<>(""); dialog.setDraggable(true); dialog.child(new TextFieldWidget() - .flex(flex -> flex.size(100, 20).align(Alignment.Center)) + .resizer(flex -> flex.size(100, 20).align(Alignment.Center)) .value(new StringValue.Dynamic(value::get, value::set))) .child(new ButtonWidget<>() - .flex(flex -> flex.size(8, 8).top(5).right(5)) + .resizer(flex -> flex.size(8, 8).top(5).right(5)) .overlay(IKey.str("x")) .onMousePressed(mouseButton -> { dialog.closeWith(value.get()); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestTile2.java b/src/main/java/com/cleanroommc/modularui/test/TestTile2.java index 1850f62eb..0a3f72b32 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestTile2.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestTile2.java @@ -1,11 +1,13 @@ package com.cleanroommc.modularui.test; +import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.IGuiHolder; import com.cleanroommc.modularui.api.IPanelHandler; import com.cleanroommc.modularui.drawable.GuiTextures; import com.cleanroommc.modularui.drawable.ItemDrawable; import com.cleanroommc.modularui.factory.PosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.UISettings; import com.cleanroommc.modularui.value.sync.PanelSyncManager; import com.cleanroommc.modularui.widget.ScrollWidget; @@ -43,6 +45,11 @@ public TestTile2() { } } + @Override + public ModularScreen createScreen(PosGuiData data, ModularPanel mainPanel) { + return new ModularScreen(ModularUI.ID, mainPanel); + } + @Override public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { ScrollWidget sw = new ScrollWidget<>(new VerticalScrollData()).size(9 * 18 + 4, 9 * 18).margin(7).top(20); diff --git a/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java b/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java new file mode 100644 index 000000000..4ce291919 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java @@ -0,0 +1,281 @@ +package com.cleanroommc.modularui.utils; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Objects; + +public class MutableSingletonList implements List { + + private boolean hasValue; + private T value; + + public MutableSingletonList() { + remove(); + } + + public MutableSingletonList(T value) { + set(value); + } + + public T get() { + if (!this.hasValue) throw new IndexOutOfBoundsException("List is empty but tried to access index 0!"); + return value; + } + + public void set(T t) { + this.value = t; + this.hasValue = true; + } + + public void remove() { + this.value = null; + this.hasValue = false; + } + + @Override + public int size() { + return hasValue ? 1 : 0; + } + + @Override + public boolean isEmpty() { + return !hasValue; + } + + public boolean hasValue() { + return hasValue; + } + + @Override + public boolean contains(Object o) { + return this.hasValue && Objects.equals(this.value, o); + } + + @Override + public @NotNull Iterator iterator() { + return new Iterator<>() { + + private byte cursor = 0; + + @Override + public boolean hasNext() { + return MutableSingletonList.this.hasValue && this.cursor == 0; + } + + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + this.cursor++; + return MutableSingletonList.this.value; + } + + @Override + public void remove() { + if (this.cursor < 1) throw new IllegalStateException(); + MutableSingletonList.this.remove(); + this.cursor--; + } + }; + } + + @Override + public @NotNull Object @NotNull [] toArray() { + if (!this.hasValue) return new Object[0]; + Object[] o = new Object[1]; + o[0] = this.value; + return o; + } + + @Override + public @NotNull T1 @NotNull [] toArray(@NotNull T1 @NotNull [] a) { + if (!this.hasValue) return a; + if (a.length == 0) a = Arrays.copyOf(a, 1); + a[0] = (T1) this.value; + return a; + } + + @Override + public boolean add(T t) { + if (this.hasValue) throw new IllegalStateException("MutableSingletonList can only have one value, but it already has a value!"); + set(t); + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + remove(); + return true; + } + return false; + } + + @Override + public boolean containsAll(@NotNull Collection c) { + int s = c.size(); + if (s > 1 || (s == 1 != this.hasValue)) return false; + if (!this.hasValue) return true; + if (c instanceof List l) return Objects.equals(this.value, l.get(0)); + return Objects.equals(this.value, c.iterator().next()); + } + + @Override + public boolean addAll(@NotNull Collection c) { + if (this.hasValue || c.isEmpty()) return false; + if (c instanceof List l) { + add((T) l.get(0)); + } else { + add(c.iterator().next()); + } + return true; + } + + private void verifyIndex(int i, boolean checkEmpty) { + if (i != 0) throw new IndexOutOfBoundsException("MutableSingletonList only accepts index 0!"); + if (checkEmpty && !this.hasValue) + throw new IndexOutOfBoundsException("Tried to access index 0, but MutableSingletonList has no element!"); + } + + @Override + public boolean addAll(int index, @NotNull Collection c) { + verifyIndex(index, false); + return addAll(c); + } + + @Override + public boolean removeAll(@NotNull Collection c) { + if (!this.hasValue || c.isEmpty()) return false; + if (c instanceof List l) { + return remove(l.get(0)); + } + return remove(c.iterator().next()); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + if (!this.hasValue || c.contains(this.value)) return false; + remove(); + return true; + } + + @Override + public void clear() { + remove(); + } + + @Override + public T get(int index) { + verifyIndex(index, true); + return null; + } + + @Override + public T set(int index, T element) { + verifyIndex(index, true); + T t = this.value; + this.value = element; + return t; + } + + @Override + public void add(int index, T element) { + verifyIndex(index, false); + add(element); + } + + @Override + public T remove(int index) { + verifyIndex(index, true); + T t = this.value; + remove(); + return t; + } + + @Override + public int indexOf(Object o) { + return contains(o) ? 0 : -1; + } + + @Override + public int lastIndexOf(Object o) { + return indexOf(o); + } + + @Override + public @NotNull ListIterator listIterator() { + return new ListIterator() { + + private byte cursor = 0; + + @Override + public boolean hasNext() { + return MutableSingletonList.this.hasValue && (this.cursor == 0 || this.cursor == -1); + } + + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + this.cursor = 1; + return MutableSingletonList.this.value; + } + + @Override + public boolean hasPrevious() { + return MutableSingletonList.this.hasValue && (this.cursor == 0 || this.cursor == 1); + } + + @Override + public T previous() { + if (!hasNext()) throw new NoSuchElementException(); + this.cursor = -1; + return MutableSingletonList.this.value; + } + + @Override + public int nextIndex() { + return cursor == 0 ? 0 : 1; + } + + @Override + public int previousIndex() { + return cursor == 0 ? 0 : -1; + } + + @Override + public void remove() { + if (this.cursor == 0) throw new IllegalStateException(); + MutableSingletonList.this.remove(); + this.cursor = 0; + } + + @Override + public void set(T t) { + if (this.cursor == 0) throw new IllegalStateException(); + MutableSingletonList.this.set(t); + } + + @Override + public void add(T t) { + MutableSingletonList.this.add(t); + } + }; + } + + @Override + public @NotNull ListIterator listIterator(int index) { + verifyIndex(index, false); + return listIterator(); + } + + @Override + public @NotNull List subList(int fromIndex, int toIndex) { + if (fromIndex < 0 || toIndex > 1 || toIndex < fromIndex) throw new IndexOutOfBoundsException(); + return new MutableSingletonList<>(this.value); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java index 9b4ea8299..f8f535ad7 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java +++ b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java @@ -39,11 +39,6 @@ public class TreeUtil { public static final NodeInfo RESIZE_NODE_INFO_FULLY_RESIZED = (root, node, builder) -> builder .append("Fully resized: ") .append(str(node.isFullyCalculated(node.hasParent() && node.getParent().isLayout()))); - public static final NodeInfo RESIZE_NODE_INFO_SELF_RESIZED_DETAIL = (root, node, builder) -> builder - .append("X: ").append(str(node.isXCalculated())) - .append(", Y: ").append(str(node.isYCalculated())) - .append(", W: ").append(str(node.isWidthCalculated())) - .append(", H: ").append(str(node.isHeightCalculated())); public static final NodeInfo RESIZE_NODE_INFO_RESIZED_DETAILED = (root, node, builder) -> { builder.append("XYWH: ") .append(str(node.isXCalculated())) 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 99b52cef2..b5f8af4d9 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/DimensionSizer.java @@ -173,6 +173,10 @@ private boolean needsSize(Unit unit) { return unit.isRelative() && unit.getAnchor() != 0; } + public boolean test() { + return resizer != null && axis.isVertical() && resizer.toString().contains("menu_list"); + } + public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { boolean sizeCalculated = isSizeCalculated(); boolean posCalculated = isPosCalculated(); @@ -190,7 +194,8 @@ public void apply(Area area, ResizeNode relativeTo, IntSupplier defaultSize) { } else if (this.end != null) { p = calcPoint(this.end, padding, s, parentSize, calcParent) - s; } else { - throw new IllegalStateException(); + p = 0; + this.posCalculated = true; } } else if (posCalculated) { // size not calculated // pos was calculated before diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java index 68c4449aa..fde51deb1 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -119,10 +119,15 @@ protected void setParentOverride(ResizeNode resizeNode) { } } + @ApiStatus.Internal public void setDefaultParentIsDelegating(boolean defaultParentIsDelegating) { this.defaultParentIsDelegating = defaultParentIsDelegating; } + public boolean hasParentOverride() { + return this.parentOverride != null; + } + @Override public void initResizing(boolean onOpen) { if (this.defaultParentIsDelegating && this.parentOverride != null) { diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 418adec2b..3e476c2aa 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -103,7 +103,7 @@ public boolean isYMarginPaddingApplied() { } @Override - public StandardResizer flex() { + public StandardResizer resizer() { return this; } @@ -127,20 +127,6 @@ public void checkExpanded(@Nullable GuiAxis axis) { 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()); diff --git a/src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java new file mode 100644 index 000000000..802b33275 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java @@ -0,0 +1,132 @@ +package com.cleanroommc.modularui.widgets; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.value.ISyncOrValue; +import com.cleanroommc.modularui.api.value.IValue; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.utils.MutableSingletonList; +import com.cleanroommc.modularui.widgets.menu.AbstractMenuButton; +import com.cleanroommc.modularui.widgets.menu.Menu; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +public class DropdownWidget> extends AbstractMenuButton { + + private final Class valueType; + private final MutableSingletonList selected = new MutableSingletonList<>(); + private final List values = new ArrayList<>(); + private IValue value; + private int maxListSize = 100; + private Function toWidget; + + public DropdownWidget(String panelName, Class valueType) { + super(panelName); + this.valueType = valueType; + this.openOnHover = false; + } + + @Override + public void onInit() { + super.onInit(); + setValue(this.value.getValue(), false); + } + + @Override + public @NotNull List getChildren() { + return selected; + } + + protected IWidget valueToWidget(T v) { + if (this.toWidget != null) { + return this.toWidget.apply(v); + } + return IKey.str(String.valueOf(v)).asWidget(); + } + + protected void setValue(T value, boolean updateValue) { + if (this.selected.hasValue()) { + this.selected.get().dispose(); + } + if (updateValue) this.value.setValue(value); + this.selected.set(valueToWidget(value)); + if (isValid()) { + this.selected.get().initialise(this, true); + scheduleResize(); + } + } + + @Override + protected Menu createMenu() { + return new Menu<>() + .widthRel(1f) + .coverChildrenHeight() + .child(new ListWidget<>() + .widthRel(1f) + .maxSize(this.maxListSize) + .children(this.values, v -> new ButtonWidget<>() + .widthRel(1f) + .coverChildrenHeight() + .child(valueToWidget(v)) + .onMousePressed(b -> { + setValue(v, true); + closeMenu(false); + return true; + }))); + } + + @Override + public boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + return syncOrValue.isValueOfType(this.valueType); + } + + @Override + protected void setSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + super.setSyncOrValue(syncOrValue); + this.value = syncOrValue.castValueNullable(this.valueType); + } + + public W value(IValue value) { + setSyncOrValue(value); + return getThis(); + } + + public W option(T option) { + this.values.add(option); + return getThis(); + } + + public W options(Iterable options) { + for (T t : options) this.values.add(t); + return getThis(); + } + + public W options(T... options) { + this.values.addAll(Arrays.asList(options)); + return getThis(); + } + + public W optionToWidget(Function toWidget) { + this.toWidget = toWidget; + return getThis(); + } + + public W maxVerticalMenuSize(int maxListSize) { + this.maxListSize = maxListSize; + return getThis(); + } + + public W directionUp() { + this.direction = Direction.UP; + return getThis(); + } + + public W directionDown() { + this.direction = Direction.DOWN; + return getThis(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java new file mode 100644 index 000000000..bdcdf45be --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java @@ -0,0 +1,179 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.IPanelHandler; +import com.cleanroommc.modularui.api.ITheme; +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.StandardResizer; + +import net.minecraft.util.text.TextFormatting; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.function.Consumer; + +@ApiStatus.Experimental +public abstract class AbstractMenuButton> extends Widget implements IMenuPart, Interactable { + + protected Direction direction = Direction.DOWN; + protected boolean openOnHover = true; + + private Menu menu; + private boolean open, softOpen; + private IPanelHandler panelHandler; + private final String panelName; + + public AbstractMenuButton(String panelName) { + this.panelName = Objects.requireNonNull(panelName); + name(panelName); + } + + public boolean isOpen() { + return open; + } + + protected boolean isSoftOpen() { + return softOpen; + } + + protected 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); + } + } + + protected void openMenu(boolean soft) { + if (this.open) { + if (this.softOpen && !soft) { + this.softOpen = false; + } + return; + } + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.openSubMenu(getMenu()); + } else { + getPanelHandler().openPanel(); + } + this.open = true; + this.softOpen = soft; + } + + protected void closeMenu(boolean soft) { + if (!this.open || (!this.softOpen && soft)) return; + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.remove(getMenu()); + } else { + getPanelHandler().closePanel(); + } + this.open = false; + this.softOpen = false; + } + + protected Menu getMenu() { + if (this.menu == null) { + this.menu = createMenu(); + if (this.menu == null) { + this.menu = new Menu<>() + .child(IKey.str("No Menu supplied") + .style(TextFormatting.RED) + .asWidget() + .center()) + .widthRel(1f) + .height(16); + if (this.direction == null) Direction.DOWN.positioner.accept(this.menu.resizer()); + } + } + if (!this.menu.resizer().hasParentOverride()) { + this.menu.resizer().relative(this); + } + if (this.direction != null) { + this.direction.positioner.accept(this.menu.resizer()); + } + this.menu.setMenuSource(this); + return this.menu; + } + + protected void setMenu(Menu menu) { + this.menu = menu; + } + + protected abstract Menu createMenu(); + + private IPanelHandler getPanelHandler() { + if (this.panelHandler == null) { + this.panelHandler = IPanelHandler.simple(getPanel(), (parentPanel, player) -> new MenuPanel(this.panelName, getMenu()), true); + } + return this.panelHandler; + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + toggleMenu(false); + return Result.SUCCESS; + } + + @Override + public void onMouseEnterArea() { + super.onMouseEnterArea(); + if (this.openOnHover) { + openMenu(true); + } + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + protected void checkClose() { + if (this.openOnHover && !isSelfOrChildHovered()) { + closeMenu(true); + if (getParent() instanceof Menu parentMenuList) { + parentMenuList.checkClose(); + } + } + } + + @Override + public boolean isSelfOrChildHovered() { + if (isBelowMouse()) return true; + if (!isOpen() || this.menu == null) return false; + return this.menu.isSelfOrChildHovered(); + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getButtonTheme(); + } + + 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/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java index e20fe0415..42761cc12 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -1,150 +1,37 @@ package com.cleanroommc.modularui.widgets.menu; -import com.cleanroommc.modularui.api.IPanelHandler; -import com.cleanroommc.modularui.api.ITheme; -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.StandardResizer; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widgets.ListWidget; 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 { +public class ContextMenuButton> extends AbstractMenuButton { - 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 Widget<>() - .widthRel(1f) - .height(12) - .overlay(IKey.str("No options supplied"))); - } - this.menuList.setSource(this); - this.menuList.relative(this); - this.direction.positioner.accept(this.menuList.resizer()); - } - - private IPanelHandler getPanelHandler() { - if (this.panelHandler == null) { - this.panelHandler = IPanelHandler.simple(getPanel(), (parentPanel, player) -> new MenuPanel(getMenuList()), true); - } - return this.panelHandler; + public ContextMenuButton(String panelName) { + super(panelName); + this.openOnHover = true; } @Override - public @NotNull Result onMousePressed(int mouseButton) { - toggleMenu(false); - return Result.SUCCESS; + protected Menu createMenu() { + return null; } - @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 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 theme.getButtonTheme(); + public W menu(Menu menu) { + setMenu(menu); + return getThis(); } - public W menuList(ContextMenuList menuList) { - this.menuList = menuList; - return getThis(); + public W menuList(Consumer> builder) { + ListWidget l = new ListWidget<>().widthRel(1f); + builder.accept(l); + return menu(new Menu<>() + .widthRel(1f) + .coverChildrenHeight() + .child(l)); } public W direction(Direction direction) { @@ -153,7 +40,7 @@ public W direction(Direction direction) { } public W requiresClick() { - this.requiresClick = true; + this.openOnHover = false; return getThis(); } @@ -185,19 +72,4 @@ 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 deleted file mode 100644 index 77272d375..000000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java +++ /dev/null @@ -1,73 +0,0 @@ -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; - -@ApiStatus.Experimental -public class ContextMenuList> extends ListWidget { - - private ContextMenuButton source; - - public ContextMenuList(String name) { - name(name); - padding(2); - } - - public void close() { - if (this.source != null) { - this.source.closeMenu(false); - } - } - - 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.resizer().hasHeight()) { - child.resizer().height(12); - } - if (!child.resizer().hasWidth()) { - child.resizer().widthRel(1f); - } - } - - @Override - protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { - return theme.getWidgetTheme(IThemeApi.PANEL); - } - - 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/IContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java deleted file mode 100644 index 0f5c2610e..000000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.cleanroommc.modularui.widgets.menu; - -import com.cleanroommc.modularui.api.widget.IWidget; - -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Experimental -public interface IContextMenuOption extends IWidget { - - default boolean isSelfOrChildHovered() { - return isBelowMouse(); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/IMenuPart.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/IMenuPart.java new file mode 100644 index 000000000..b5405ca19 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/IMenuPart.java @@ -0,0 +1,13 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widget.WidgetTree; + +public interface IMenuPart extends IWidget { + + default boolean isSelfOrChildHovered() { + return isBelowMouse() || !WidgetTree.foreachChild(this, + w -> !(w instanceof IMenuPart menuPart ? menuPart.isSelfOrChildHovered() : w.isBelowMouse()), + false); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/Menu.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/Menu.java new file mode 100644 index 000000000..2b577e7f5 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/Menu.java @@ -0,0 +1,45 @@ +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.ParentWidget; + +public class Menu> extends ParentWidget implements IMenuPart { + + private AbstractMenuButton menuSource; + + void setMenuSource(AbstractMenuButton source) { + this.menuSource = source; + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + protected void checkClose() { + if (this.menuSource != null && !this.menuSource.isBelowMouse() && !isSelfOrChildHovered()) { + this.menuSource.closeMenu(true); + this.menuSource.checkClose(); + } + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + if (!child.resizer().hasHeight()) { + child.resizer().height(12); + } + if (!child.resizer().hasWidth()) { + child.resizer().widthRel(1f); + } + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getWidgetTheme(IThemeApi.PANEL); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java index 392b1dbdb..6ce837a83 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java @@ -8,14 +8,14 @@ @ApiStatus.Experimental public class MenuPanel extends ModularPanel { - public MenuPanel(ContextMenuList menuList) { - super(menuList.getName()); + public MenuPanel(String name, IWidget menu) { + super(name); fullScreenInvisible(); - child(menuList); + child(menu); themeOverride("modularui.context_menu"); } - public void openSubMenu(ContextMenuList menuList) { + public void openSubMenu(IWidget menuList) { child(menuList); } From 56bc9c5e7fd36cb70d9e73f8a2d6869fa1cf5f4b Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 31 Jan 2026 10:47:21 +0100 Subject: [PATCH 15/20] javadoc --- .../com/cleanroommc/modularui/ModularUI.java | 3 + .../modularui/screen/CustomModularScreen.java | 4 +- .../modularui/screen/ModularScreen.java | 4 +- .../cleanroommc/modularui/test/TestGuis.java | 10 +- .../modularui/widgets/DropdownWidget.java | 132 ----------- .../widgets/menu/AbstractMenuButton.java | 38 ++- .../widgets/menu/ContextMenuButton.java | 90 ++++++- .../widgets/menu/DropdownWidget.java | 222 ++++++++++++++++++ 8 files changed, 355 insertions(+), 148 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/DropdownWidget.java diff --git a/src/main/java/com/cleanroommc/modularui/ModularUI.java b/src/main/java/com/cleanroommc/modularui/ModularUI.java index 5bf867b34..2f9ec2b47 100644 --- a/src/main/java/com/cleanroommc/modularui/ModularUI.java +++ b/src/main/java/com/cleanroommc/modularui/ModularUI.java @@ -7,6 +7,7 @@ import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.event.FMLServerStartingEvent; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -36,6 +37,8 @@ public class ModularUI { serverSide = "com.cleanroommc.modularui.CommonProxy") public static CommonProxy proxy; + public static final boolean isDev = FMLLaunchHandler.isDeobfuscatedEnvironment(); + @Mod.Instance public static ModularUI INSTANCE; diff --git a/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java index c9452f51b..dd0f9aeb0 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/CustomModularScreen.java @@ -22,7 +22,9 @@ public abstract class CustomModularScreen extends ModularScreen { @Deprecated public CustomModularScreen() { super(ModularUI.ID); - ModularUI.LOGGER.error("The single arg ModularScreen constructor should not be used. Use the other one and pass in your mod id."); + if (ModularUI.isDev) { + ModularUI.LOGGER.error("The single arg ModularScreen constructor should not be used. Use the other one and pass in your mod id."); + } } /** diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java index 5c4f2ae68..30db01e8f 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularScreen.java @@ -96,7 +96,9 @@ public static ModularScreen getCurrent() { @ApiStatus.ScheduledForRemoval(inVersion = "3.2.0") public ModularScreen(@NotNull ModularPanel mainPanel) { this(ModularUI.ID, mainPanel); - ModularUI.LOGGER.warn("The single arg ModularScreen constructor should not be used. Use the any of the other ones and pass in your mod id."); + if (ModularUI.isDev) { + ModularUI.LOGGER.warn("The single arg ModularScreen constructor should not be used. Use the any of the other ones and pass in your mod id."); + } } /** diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index 285b65b92..65997da28 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -45,7 +45,6 @@ import com.cleanroommc.modularui.widget.Widget; import com.cleanroommc.modularui.widgets.ButtonWidget; import com.cleanroommc.modularui.widgets.ColorPickerDialog; -import com.cleanroommc.modularui.widgets.DropdownWidget; import com.cleanroommc.modularui.widgets.ListWidget; import com.cleanroommc.modularui.widgets.RichTextWidget; import com.cleanroommc.modularui.widgets.SchemaWidget; @@ -57,6 +56,7 @@ 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.DropdownWidget; import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.client.Minecraft; @@ -577,12 +577,14 @@ public static ModularPanel buildContextMenu() { .option(new ItemStack(Items.IRON_SHOVEL)) .option(new ItemStack(Items.STICK)) .option(new ItemStack(Items.NETHER_STAR)) - .optionToWidget(i -> Flow.row() + .optionToWidget((item, forSelected) -> Flow.row() .coverChildrenHeight() .padding(4, 1) .mainAxisAlignment(Alignment.MainAxis.SPACE_BETWEEN) - .child(new ItemDrawable(i).asWidget()) - .child(IKey.str(i.getDisplayName()).asWidget())) + .child(new ItemDrawable(item).asWidget()) + .child(IKey.str(item.getDisplayName()).asWidget() + .widgetTheme(IThemeApi.BUTTON) + .invisible())) ); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java deleted file mode 100644 index 802b33275..000000000 --- a/src/main/java/com/cleanroommc/modularui/widgets/DropdownWidget.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.cleanroommc.modularui.widgets; - -import com.cleanroommc.modularui.api.drawable.IKey; -import com.cleanroommc.modularui.api.value.ISyncOrValue; -import com.cleanroommc.modularui.api.value.IValue; -import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.utils.MutableSingletonList; -import com.cleanroommc.modularui.widgets.menu.AbstractMenuButton; -import com.cleanroommc.modularui.widgets.menu.Menu; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - -public class DropdownWidget> extends AbstractMenuButton { - - private final Class valueType; - private final MutableSingletonList selected = new MutableSingletonList<>(); - private final List values = new ArrayList<>(); - private IValue value; - private int maxListSize = 100; - private Function toWidget; - - public DropdownWidget(String panelName, Class valueType) { - super(panelName); - this.valueType = valueType; - this.openOnHover = false; - } - - @Override - public void onInit() { - super.onInit(); - setValue(this.value.getValue(), false); - } - - @Override - public @NotNull List getChildren() { - return selected; - } - - protected IWidget valueToWidget(T v) { - if (this.toWidget != null) { - return this.toWidget.apply(v); - } - return IKey.str(String.valueOf(v)).asWidget(); - } - - protected void setValue(T value, boolean updateValue) { - if (this.selected.hasValue()) { - this.selected.get().dispose(); - } - if (updateValue) this.value.setValue(value); - this.selected.set(valueToWidget(value)); - if (isValid()) { - this.selected.get().initialise(this, true); - scheduleResize(); - } - } - - @Override - protected Menu createMenu() { - return new Menu<>() - .widthRel(1f) - .coverChildrenHeight() - .child(new ListWidget<>() - .widthRel(1f) - .maxSize(this.maxListSize) - .children(this.values, v -> new ButtonWidget<>() - .widthRel(1f) - .coverChildrenHeight() - .child(valueToWidget(v)) - .onMousePressed(b -> { - setValue(v, true); - closeMenu(false); - return true; - }))); - } - - @Override - public boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) { - return syncOrValue.isValueOfType(this.valueType); - } - - @Override - protected void setSyncOrValue(@NotNull ISyncOrValue syncOrValue) { - super.setSyncOrValue(syncOrValue); - this.value = syncOrValue.castValueNullable(this.valueType); - } - - public W value(IValue value) { - setSyncOrValue(value); - return getThis(); - } - - public W option(T option) { - this.values.add(option); - return getThis(); - } - - public W options(Iterable options) { - for (T t : options) this.values.add(t); - return getThis(); - } - - public W options(T... options) { - this.values.addAll(Arrays.asList(options)); - return getThis(); - } - - public W optionToWidget(Function toWidget) { - this.toWidget = toWidget; - return getThis(); - } - - public W maxVerticalMenuSize(int maxListSize) { - this.maxListSize = maxListSize; - return getThis(); - } - - public W directionUp() { - this.direction = Direction.UP; - return getThis(); - } - - public W directionDown() { - this.direction = Direction.DOWN; - return getThis(); - } -} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java index bdcdf45be..225846bfc 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java @@ -6,36 +6,63 @@ import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.theme.WidgetThemeEntry; import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widget.WidgetTree; import com.cleanroommc.modularui.widget.sizer.StandardResizer; import net.minecraft.util.text.TextFormatting; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.Objects; import java.util.function.Consumer; -@ApiStatus.Experimental +/** + * This is the base class for a button that can open a floating widget by clicking or hovering the button. In ModularUI this is used for + * context menus and dropdown menus. When the menu is opened a new panel is created and the {@link Menu} widget is added. If that menu + * contains another one of these menu buttons, it will be added to that panel. + * + * @param type of this widget + */ public abstract class AbstractMenuButton> extends Widget implements IMenuPart, Interactable { + /** + * The general direction where the menu will be opened. This is just a shortcut to standard resizer calls. + * If this is null you can customize the position yourself. + */ protected Direction direction = Direction.DOWN; + /** + * If this is true, the menu can be opened when the mouse hovers this button. The menu will automatically close, when the button AND + * all widgets in the menus tree are no longer hovered. + */ protected boolean openOnHover = true; + /** + * The current menu widget. The menu will be created with {@link #createMenu()} if the menu is null when it's needed. If the method + * also returns a null value, a default menu is created. The menu can be set at any time with {@link #setMenu(Menu)}. + */ private Menu menu; - private boolean open, softOpen; + private boolean open, softOpen; // state, soft means opened by hovering private IPanelHandler panelHandler; private final String panelName; + /** + * @param panelName the name for the panel that may be created when opening the menu + */ public AbstractMenuButton(String panelName) { this.panelName = Objects.requireNonNull(panelName); name(panelName); } + /** + * @return true if the menu is currently open (soft or hard) + */ public boolean isOpen() { return open; } + /** + * @return true if the menu is currently soft open (opened by hovering) + */ protected boolean isSoftOpen() { return softOpen; } @@ -143,8 +170,9 @@ public void onMouseLeaveArea() { protected void checkClose() { if (this.openOnHover && !isSelfOrChildHovered()) { closeMenu(true); - if (getParent() instanceof Menu parentMenuList) { - parentMenuList.checkClose(); + Menu menuParent = WidgetTree.findParent(this, Menu.class); + if (menuParent != null) { + menuParent.checkClose(); } } } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java index 42761cc12..496323310 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -1,15 +1,21 @@ package com.cleanroommc.modularui.widgets.menu; +import com.cleanroommc.modularui.api.widget.IPositioned; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.widgets.ListWidget; -import org.jetbrains.annotations.ApiStatus; - import java.util.function.Consumer; -@ApiStatus.Experimental +/** + * A button that opens a context menu on click or hover. + * + * @param type of this widget + */ public class ContextMenuButton> extends AbstractMenuButton { + /** + * @param panelName the panel name that the menu may create + */ public ContextMenuButton(String panelName) { super(panelName); this.openOnHover = true; @@ -17,14 +23,30 @@ public ContextMenuButton(String panelName) { @Override protected Menu createMenu() { + // menu is created by the user with the setter return null; } + /** + * Sets the menu widget. The menu will by default be relative to this button. + * It is common to use {@link IPositioned#widthRel(float)} and {@link IPositioned#coverChildrenHeight()}. + * The {@link #direction(Direction)} will handle the position of the menu, but can be customized if {@link #openCustom()} is used. + * + * @param menu displayed menu + * @return this + */ public W menu(Menu menu) { setMenu(menu); return getThis(); } + /** + * This is a shortcut that is meant to be used when a simple list of options should be displayed. It is recommended to call + * {@link ListWidget#maxSize(int)} to limit the size. This is method is not suited for any customization on the actual menu widget. + * + * @param builder list builder which is called exactly once + * @return this + */ public W menuList(Consumer> builder) { ListWidget l = new ListWidget<>().widthRel(1f); builder.accept(l); @@ -34,42 +56,100 @@ public W menuList(Consumer> builder) { .child(l)); } + /** + * Sets the general direction in which the menu should open. This is just a shortcut to {@link IPositioned} position calls. + * Use {@link #openCustom()} if none of the predefined options suit your needs. You can then position the menu yourself. + * + * @param direction general direction to open the menu in + * @return this + */ public W direction(Direction direction) { this.direction = direction; return getThis(); } + /** + * Sets this button to require a click to open the menu. Hovering this button will not open it. + * + * @return this + */ public W requiresClick() { - this.openOnHover = false; + return openOnHover(false); + } + + /** + * Sets whether the menu should be opened when this button is hovered by the mouse. + * + * @param openOnHover true if the menu can be opened by hover + * @return this + */ + public W openOnHover(boolean openOnHover) { + this.openOnHover = openOnHover; return getThis(); } + /** + * Sets the menu to open in the "up" direction. This does not set a horizontal position. + * This is best used with {@link IPositioned#widthRel(float) IPositioned.widthRel(1f)} + * + * @return this + */ public W openUp() { return direction(Direction.UP); } + /** + * Sets the menu to open in the "down" direction. This does not set a horizontal position. + * This is best used with {@link IPositioned#widthRel(float) IPositioned.widthRel(1f)} + * + * @return this + */ public W openDown() { return direction(Direction.DOWN); } + /** + * Sets the menu to open in the "left and up" direction. + * + * @return this + */ public W openLeftUp() { return direction(Direction.LEFT_UP); } + /** + * Sets the menu to open in the "left and down" direction. + * + * @return this + */ public W openLeftDown() { return direction(Direction.LEFT_DOWN); } + /** + * Sets the menu to open in the "right and up" direction. + * + * @return this + */ public W openRightUp() { return direction(Direction.RIGHT_UP); } + /** + * Sets the menu to open in the "right and down" direction. + * + * @return this + */ public W openRightDown() { return direction(Direction.RIGHT_DOWN); } + /** + * Sets the menu to open in no specified direction. The position of the menu must be set manually, or it is left at 0,0. + * + * @return this + */ public W openCustom() { return direction(Direction.UNDEFINED); } - } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/DropdownWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/DropdownWidget.java new file mode 100644 index 000000000..fc984c452 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/DropdownWidget.java @@ -0,0 +1,222 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.value.ISyncOrValue; +import com.cleanroommc.modularui.api.value.IValue; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.utils.MutableSingletonList; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ListWidget; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A button which displays a list of options when clicked. When an option is clicked, the menu closes and the button displays that selected + * option. To use it, supply the appropriate value type you want to use to the constructor. This is used to validate synced values. + * Add options that can be selected with {@link #option(Object)}, {@link #options(Iterable)} or {@link #options(Object[])}. + * Finally set a function that converts an option to a widget using {@link #optionToWidget(ToWidget)}. When a value is selected, it will + * sync to any set value handler. + * + * @param type of the values used + * @param type of this widget + */ +public class DropdownWidget> extends AbstractMenuButton { + + private final Class valueType; + private final MutableSingletonList selected = new MutableSingletonList<>(); + private final List values = new ArrayList<>(); + private IValue value; + private int maxListSize = 100; + private ToWidget toWidget; + + public DropdownWidget(String panelName, Class valueType) { + super(panelName); + this.valueType = valueType; + this.openOnHover = false; + } + + @Override + public void onInit() { + super.onInit(); + setValue(this.value.getValue(), false); + } + + @Override + public @NotNull List getChildren() { + return selected; + } + + protected IWidget valueToWidget(T v, boolean forSelectedDisplay) { + if (this.toWidget != null) { + return this.toWidget.apply(v, forSelectedDisplay); + } + return IKey.str(String.valueOf(v)).asWidget(); + } + + protected void setValue(T value, boolean updateValue) { + if (this.selected.hasValue()) { + this.selected.get().dispose(); + } + if (updateValue && this.value != null) this.value.setValue(value); + this.selected.set(valueToWidget(value, true)); + if (isValid()) { + this.selected.get().initialise(this, true); + scheduleResize(); + } + } + + @Override + protected Menu createMenu() { + return new Menu<>() + .widthRel(1f) + .coverChildrenHeight() + .child(new ListWidget<>() + .widthRel(1f) + .maxSize(this.maxListSize) + .children(this.values, v -> new ButtonWidget<>() + .widthRel(1f) + .coverChildrenHeight() + .child(valueToWidget(v, false)) + .onMousePressed(b -> { + setValue(v, true); + closeMenu(false); + return true; + }))); + } + + @Override + public boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + return syncOrValue.isValueOfType(this.valueType); + } + + @Override + protected void setSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + super.setSyncOrValue(syncOrValue); + this.value = syncOrValue.castValueNullable(this.valueType); + } + + /** + * Deletes the current cached menu. This can be used if the list of options changes while the menu is open. + * If the menu is currently open it won't be affected. + */ + public void deleteMenu() { + setMenu(null); + } + + /** + * Sets a value handler that handles the selected value. + * + * @param value value handler + * @return this + */ + public W value(IValue value) { + setSyncOrValue(value); + return getThis(); + } + + /** + * Adds an option that can be selected. + * + * @param option selectable option + * @return this + */ + public W option(T option) { + this.values.add(option); + return getThis(); + } + + /** + * Adds an iterable of selectable options. + * + * @param options selectable options + * @return this + */ + public W options(Iterable options) { + for (T t : options) this.values.add(t); + return getThis(); + } + + /** + * Adds an array of selectable options. + * + * @param options selectable options + * @return this + */ + public W options(T... options) { + this.values.addAll(Arrays.asList(options)); + return getThis(); + } + + /** + * Clears all currently set selectable options. + * + * @return this + */ + public W clearOptions() { + this.values.clear(); + return getThis(); + } + + /** + * Sets a function which converts options into displayable widgets. The function is called once for each option when the menu is created + * and each time when a value is selected. The function itself has a value and boolean parameter. The value is the option which is + * displayed and the boolean is true if the widget is created for the selected display. There is no limit to what combination of widgets + * an option can be. Usually it is a text or a row with an icon and text. + * If this function is not a set {@link String#valueOf(Object)} is used to display text. + * + * @param toWidget function to create a display widget from an option + * @return this + * @see ToWidget + */ + public W optionToWidget(ToWidget toWidget) { + this.toWidget = toWidget; + return getThis(); + } + + /** + * Sets the maximum size of the list widget in pixel. By default, it is set to 100. + * + * @param maxListSize maximum list size in pixel + * @return this + */ + public W maxVerticalMenuSize(int maxListSize) { + this.maxListSize = maxListSize; + return getThis(); + } + + /** + * Sets the menu to open in the "up" direction. + * + * @return this + */ + public W directionUp() { + this.direction = Direction.UP; + return getThis(); + } + + /** + * Sets the menu to open in the "down" direction. + * + * @return this + */ + public W directionDown() { + this.direction = Direction.DOWN; + return getThis(); + } + + public interface ToWidget { + + /** + * A function to convert a value into a display widget. + * + * @param value value to display + * @param forSelectedDisplay if the widget is used on the button when the menu is closed + * @return this + */ + IWidget apply(T value, boolean forSelectedDisplay); + } +} From 1fb4e3c1e37b9d971cf32cd0b41d8ca66dec3e70 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 31 Jan 2026 11:07:18 +0100 Subject: [PATCH 16/20] relativeToScreen works again --- .../cleanroommc/modularui/screen/ModularPanel.java | 2 +- .../modularui/widget/AbstractWidget.java | 4 ++-- .../modularui/widget/sizer/ResizeNode.java | 5 +++++ .../modularui/widget/sizer/StandardResizer.java | 13 ++++++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java index e320c313b..d70d56f7c 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ModularPanel.java @@ -237,7 +237,7 @@ public boolean canHover() { public void onOpen(ModularScreen screen) { this.screen = screen; getArea().z(1); - resizer().setDefaultParent(this.screen.getResizeNode()); + resizer().initialize(this.screen.getResizeNode(), this.screen.getResizeNode()); initialise(this, false); // call first tick after everything is initialised WidgetTree.onUpdate(this); diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java index 237e4e50a..c20e6f405 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -77,9 +77,9 @@ public final void initialise(@NotNull IWidget parent, boolean late) { this.context = parent.getContext(); getArea().z(parent.getArea().z() + 1); if (parent instanceof AbstractWidget aw) { - this.resizer.setDefaultParent(aw.resizer); + this.resizer.initialize(aw.resizer, parent.getScreen().getResizeNode()); } else { - this.resizer.setDefaultParent(parent.resizer()); + this.resizer.initialize(parent.resizer(), parent.getScreen().getResizeNode()); } } this.valid = 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 index fde51deb1..12692547c 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/ResizeNode.java @@ -96,6 +96,11 @@ private boolean removeFromParent(ResizeNode parent, ResizeNode parent2, ResizeNo return false; } + @ApiStatus.Internal + public void initialize(ResizeNode defaultParent, ResizeNode root) { + setDefaultParent(defaultParent); + } + @ApiStatus.Internal public void setDefaultParent(ResizeNode resizeNode) { //ModularUI.LOGGER.info("Set default parent of {} to {}. Current: default: {}, override: {}", this, resizeNode, this.defaultParent, this.parentOverride); diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 3e476c2aa..994b58c6b 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -26,6 +26,7 @@ public class StandardResizer extends WidgetResizeNode implements IPositioned Date: Sat, 31 Jan 2026 11:28:05 +0100 Subject: [PATCH 17/20] reorganize IWidget --- .../modularui/api/widget/IWidget.java | 271 +++++++++++------- 1 file changed, 173 insertions(+), 98 deletions(-) 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 8a1eb5faf..c0e11d6d8 100644 --- a/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java @@ -5,6 +5,7 @@ import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.drawable.Stencil; import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; import com.cleanroommc.modularui.theme.WidgetThemeEntry; import com.cleanroommc.modularui.widget.sizer.Area; @@ -19,87 +20,53 @@ import java.util.function.Consumer; /** - * A widget in a Gui + * A widget in a Gui. */ public interface IWidget extends IGuiElement, ITreeNode { /** - * Validates and initialises this element. - * This element now becomes valid - * - * @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 + * @return the screen this element is in */ - void initialise(@NotNull IWidget parent, boolean late); + ModularScreen getScreen(); /** - * Invalidates this element. + * @return the parent of this widget */ - void dispose(); + @NotNull + @Override + IWidget getParent(); - /** - * Determines if this element exist in an active gui. - * - * @return if this is in a valid gui - */ - boolean isValid(); + @Override + default boolean hasParent() { + return isValid(); + } /** - * Draws the background of this widget. - * - * @param context gui context - * @param widgetTheme widget theme of this widget + * @return the context the current screen */ - default void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + ModularGuiContext getContext(); /** - * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetThemeEntry)} and before - * {@link #drawOverlay(ModularGuiContext, WidgetThemeEntry)} - * - * @param context gui context - * @param widgetTheme widget theme + * @return the panel this widget is in */ - default void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + @NotNull + ModularPanel getPanel(); /** - * Draws the overlay of this theme. - * - * @param context gui context - * @param widgetTheme widget theme + * @return the area this widget occupies */ - default void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + @Override + Area getArea(); /** - * Draws foreground elements of this widget. For example tooltips. - * No transformations are applied here. + * Shortcut to get the area of the parent * - * @param context gui context + * @return parent area */ - default void drawForeground(ModularGuiContext context) {} - - default void transform(IViewportStack stack) { - stack.translate(getArea().rx, getArea().ry, 0); + default Area getParentArea() { + return getParent().getArea(); } - default Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { - return null; - } - - default WidgetThemeEntry getWidgetTheme(ITheme theme) { - return theme.getFallback(); - } - - /** - * Called 20 times per second. - */ - default void onUpdate() {} - - /** - * @return the area this widget occupies - */ - @Override - Area getArea(); - /** * Calculates if a given pos is inside this widgets area. * This should be used over {@link Area#isInside(int, int)}, since this accounts for transformations. @@ -134,45 +101,49 @@ default boolean isInside(IViewportStack stack, int mx, int my, boolean absolute) } /** - * @return all children of this widget + * Called when the mouse hovers this element. This means this element is directly below the mouse or there are widgets in between which + * all allow to pass hover through. This is not called when the element is at any point below the mouse. */ - @NotNull - @Override - default List getChildren() { - return Collections.emptyList(); - } + default void onMouseStartHover() {} /** - * @return if this widget has any children + * Called when the mouse no longer hovers this element. This widget can still be below the mouse on some level. */ - @Override - default boolean hasChildren() { - return !getChildren().isEmpty(); - } + default void onMouseEndHover() {} /** - * 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. - * - * @return if this element is enabled + * Called when the mouse enters this elements area with any amount of widgets above it from the current panel. */ - @Override - boolean isEnabled(); + default void onMouseEnterArea() {} - void setEnabled(boolean enabled); + /** + * Called when the mouse leaves the area, or it started hovering a different panel. + */ + default void onMouseLeaveArea() {} /** - * Checks if all ancestors are enabled. Only then this widget is visible and interactable. + * @return if this widget is currently right below the mouse + */ + default boolean isHovering() { + return isHoveringFor(0); + } + + /** + * Returns if this element is right blow the mouse for a certain amount of time * - * @return if all ancestors are enabled. + * @param ticks time in ticks + * @return if this element is right blow the mouse for a certain amount of time */ - default boolean areAncestorsEnabled() { - IWidget parent = this; - do { - if (!parent.isEnabled()) return false; - parent = parent.getParent(); - } while (parent.hasParent()); - return true; + default boolean isHoveringFor(int ticks) { + return false; + } + + default boolean isBelowMouse() { + return isBelowMouseFor(0); + } + + default boolean isBelowMouseFor(int ticks) { + return false; } /** @@ -210,32 +181,111 @@ default boolean canHoverThrough() { } /** - * Marks tooltip for this widget as dirty. + * @return default width if it can't be calculated */ - default void markTooltipDirty() {} + default int getDefaultWidth() { + return 18; + } /** - * @return the parent of this widget + * @return default height if it can't be calculated */ - @NotNull - @Override - IWidget getParent(); - - @Override - default boolean hasParent() { - return isValid(); + default int getDefaultHeight() { + return 18; } + /** - * @return the context the current screen + * Validates and initialises this element. + * This element now becomes valid + * + * @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 */ - ModularGuiContext getContext(); + void initialise(@NotNull IWidget parent, boolean late); /** - * @return the panel this widget is in + * Invalidates this element. + */ + void dispose(); + + /** + * Determines if this element exist in an active gui. + * + * @return if this is in a valid gui + */ + boolean isValid(); + + /** + * Called 20 times per second. + */ + default void onUpdate() {} + + /** + * Draws the background of this widget. + * + * @param context gui context + * @param widgetTheme widget theme of this widget + */ + default void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetThemeEntry)} and before + * {@link #drawOverlay(ModularGuiContext, WidgetThemeEntry)} + * + * @param context gui context + * @param widgetTheme widget theme + */ + default void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws the overlay of this theme. + * + * @param context gui context + * @param widgetTheme widget theme + */ + default void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws foreground elements of this widget. For example tooltips. + * No transformations are applied here. + * + * @param context gui context + */ + default void drawForeground(ModularGuiContext context) {} + + default void transform(IViewportStack stack) { + stack.translate(getArea().rx, getArea().ry, 0); + } + + default Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { + return null; + } + + default WidgetThemeEntry getWidgetTheme(ITheme theme) { + return theme.getFallback(); + } + + /** + * @return all children of this widget */ @NotNull - ModularPanel getPanel(); + @Override + default List getChildren() { + return Collections.emptyList(); + } + + /** + * @return if this widget has any children + */ + @Override + default boolean hasChildren() { + return !getChildren().isEmpty(); + } + + void scheduleResize(); + + boolean requiresResize(); /** * @return flex of this widget @@ -296,6 +346,31 @@ default void onResized() {} */ default void postResize() {} + /** + * 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. + * + * @return if this element is enabled + */ + @Override + boolean isEnabled(); + + void setEnabled(boolean enabled); + + /** + * Checks if all ancestors are enabled. Only then this widget is visible and interactable. + * + * @return if all ancestors are enabled. + */ + default boolean areAncestorsEnabled() { + IWidget parent = this; + do { + if (!parent.isEnabled()) return false; + parent = parent.getParent(); + } while (parent.hasParent()); + return true; + } + @Nullable String getName(); default boolean isName(String name) { From 19d68cb19755c9983e73033e0e1a6df73d816d05 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 31 Jan 2026 12:22:07 +0100 Subject: [PATCH 18/20] fixes & cleanup --- .../modularui/drawable/graph/Plot.java | 8 ----- .../cleanroommc/modularui/test/TestGuis.java | 1 + .../modularui/utils/MutableSingletonList.java | 4 +++ .../modularui/widget/AbstractWidget.java | 3 ++ .../modularui/widget/DelegatingWidget.java | 32 +++++++++++++------ .../modularui/widget/EmptyWidget.java | 3 -- .../modularui/widget/InternalWidgetTree.java | 23 ------------- .../cleanroommc/modularui/widget/Widget.java | 1 - .../modularui/widget/WidgetTree.java | 11 ++++--- 9 files changed, 36 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/drawable/graph/Plot.java b/src/main/java/com/cleanroommc/modularui/drawable/graph/Plot.java index a622fbfbf..eff200aeb 100644 --- a/src/main/java/com/cleanroommc/modularui/drawable/graph/Plot.java +++ b/src/main/java/com/cleanroommc/modularui/drawable/graph/Plot.java @@ -1,13 +1,11 @@ package com.cleanroommc.modularui.drawable.graph; -import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.GuiAxis; import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.utils.Color; import com.cleanroommc.modularui.utils.DAM; import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.MathUtils; -import com.cleanroommc.modularui.utils.NumberFormat; import com.cleanroommc.modularui.utils.Platform; public class Plot { @@ -37,7 +35,6 @@ public void redraw() { } private void redraw(GraphView view) { - long time = System.nanoTime(); float dHalf = thickness * 0.5f; int n = xs.length * 4; // each point has 2 offset vertices and each vertex has an x and y component @@ -139,8 +136,6 @@ private void redraw(GraphView view) { lpoy = py * dHalf; storePoints(vertexIndex, view, x1, y1, lpox, lpoy); - time = System.nanoTime() - time; - ModularUI.LOGGER.error("Calculating vertices from {} data points took {}s", xs.length, NumberFormat.formatNanos(time)); } private int storePoints(int index, GraphView view, float sx, float sy, float ox, float oy) { @@ -167,12 +162,9 @@ public void draw(GraphView view) { int a = Color.getAlpha(color); Platform.setupDrawColor(); Platform.startDrawing(Platform.DrawMode.TRIANGLE_STRIP, Platform.VertexFormat.POS_COLOR, buffer -> { - long time = System.nanoTime(); for (int i = 0; i < this.vertexBuffer.length; i += 2) { buffer.pos(this.vertexBuffer[i], this.vertexBuffer[i + 1], 0).color(r, g, b, a).endVertex(); } - time = System.nanoTime() - time; - ModularUI.LOGGER.error("Drawing plot with {} points took {}s", xs.length, NumberFormat.formatNanos(time)); }); } diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index 65997da28..80f86870b 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -287,6 +287,7 @@ public void onInit() { @Override public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { GuiDraw.drawEntity(entity, 0, 0, width, height, context.getCurrentDrawingZ(), e -> { + // TODO the drawable doesnt seem to update the rotation float scale = 0.9f; GlStateManager.scale(scale, scale, scale); GlStateManager.translate(0, 7, 0); diff --git a/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java b/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java index 4ce291919..111b543db 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java +++ b/src/main/java/com/cleanroommc/modularui/utils/MutableSingletonList.java @@ -28,6 +28,10 @@ public T get() { return value; } + public T getOrNull() { + return hasValue ? value : null; + } + public void set(T t) { this.value = t; this.hasValue = true; diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java index c20e6f405..aac8700c3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -16,6 +16,9 @@ import java.util.Objects; +/** + * Very basic implementation of {@link IWidget}. + */ public abstract class AbstractWidget implements IWidget { // gui context diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java index d7c03729f..622a11bb3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingWidget.java @@ -3,33 +3,45 @@ import com.cleanroommc.modularui.api.layout.IViewportStack; import com.cleanroommc.modularui.api.widget.IDelegatingWidget; import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.utils.MutableSingletonList; import com.cleanroommc.modularui.widget.sizer.Area; import com.cleanroommc.modularui.widget.sizer.StandardResizer; import org.jetbrains.annotations.NotNull; +import java.util.List; + public class DelegatingWidget extends AbstractWidget implements IDelegatingWidget { - private IWidget delegate; + private final MutableSingletonList delegate = new MutableSingletonList<>(); public DelegatingWidget(IWidget delegate) { - this.delegate = delegate; + this.delegate.set(delegate); + resizer(new StandardResizer(this)); } protected void setDelegate(IWidget delegate) { - if (this.delegate != null) { - this.delegate.dispose(); + if (!this.delegate.isEmpty()) { + this.delegate.get().dispose(); + this.delegate.remove(); } - this.delegate = delegate; - if (this.delegate != null && isValid()) { - initialise(getParent(), true); - delegate.scheduleResize(); + if (delegate != null) { + this.delegate.set(delegate); + if (isValid()) { + initialise(getParent(), true); + delegate.scheduleResize(); + } + onChangeDelegate(delegate); } - onChangeDelegate(delegate); } protected void onChangeDelegate(IWidget delegate) {} + @Override + public @NotNull List getChildren() { + return this.delegate; + } + @Override public void afterInit() { super.resizer().setDefaultParent(null); // remove this widget from the resize node tree @@ -90,6 +102,6 @@ public int getDefaultHeight() { @Override public IWidget getDelegate() { - return delegate; + return delegate.getOrNull(); } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java index 79db712ee..52ee8350a 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/EmptyWidget.java @@ -106,9 +106,6 @@ public boolean canHoverThrough() { return true; } - @Override - public void markTooltipDirty() {} - @Override public @NotNull IWidget getParent() { return this.parent; diff --git a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java index cdc1b300e..512a642ff 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/InternalWidgetTree.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; -import java.util.function.Predicate; @ApiStatus.Internal class InternalWidgetTree { @@ -277,26 +276,4 @@ private static void checkFullyCalculated(List children, BitSet state j++; } } - - static void getTree(IWidget root, IWidget parent, Predicate test, StringBuilder builder, WidgetTree.WidgetInfo additionalInfo, String indent, boolean hasNextSibling) { - if (!indent.isEmpty()) { - builder.append(indent).append(hasNextSibling ? "├ " : "└ "); - } - builder.append(parent); - if (additionalInfo != null) { - builder.append(" {"); - additionalInfo.addInfo(root, parent, builder); - builder.append("}"); - } - builder.append('\n'); - if (parent.hasChildren()) { - List children = parent.getChildren(); - for (int i = 0; i < children.size(); i++) { - IWidget child = children.get(i); - if (test.test(child)) { - getTree(root, child, test, builder, additionalInfo, indent + (hasNextSibling ? "│ " : " "), i < children.size() - 1); - } - } - } - } } diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 8974e35f5..8d23f9613 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -303,7 +303,6 @@ public W tooltip(RichTooltip tooltip) { * Should be called when information which is displayed in the tooltip via {@link ITooltip#tooltipDynamic(Consumer)}. * It will invalidate the current tooltip and be caused to rebuild. */ - @Override public void markTooltipDirty() { if (this.tooltip != null) { this.tooltip.markDirty(); diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index b9feebd47..b90094343 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -34,7 +34,7 @@ public class WidgetTree extends TreeUtil { */ public static boolean logResizeTime = false; - public static final WidgetInfo INFO_AREA = (root, widget, builder) -> builder + public static final WidgetInfo WIDGET_INFO_AREA = (root, widget, builder) -> builder .append("Area xywh:") .append(widget.getArea().x - root.getArea().x) .append(", ") @@ -43,8 +43,8 @@ public class WidgetTree extends TreeUtil { .append(widget.getArea().width) .append(", ") .append(widget.getArea().height); - public static final WidgetInfo INFO_ENABLED = (root, widget, builder) -> builder.append("Enabled: ").append(widget.isEnabled()); - public static final WidgetInfo INFO_WIDGET_THEME = (root, widget, builder) -> builder.append("Widget theme: ") + public static final WidgetInfo WIDGET_INFO_ENABLED = (root, widget, builder) -> builder.append("Enabled: ").append(widget.isEnabled()); + public static final WidgetInfo WIDGET_INFO_WIDGET_THEME = (root, widget, builder) -> builder.append("Widget theme: ") .append(widget.getWidgetTheme(widget.getPanel().getTheme()).getKey().getFullName()); private WidgetTree() {} @@ -258,9 +258,10 @@ public static void resizeInternal(ResizeNode parent, boolean onOpen) { applyPos(parent); postFullResize(parent); } catch (Throwable e) { - ModularUI.LOGGER.fatal("An exception was thrown while resizing widgets. Affected node tree:"); + ModularUI.LOGGER.fatal("An exception was thrown while resizing widgets. Exception:"); + ModularUI.LOGGER.catching(e); + ModularUI.LOGGER.fatal("Affected node tree:"); print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); - ModularUI.LOGGER.fatal("."); } if (WidgetTree.logResizeTime) { From 6348ec7aae6a60701b30c32c013544b75047b07d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 31 Jan 2026 15:53:19 +0100 Subject: [PATCH 19/20] fix applyPos issue --- .../cleanroommc/modularui/animation/Animator.java | 2 -- .../modularui/api/layout/IResizeable.java | 5 +++++ .../com/cleanroommc/modularui/test/TestGui.java | 3 ++- .../modularui/widget/AbstractWidget.java | 4 ---- .../cleanroommc/modularui/widget/WidgetTree.java | 12 +++++++++++- .../cleanroommc/modularui/widget/sizer/Area.java | 13 ++++--------- .../modularui/widget/sizer/StandardResizer.java | 5 ++--- .../cleanroommc/modularui/widgets/CategoryList.java | 1 + .../modularui/widgets/SortableListWidget.java | 12 ++++++------ 9 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/animation/Animator.java b/src/main/java/com/cleanroommc/modularui/animation/Animator.java index 17a844b61..fcca6ce1f 100644 --- a/src/main/java/com/cleanroommc/modularui/animation/Animator.java +++ b/src/main/java/com/cleanroommc/modularui/animation/Animator.java @@ -171,8 +171,6 @@ public Animator curve(IInterpolation curve) { return this; } - - /** * Sets a function which is executed everytime the progress updates, that is on every frame. * The argument of the function is the interpolated value. 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 5313f309f..ddc0d8312 100644 --- a/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java +++ b/src/main/java/com/cleanroommc/modularui/api/layout/IResizeable.java @@ -32,6 +32,11 @@ public interface IResizeable extends IResizeParent { * Called after all elements in the tree are resized and the absolute positions needs to be calculated from the * relative postion. */ + default void preApplyPos() {} + + /** + * This converts the relative pos to resizer parent to relative pos to widget parent. + */ default void applyPos() {} void setChildrenResized(boolean resized); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGui.java b/src/main/java/com/cleanroommc/modularui/test/TestGui.java index 1d7b11ec9..ae48628d7 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGui.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGui.java @@ -54,7 +54,8 @@ public void onClose() { final Map> items = new Object2ObjectOpenHashMap<>(); for (String line : this.lines) { items.put(line, new SortableListWidget.Item<>(line) - .child(item -> new Row() + .name("item_" + line) + .child(item -> new Row().name("row_" + line) .child(new Widget<>() .addTooltipLine(line) .widgetTheme(IThemeApi.BUTTON) diff --git a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java index aac8700c3..0165fb2d7 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/AbstractWidget.java @@ -35,10 +35,6 @@ public abstract class AbstractWidget implements IWidget { private final Area area = new Area(); private StandardResizer resizer; - public AbstractWidget() { - area.widget = this; - } - /** * Returns the screen of the panel of this widget is being opened in. * diff --git a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java index b90094343..87acc6e9b 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java +++ b/src/main/java/com/cleanroommc/modularui/widget/WidgetTree.java @@ -42,7 +42,9 @@ public class WidgetTree extends TreeUtil { .append(", ") .append(widget.getArea().width) .append(", ") - .append(widget.getArea().height); + .append(widget.getArea().height) + .append(", rx: ").append(widget.getArea().rx) + .append(", ry: ").append(widget.getArea().ry); public static final WidgetInfo WIDGET_INFO_ENABLED = (root, widget, builder) -> builder.append("Enabled: ").append(widget.isEnabled()); public static final WidgetInfo WIDGET_INFO_WIDGET_THEME = (root, widget, builder) -> builder.append("Widget theme: ") .append(widget.getWidgetTheme(widget.getPanel().getTheme()).getKey().getFullName()); @@ -255,6 +257,7 @@ public static void resizeInternal(ResizeNode parent, boolean onOpen) { print(parent, RESIZE_NODE_INFO_RESIZED_COLLAPSED); } // now apply the calculated pos + preApplyPos(parent); applyPos(parent); postFullResize(parent); } catch (Throwable e) { @@ -270,6 +273,13 @@ public static void resizeInternal(ResizeNode parent, boolean onOpen) { } } + public static void preApplyPos(ResizeNode parent) { + parent.preApplyPos(); + for (ResizeNode resizeNode : parent.getChildren()) { + preApplyPos(resizeNode); + } + } + public static void applyPos(ResizeNode parent) { parent.applyPos(); for (ResizeNode resizeNode : parent.getChildren()) { 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 316e5eac1..8687af01d 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.IWidget; import com.cleanroommc.modularui.utils.Interpolations; import com.cleanroommc.modularui.utils.MathUtils; @@ -40,8 +39,6 @@ public static boolean isInside(int x, int y, int w, int h, int px, int py) { private final Box margin = new Box(); private final Box padding = new Box(); - public IWidget widget; - public Area() {} public Area(int x, int y, int w, int h) { @@ -61,10 +58,6 @@ public Area(Area area) { getPadding().set(area.getPadding()); } - public boolean isWidget(String s) { - return widget != null && widget.getName() != null && (widget.getName().endsWith("ctx_tb6") || widget.getName().endsWith("ctx_mo_sub6")); - } - public int x() { return this.x; } @@ -516,8 +509,10 @@ public String toString() { return "Area{" + "x=" + this.x + ", y=" + this.y + - ", width=" + this.width + - ", height=" + this.height + + ", w=" + this.width + + ", h=" + this.height + + ", rx=" + this.rx + + ", ry=" + this.ry + '}'; } diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java index 994b58c6b..948093891 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/StandardResizer.java @@ -310,7 +310,7 @@ private void coverChildrenForEmpty() { } @Override - public void applyPos() { + public void preApplyPos() { IWidget widget = getWidget(); Area relativeTo = getParent().getArea(); Area area = widget.getArea(); @@ -322,7 +322,7 @@ public void applyPos() { } @Override - public void postFullResize() { + public void applyPos() { IWidget widget = getWidget(); if (widget instanceof IDelegatingWidget dw && dw.getDelegate() != null) { super.postFullResize(); @@ -342,7 +342,6 @@ public void postFullResize() { slot.xPos = widget.getArea().x - mainArea.x + 1; slot.yPos = widget.getArea().y - mainArea.y + 1; } - super.postFullResize(); } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java index b046f1978..15e338276 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/CategoryList.java @@ -139,6 +139,7 @@ public void onChildAdd(IWidget child) { private void updateHeight() { layoutWidgets(); + WidgetTree.preApplyPos(resizer()); WidgetTree.applyPos(resizer()); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java index 35cebee46..07fa0c4a4 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/SortableListWidget.java @@ -5,9 +5,9 @@ import com.cleanroommc.modularui.api.widget.IValueWidget; import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.screen.viewport.LocatedWidget; import com.cleanroommc.modularui.utils.ObjectList; import com.cleanroommc.modularui.widget.DraggableWidget; -import com.cleanroommc.modularui.widget.WidgetTree; import com.cleanroommc.modularui.widget.sizer.Area; import org.jetbrains.annotations.NotNull; @@ -189,11 +189,11 @@ public boolean canDropHere(int x, int y, @Nullable IWidget widget) { @Override public void onDrag(int mouseButton, long timeSinceLastClick) { super.onDrag(mouseButton, timeSinceLastClick); - // TODO this kind of assumes the hovered is in bounds of the parent Item, which may not be true. - IWidget hovered = getContext().getTopHovered(); - SortableListWidget.Item item = WidgetTree.findParent(hovered, Item.class); - if (item != null && item != this && item.listWidget == this.listWidget) { - this.listWidget.moveTo(this.index, item.index); + for (LocatedWidget hovering : getPanel().getAllHoveringList(false)) { + if (hovering.getElement() instanceof SortableListWidget.Item item && item != this && item.listWidget == this.listWidget) { + this.listWidget.moveTo(this.index, item.index); + break; + } } } From 380527df7f5f99e769dadd8978da04954c17b329 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 31 Jan 2026 16:58:14 +0100 Subject: [PATCH 20/20] minor adjustments and fixes --- .../modularui/overlay/DebugOverlay.java | 25 ++++++++++++------- .../modularui/screen/ClientScreenHandler.java | 1 + .../cleanroommc/modularui/utils/TreeUtil.java | 9 ++++--- .../widgets/menu/AbstractMenuButton.java | 23 +++++++++++++++-- 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java index bece3df97..8425fb1e0 100644 --- a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -11,10 +11,9 @@ 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.utils.TreeUtil; import com.cleanroommc.modularui.widget.WidgetTree; import com.cleanroommc.modularui.widgets.ButtonWidget; import com.cleanroommc.modularui.widgets.ListWidget; @@ -44,19 +43,29 @@ public DebugOverlay(IMuiScreen screen) { .bottom(0) .height(12) .width(160) - .background(new Rectangle().color(Color.withAlpha(Color.WHITE.main, 0.2f)).cornerRadius(4)) + .background(new Rectangle().color(Color.withAlpha(DebugOptions.INSTANCE.outlineColor.getIntValue(), 0.4f)).cornerRadius(4)) + .disableHoverBackground() .overlay(IKey.str("Debug Options")) .openUp() .menuList(l1 -> l1 .name("menu_list") .maxSize(100) .widthRel(1f) - .child(new ButtonWidget<>().name("ctx_b") + .child(new ButtonWidget<>().name("print_widget_tree_button") .height(12) .widthRel(1f) .invisible() .overlay(IKey.str("Print widget trees")) .onMousePressed(this::logWidgetTrees)) + .child(new ButtonWidget<>().name("print_resizer_tree_button") + .height(12) + .widthRel(1f) + .invisible() + .overlay(IKey.str("Print resizer tree")) + .onMousePressed(b -> { + TreeUtil.print(parent.getScreen().getResizeNode()); + return true; + })) .child(new ContextMenuButton<>("menu_hover_info") .height(10) .widthRel(1f) @@ -65,9 +74,10 @@ public DebugOverlay(IMuiScreen screen) { .menu(new Menu<>() .width(100) .coverChildrenHeight() + .padding(2) .child(new ListWidget<>() .maxSize(100) - .width(100) + .widthRel(1f) .child(toggleOption(0, "Any", DebugOptions.INSTANCE.showHovered)) .child(toggleOption(1, "Pos", DebugOptions.INSTANCE.showPos)) .child(toggleOption(2, "Size", DebugOptions.INSTANCE.showSize)) @@ -83,6 +93,7 @@ public DebugOverlay(IMuiScreen screen) { .menu(new Menu<>() .width(100) .coverChildrenHeight() + .padding(2) .child(new ListWidget<>() .maxSize(100) .widthRel(1f) @@ -108,10 +119,6 @@ public static IWidget toggleOption(int i, String name, IBoolValue boolValue) .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.print(panel); diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index 9949c5152..214b51790 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -579,6 +579,7 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable float scale = DebugOptions.INSTANCE.scale.getFloatValue(); int shift = (int) (11 * scale + 0.5f); int lineY = screenH - shift - 2; + if (ModularUI.Mods.JEI.isLoaded()) lineY -= 12; GuiDraw.drawText("Mouse Pos: " + mouseX + ", " + mouseY, 5, lineY, scale, outlineColor, true); lineY -= shift; GuiDraw.drawText("FPS: " + fpsCounter.getFps(), 5, lineY, scale, outlineColor, true); diff --git a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java index f8f535ad7..8fd019312 100644 --- a/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java +++ b/src/main/java/com/cleanroommc/modularui/utils/TreeUtil.java @@ -2,6 +2,7 @@ import com.cleanroommc.modularui.ModularUI; import com.cleanroommc.modularui.api.ITreeNode; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.widget.sizer.ResizeNode; @@ -385,9 +386,11 @@ public static > void print(T parent, Predicate test) { * @param additionalInfo additional info function which is executed for each widget */ public static > void print(T parent, Predicate test, NodeInfo additionalInfo) { - StringBuilder builder = new StringBuilder("Widget tree of ") - .append(parent) - .append('\n'); + StringBuilder builder = new StringBuilder(); + if (parent instanceof IWidget) builder.append("Widget"); + else if (parent instanceof ResizeNode) builder.append("ResizeNode"); + else builder.append(parent.getClass()); + builder.append(" tree of ").append(parent).append('\n'); ModularUI.LOGGER.info(toString(builder, parent, test, additionalInfo)); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java index 225846bfc..1bc4bfd5b 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/AbstractMenuButton.java @@ -15,6 +15,7 @@ import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Predicate; /** * This is the base class for a button that can open a floating widget by clicking or hovering the button. In ModularUI this is used for @@ -76,7 +77,11 @@ protected void toggleMenu(boolean soft) { this.softOpen = false; } } else if (!soft) { - closeMenu(false); + if (this.openOnHover) { + this.softOpen = true; + } else { + closeMenu(false); + } } } else { openMenu(soft); @@ -149,6 +154,12 @@ private IPanelHandler getPanelHandler() { @Override public @NotNull Result onMousePressed(int mouseButton) { + if (!this.open) { + forEachSiblingMenuButton(w -> { + w.closeMenu(false); + return true; + }); + } toggleMenu(false); return Result.SUCCESS; } @@ -156,11 +167,19 @@ private IPanelHandler getPanelHandler() { @Override public void onMouseEnterArea() { super.onMouseEnterArea(); - if (this.openOnHover) { + if (this.openOnHover && forEachSiblingMenuButton(mb -> !mb.open || mb.softOpen)) { openMenu(true); } } + protected boolean forEachSiblingMenuButton(Predicate> test) { + Menu menuParent = WidgetTree.findParent(this, Menu.class); + if (menuParent != null) { + return WidgetTree.foreachChild(menuParent, w -> !(w instanceof AbstractMenuButton mb) || mb == this || test.test(mb), false); + } + return true; + } + @Override public void onMouseLeaveArea() { super.onMouseLeaveArea();