From 305cf6e6356194f7dc5d17d0d51ff4782871b59d Mon Sep 17 00:00:00 2001 From: brachy84 Date: Sat, 1 Nov 2025 11:49:58 +0100 Subject: [PATCH 1/3] context menu things --- .../cleanroommc/modularui/ClientProxy.java | 2 + .../cleanroommc/modularui/api/IThemeApi.java | 8 + .../api/widget/IDelegatingWidget.java | 18 ++ .../modularui/overlay/DebugOptions.java | 30 +++ .../modularui/overlay/DebugOverlay.java | 99 +++++++++ .../modularui/screen/ClientScreenHandler.java | 9 +- .../cleanroommc/modularui/test/TestGuis.java | 51 ++++- .../widget/DelegatingSingleChildWidget.java | 20 +- .../cleanroommc/modularui/widget/Widget.java | 13 ++ .../modularui/widget/sizer/Bounds.java | 50 +++++ .../widgets/AbstractCycleButtonWidget.java | 47 +++- .../modularui/widgets/ToggleButton.java | 5 + .../widgets/menu/ContextMenuButton.java | 210 ++++++++++++++++++ .../widgets/menu/ContextMenuList.java | 81 +++++++ .../widgets/menu/ContextMenuOption.java | 31 +++ .../widgets/menu/IContextMenuOption.java | 22 ++ .../modularui/widgets/menu/MenuPanel.java | 36 +++ .../textures/gui/background/menu.png | Bin 0 -> 602 bytes 18 files changed, 717 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java create mode 100644 src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java create mode 100644 src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java create mode 100644 src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java create mode 100644 src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java create mode 100644 src/main/resources/assets/modularui/textures/gui/background/menu.png diff --git a/src/main/java/com/cleanroommc/modularui/ClientProxy.java b/src/main/java/com/cleanroommc/modularui/ClientProxy.java index f9153df52..1a895d309 100644 --- a/src/main/java/com/cleanroommc/modularui/ClientProxy.java +++ b/src/main/java/com/cleanroommc/modularui/ClientProxy.java @@ -8,6 +8,7 @@ import com.cleanroommc.modularui.holoui.HoloScreenEntity; import com.cleanroommc.modularui.holoui.ScreenEntityRender; import com.cleanroommc.modularui.keybind.KeyBindHandler; +import com.cleanroommc.modularui.overlay.DebugOverlay; import com.cleanroommc.modularui.screen.ClientScreenHandler; import com.cleanroommc.modularui.test.EventHandler; import com.cleanroommc.modularui.test.OverlayTest; @@ -68,6 +69,7 @@ void preInit(FMLPreInitializationEvent event) { testKey = new KeyBinding("key.test", KeyConflictContext.IN_GAME, Keyboard.KEY_NUMPAD4, "key.categories.modularui"); ClientRegistry.registerKeyBinding(testKey); } + DebugOverlay.register(); if (ModularUIConfig.enableTestOverlays) { OverlayTest.init(); } diff --git a/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java b/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java index b9fa69ab4..a8a363f5f 100644 --- a/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java +++ b/src/main/java/com/cleanroommc/modularui/api/IThemeApi.java @@ -70,6 +70,14 @@ public interface IThemeApi { .defaultHoverTheme(SelectableTheme.whiteTextShadow(18, 18, GuiTextures.MC_BUTTON_HOVERED, IDrawable.NONE)) .register(); + WidgetThemeKey CONTEXT_MENU = get().widgetThemeKeyBuilder("menu", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(80, 100, GuiTextures.MENU_BACKGROUND)) + .register(); + + WidgetThemeKey MENU_OPTION = get().widgetThemeKeyBuilder("menuOption", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(80, 12, IDrawable.EMPTY)) + .register(); + // sub widget themes WidgetThemeKey ITEM_SLOT_PLAYER = ITEM_SLOT.createSubKey("player"); WidgetThemeKey ITEM_SLOT_PLAYER_HOTBAR = ITEM_SLOT_PLAYER.createSubKey("playerHotbar"); diff --git a/src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java b/src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java new file mode 100644 index 000000000..b8132f336 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/api/widget/IDelegatingWidget.java @@ -0,0 +1,18 @@ +package com.cleanroommc.modularui.api.widget; + +import net.minecraft.inventory.Slot; + +public interface IDelegatingWidget extends IWidget, IVanillaSlot { + + IWidget getDelegate(); + + @Override + default Slot getVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot ? vanillaSlot.getVanillaSlot() : null; + } + + @Override + default boolean handleAsVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java new file mode 100644 index 000000000..b4badcf98 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOptions.java @@ -0,0 +1,30 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.utils.Color; + +public class DebugOptions { + + public static final DebugOptions INSTANCE = new DebugOptions(); + + public boolean showHovered = true; + public boolean showName = true; + public boolean showPos = true; + public boolean showSize = true; + public boolean showRelPos = true; + public boolean showWidgetTheme = true; + public boolean showOutline = true; + + public boolean showParent = true; + public boolean showParentName = true; + public boolean showParentPos = true; + public boolean showParentSize = true; + public boolean showParentRelPos = false; + public boolean showParentWidgetTheme = false; + public boolean showParentOutline = true; + + public int textColor = Color.argb(180, 40, 115, 220); + public int outlineColor = textColor; + public int cursorColor = Color.GREEN.main; + public float scale = 0.8f; + +} diff --git a/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java new file mode 100644 index 000000000..0fef8cf05 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/overlay/DebugOverlay.java @@ -0,0 +1,99 @@ +package com.cleanroommc.modularui.overlay; + +import com.cleanroommc.modularui.ModularUIConfig; +import com.cleanroommc.modularui.api.IMuiScreen; +import com.cleanroommc.modularui.api.drawable.IIcon; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.value.IBoolValue; +import com.cleanroommc.modularui.drawable.GuiTextures; +import com.cleanroommc.modularui.drawable.NamedDrawableRow; +import com.cleanroommc.modularui.drawable.Rectangle; +import com.cleanroommc.modularui.screen.CustomModularScreen; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.screen.viewport.GuiContext; +import com.cleanroommc.modularui.screen.viewport.ModularGuiContext; +import com.cleanroommc.modularui.theme.WidgetTheme; +import com.cleanroommc.modularui.utils.Color; +import com.cleanroommc.modularui.value.BoolValue; +import com.cleanroommc.modularui.widget.WidgetTree; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import com.cleanroommc.modularui.widgets.ToggleButton; +import com.cleanroommc.modularui.widgets.menu.ContextMenuButton; +import com.cleanroommc.modularui.widgets.menu.ContextMenuList; +import com.cleanroommc.modularui.widgets.menu.ContextMenuOption; + +import org.jetbrains.annotations.NotNull; + +public class DebugOverlay extends CustomModularScreen { + + public static void register() { + OverlayManager.register(new OverlayHandler(screen -> ModularUIConfig.guiDebugMode && screen instanceof IMuiScreen, screen -> new DebugOverlay((IMuiScreen) screen))); + } + + private static final IIcon CHECKMARK = GuiTextures.CHECKMARK.asIcon().size(8); + + private final IMuiScreen parent; + + public DebugOverlay(IMuiScreen screen) { + this.parent = screen; + } + + @Override + public @NotNull ModularPanel buildUI(ModularGuiContext context) { + return new ModularPanel("debug") + .fullScreenInvisible() + .child(new ContextMenuButton<>() + .horizontalCenter() + .bottom(0) + .height(12) + .width(100) + .background(new Rectangle().setColor(Color.withAlpha(Color.WHITE.main, 0.2f)).setCornerRadius(4)) + .overlay(IKey.str("Debug Options")) + .openUp() + .menuList(new ContextMenuList<>("debug_options") + .maxSize(100) + .widthRel(1f) + .child(new ContextMenuOption<>() + .child(new ButtonWidget<>() + .invisible() + .overlay(IKey.str("Print widget trees")) + .onMousePressed(this::logWidgetTrees))) + .child(new ContextMenuButton<>() + .height(10) + .overlay(IKey.str("Widget hover info")) + .openRightUp() + .menuList(new ContextMenuList<>("hover_info") + .maxSize(100) + .child(toggleOption("Name", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showName, v -> DebugOptions.INSTANCE.showName = v))) + .child(toggleOption("Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showPos, v -> DebugOptions.INSTANCE.showPos = v))) + .child(toggleOption("Size", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showSize, v -> DebugOptions.INSTANCE.showSize = v))) + .child(toggleOption("Rel Pos", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showRelPos, v -> DebugOptions.INSTANCE.showRelPos = v))) + .child(toggleOption("Widget Theme", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showWidgetTheme, v -> DebugOptions.INSTANCE.showWidgetTheme = v))) + .child(toggleOption("Outline", new BoolValue.Dynamic(() -> DebugOptions.INSTANCE.showOutline, v -> DebugOptions.INSTANCE.showOutline = v))) + )))); + } + + public static ContextMenuOption toggleOption(String name, IBoolValue boolValue) { + return new ContextMenuOption<>() + .child(new ToggleButton() + .name("menu toggle " + name) + .invisible() + .value(boolValue) + .overlay(true, new NamedDrawableRow() + .name(IKey.str(name)) + .drawable(CHECKMARK)) + .overlay(false, new NamedDrawableRow() + .name(IKey.str(name)))); + } + + private void drawDebug(GuiContext context, int x, int y, int w, int h, WidgetTheme widgetTheme) { + + } + + private boolean logWidgetTrees(int b) { + for (ModularPanel panel : parent.getScreen().getPanelManager().getOpenPanels()) { + WidgetTree.printTree(panel); + } + return true; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java index da6a8c82d..069fed627 100644 --- a/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java +++ b/src/main/java/com/cleanroommc/modularui/screen/ClientScreenHandler.java @@ -14,6 +14,7 @@ import com.cleanroommc.modularui.core.mixins.early.minecraft.GuiScreenAccessor; import com.cleanroommc.modularui.drawable.GuiDraw; import com.cleanroommc.modularui.drawable.Stencil; +import com.cleanroommc.modularui.overlay.DebugOptions; import com.cleanroommc.modularui.overlay.OverlayManager; import com.cleanroommc.modularui.overlay.OverlayStack; import com.cleanroommc.modularui.screen.viewport.GuiContext; @@ -296,7 +297,7 @@ private static boolean handleKeyboardInput(@Nullable ModularScreen muiScreen, Gu } else { // releasing a key // for some reason when you press E after joining a world the button will not trigger the press event, - // but ony the release event, causing this to be null + // but only the release event, causing this to be null if (lastChar == null) return false; // when the key is released, the event char is empty if (inputPhase.isEarly() && doAction(muiScreen, ms -> ms.onKeyRelease(lastChar, key))) { @@ -570,8 +571,10 @@ public static void drawDebugScreen(@Nullable ModularScreen muiScreen, @Nullable locatedHovered.unapplyMatrix(context); GuiDraw.drawText("Widget Theme: " + hovered.getWidgetTheme(muiScreen.getCurrentTheme()).getKey().getFullName(), 5, lineY, scale, color, true); lineY -= shift; - GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, scale, color, true); - lineY -= shift; + if (DebugOptions.INSTANCE.showSize) { + GuiDraw.drawText("Size: " + area.width + ", " + area.height, 5, lineY, scale, color, true); + lineY -= shift; + } GuiDraw.drawText("Pos: " + area.x + ", " + area.y + " Rel: " + area.rx + ", " + area.ry, 5, lineY, scale, color, true); lineY -= shift; GuiDraw.drawText("Class: " + hovered, 5, lineY, scale, color, true); diff --git a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java index d26e20c74..ca765554c 100644 --- a/src/main/java/com/cleanroommc/modularui/test/TestGuis.java +++ b/src/main/java/com/cleanroommc/modularui/test/TestGuis.java @@ -50,6 +50,8 @@ import com.cleanroommc.modularui.widgets.layout.Flow; import com.cleanroommc.modularui.widgets.layout.Grid; import com.cleanroommc.modularui.widgets.layout.Row; +import com.cleanroommc.modularui.widgets.menu.ContextMenuButton; +import com.cleanroommc.modularui.widgets.menu.ContextMenuList; import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget; import net.minecraft.client.Minecraft; @@ -74,6 +76,7 @@ import java.util.Comparator; import java.util.List; import java.util.Random; +import java.util.stream.Collectors; import java.util.stream.IntStream; public class TestGuis extends CustomModularScreen { @@ -503,7 +506,7 @@ public static ModularPanel buildCollapseDisabledChildrenUI() { } public static @NotNull ModularPanel buildViewportTransformUI() { - return new TestPanel("test") + return new TestPanel("viewport_transform") .child(new Widget<>() .align(Alignment.Center) .size(50, 50) @@ -511,6 +514,52 @@ public static ModularPanel buildCollapseDisabledChildrenUI() { .hoverBackground(GuiTextures.MC_BUTTON_HOVERED)); } + public static ModularPanel buildContextMenu() { + List options1 = IntStream.range(0, 5).mapToObj(i -> "Option " + (i + 1)).collect(Collectors.toList()); + List options2 = IntStream.range(0, 5).mapToObj(i -> "Sub Option " + (i + 1)).collect(Collectors.toList()); + return new ModularPanel("context_menu_test") + .size(150) + .child(new ListWidget<>() + .height(100) + .left(25) + .coverChildrenWidth() + //.right(25) + .top(40) + .child(new ToggleButton() + .coverChildrenHeight() + .widthRel(1f) + .child(true, new Row() + .coverChildrenHeight() + .widthRel(1f) + .mainAxisAlignment(Alignment.MainAxis.SPACE_BETWEEN) + .child(IKey.str("Text1").asWidget()) + .child(new ItemDrawable(Items.PORKCHOP).asWidget())) + .child(false, new Row() + .coverChildrenHeight() + .widthRel(1f) + .mainAxisAlignment(Alignment.MainAxis.SPACE_BETWEEN) + .child(IKey.str("Text2").asWidget()) + .child(new ItemDrawable(Items.MAGMA_CREAM).asWidget())))) + .child(new ContextMenuButton<>() + .top(7) + .width(100) + .horizontalCenter() + .height(16) + .overlay(IKey.str("Menu")) + .menuList(new ContextMenuList<>("menu1") + .widthRel(1f) + .maxSize(80) + .children(options1, s -> IKey.str(s).asWidget()) + .child(new ContextMenuButton<>() + .overlay(IKey.str("Sub Menu")) + .openRightDown() + .menuList(new ContextMenuList<>("menu2") + .coverChildrenWidth() + //.width(90) + .maxSize(80) + .children(options2, s -> IKey.str(s).asWidget()))))); + } + private static class TestPanel extends ModularPanel { public TestPanel(String name) { diff --git a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java index b766224af..44eb79ce3 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/DelegatingSingleChildWidget.java @@ -43,15 +43,17 @@ public void postResize() { super.postResize(); if (getDelegate() != null) getDelegate().postResize(); this.currentlyResizing = false; - Area childArea = getChild().getArea(); - Area area = super.getArea(); - area.set(childArea); - area.rx = childArea.rx; - area.ry = childArea.ry; - childArea.x = 0; - childArea.y = 0; - childArea.rx = 0; - childArea.ry = 0; + if (getDelegate() != null) { + Area childArea = getChild().getArea(); + Area area = super.getArea(); + area.set(childArea); + area.rx = childArea.rx; + area.ry = childArea.ry; + childArea.x = 0; + childArea.y = 0; + childArea.rx = 0; + childArea.ry = 0; + } } @Override diff --git a/src/main/java/com/cleanroommc/modularui/widget/Widget.java b/src/main/java/com/cleanroommc/modularui/widget/Widget.java index 43dd716f0..fbabb11a4 100644 --- a/src/main/java/com/cleanroommc/modularui/widget/Widget.java +++ b/src/main/java/com/cleanroommc/modularui/widget/Widget.java @@ -24,6 +24,7 @@ import com.cleanroommc.modularui.value.sync.SyncHandler; import com.cleanroommc.modularui.value.sync.ValueSyncHandler; import com.cleanroommc.modularui.widget.sizer.Area; +import com.cleanroommc.modularui.widget.sizer.Bounds; import com.cleanroommc.modularui.widget.sizer.Flex; import com.cleanroommc.modularui.widget.sizer.IUnResizeable; @@ -72,6 +73,7 @@ public class Widget> implements IWidget, IPositioned, ITo @Nullable private String syncKey; @Nullable private SyncHandler syncHandler; // rendering + @Nullable private IDrawable shadow = null; @Nullable private IDrawable background = null; @Nullable private IDrawable overlay = null; @Nullable private IDrawable hoverBackground = null; @@ -207,6 +209,9 @@ public void dispose() { */ @Override public void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + if (this.shadow != null) { + this.shadow.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); + } IDrawable bg = getCurrentBackground(context.getTheme(), widgetTheme); if (bg != null) { bg.drawAtZero(context, getArea().width, getArea().height, getActiveWidgetTheme(widgetTheme, isHovering())); @@ -391,6 +396,10 @@ public final WidgetThemeEntry getWidgetTheme(ITheme theme) { return getWidgetThemeInternal(theme); } + public final @Nullable WidgetThemeKey getWidgetThemeOverride() { + return widgetThemeOverride; + } + /** * Returns the actual used widget theme. Uses {@link #widgetTheme(String)} if it has been set, otherwise calls * {@link #getWidgetThemeInternal(ITheme)} @@ -621,6 +630,10 @@ public W setEnabledIf(Predicate condition) { // === Resizing === // ---------------- + public void estimateSize(Bounds bounds) { + + } + @Override public int getDefaultWidth() { return isValid() ? getWidgetTheme(getContext().getTheme()).getTheme().getDefaultWidth() : 18; diff --git a/src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java b/src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java new file mode 100644 index 000000000..f28b6b4f9 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widget/sizer/Bounds.java @@ -0,0 +1,50 @@ +package com.cleanroommc.modularui.widget.sizer; + +public class Bounds { + + public static final int UNLIMITED_MAX = Integer.MAX_VALUE; + public static final int UNLIMITED_MIN = Integer.MIN_VALUE; + + private int minWidth = UNLIMITED_MIN, minHeight = UNLIMITED_MIN; + private int maxWidth = UNLIMITED_MAX, maxHeight = UNLIMITED_MAX; + + public Bounds set(int minWidth, int minHeight, int maxWidth, int maxHeight) { + this.minWidth = minWidth; + this.minHeight = minHeight; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + return this; + } + + public Bounds max(int width, int height) { + this.maxWidth = width; + this.maxHeight = height; + return this; + } + + public Bounds min(int width, int height) { + this.minWidth = width; + this.minHeight = height; + return this; + } + + public Bounds exact(int w, int h) { + return set(w, h, w, h); + } + + public int getMaxHeight() { + return maxHeight; + } + + public int getMaxWidth() { + return maxWidth; + } + + public int getMinHeight() { + return minHeight; + } + + public int getMinWidth() { + return minWidth; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java index 562c5d37b..bbf9be2b5 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/AbstractCycleButtonWidget.java @@ -7,6 +7,7 @@ import com.cleanroommc.modularui.api.value.IBoolValue; import com.cleanroommc.modularui.api.value.IEnumValue; import com.cleanroommc.modularui.api.value.IIntValue; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.api.widget.Interactable; import com.cleanroommc.modularui.drawable.UITexture; import com.cleanroommc.modularui.screen.RichTooltip; @@ -14,7 +15,7 @@ import com.cleanroommc.modularui.utils.Alignment; import com.cleanroommc.modularui.value.IntValue; import com.cleanroommc.modularui.value.sync.SyncHandler; -import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widget.SingleChildWidget; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +25,7 @@ import java.util.List; import java.util.function.Consumer; -public class AbstractCycleButtonWidget> extends Widget implements Interactable { +public class AbstractCycleButtonWidget> extends SingleChildWidget implements Interactable { private int stateCount = 1; private IIntValue intValue; @@ -33,6 +34,8 @@ public class AbstractCycleButtonWidget> e protected IDrawable[] hoverBackground = null; protected IDrawable[] overlay = null; protected IDrawable[] hoverOverlay = null; + protected IWidget[] stateChildren = null; + protected IWidget fallbackChild = null; private final List stateTooltip = new ArrayList<>(); @Override @@ -40,6 +43,7 @@ public void onInit() { if (this.intValue == null) { this.intValue = new IntValue(0); } + updateChild(getState()); } @Override @@ -74,6 +78,7 @@ public void setState(int state, boolean setSource) { if (state < 0 || state >= this.stateCount) { throw new IndexOutOfBoundsException("CycleButton state out of bounds"); } + updateChild(state); if (setSource) { this.intValue.setIntValue(state); } @@ -81,6 +86,15 @@ public void setState(int state, boolean setSource) { markTooltipDirty(); } + private void updateChild(int state) { + IWidget child = this.stateChildren != null && this.stateChildren.length > state ? this.stateChildren[state] : null; + if (child != null) { + child(child); + } else if (getChild() != this.fallbackChild) { + child(this.fallbackChild); + } + } + @Override public @NotNull Result onMousePressed(int mouseButton) { switch (mouseButton) { @@ -166,6 +180,17 @@ public W disableHoverOverlay() { return getThis(); } + @Override + public W invisible() { + if (this.background != null) { + Arrays.fill(this.background, IDrawable.EMPTY); + } + if (getBackground() == null) { + super.background(IDrawable.EMPTY); + } + return disableHoverBackground(); + } + protected W value(IIntValue value) { this.intValue = value; setValue(value); @@ -177,6 +202,22 @@ protected W value(IIntValue value) { return getThis(); } + @Override + public W child(IWidget child) { + this.fallbackChild = child; + return super.child(child); + } + + public W stateChild(int state, IWidget child) { + if (this.stateChildren == null) { + this.stateChildren = new IWidget[state + 1]; + } else if (this.stateChildren.length < state + 1) { + this.stateChildren = Arrays.copyOf(this.stateChildren, state + 1); + } + this.stateChildren[state] = child; + return getThis(); + } + /** * Sets the state dependent background. The images should be vertically stacked images from top to bottom * Note: The length must be already set! @@ -467,6 +508,8 @@ protected W stateCount(int stateCount) { this.overlay = checkArray(this.overlay, stateCount); this.hoverBackground = checkArray(this.hoverBackground, stateCount); this.hoverOverlay = checkArray(this.hoverOverlay, stateCount); + if (this.stateChildren == null) this.stateChildren = new IWidget[stateCount]; + else if (this.stateChildren.length < stateCount) this.stateChildren = Arrays.copyOf(this.stateChildren, stateCount); return getThis(); } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java b/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java index 945884578..3af614c56 100644 --- a/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java +++ b/src/main/java/com/cleanroommc/modularui/widgets/ToggleButton.java @@ -3,6 +3,7 @@ import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.drawable.IDrawable; import com.cleanroommc.modularui.api.value.IBoolValue; +import com.cleanroommc.modularui.api.widget.IWidget; import com.cleanroommc.modularui.screen.RichTooltip; import com.cleanroommc.modularui.theme.SelectableTheme; import com.cleanroommc.modularui.theme.WidgetTheme; @@ -101,6 +102,10 @@ public ToggleButton invertSelected(boolean invert) { return getThis(); } + public ToggleButton child(boolean selected, IWidget widget) { + return stateChild(selected ? 1 : 0, widget); + } + public boolean invertSelected() { return this.invert; } diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java new file mode 100644 index 000000000..9bf52fe5a --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuButton.java @@ -0,0 +1,210 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.IPanelHandler; +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.api.widget.Interactable; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widget.sizer.Flex; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +@ApiStatus.Experimental +public class ContextMenuButton> extends Widget implements IContextMenuOption, Interactable { + + private Direction direction = Direction.DOWN; + private boolean requiresClick; + + private ContextMenuList menuList; + private boolean open, softOpen; + private IPanelHandler panelHandler; + + public boolean isOpen() { + return open; + } + + public boolean isSoftOpen() { + return softOpen; + } + + public void toggleMenu(boolean soft) { + if (this.open) { + if (this.softOpen) { + if (soft) { + closeMenu(true); + } else { + this.softOpen = false; + } + } else if (!soft) { + closeMenu(false); + } + } else { + openMenu(soft); + } + } + + public void openMenu(boolean soft) { + if (this.open) { + if (this.softOpen && !soft) { + this.softOpen = false; + } + return; + } + initMenuList(); + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.openSubMenu(getMenuList()); + } else { + getPanelHandler().openPanel(); + } + this.open = true; + this.softOpen = soft; + } + + public void closeMenu(boolean soft) { + if (!this.open || (!this.softOpen && soft)) return; + if (getPanel() instanceof MenuPanel menuPanel) { + menuPanel.remove(getMenuList()); + } else { + getPanelHandler().closePanel(); + } + this.open = false; + this.softOpen = false; + } + + private ContextMenuList getMenuList() { + return this.menuList; + } + + private void initMenuList() { + if (this.menuList == null) { + this.menuList = new ContextMenuList<>("no_list") + .width(50) + .maxSize(30) + .child(new ContextMenuOption<>() + .widthRel(50) + .height(12) + .overlay(IKey.str("No options supplied"))); + } + this.menuList.setSource(this); + this.menuList.relative(this); + this.menuList.bypassLayerRestriction(); + this.direction.positioner.accept(this.menuList.flex()); + } + + private IPanelHandler getPanelHandler() { + if (this.panelHandler == null) { + this.panelHandler = IPanelHandler.simple(getPanel(), (parentPanel, player) -> new MenuPanel(getMenuList()), true); + } + return this.panelHandler; + } + + @Override + public @NotNull Result onMousePressed(int mouseButton) { + toggleMenu(false); + return Result.SUCCESS; + } + + @Override + public void onMouseEnterArea() { + super.onMouseEnterArea(); + if (!this.requiresClick) { + openMenu(true); + } + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + public void checkClose() { + if (!this.requiresClick && !isSelfOrChildHovered()) { + closeMenu(true); + if (getParent() instanceof ContextMenuList parentMenuList) { + parentMenuList.checkClose(); + } + } + } + + @Override + public void closeParent() { + closeMenu(false); + } + + @Override + public boolean isSelfOrChildHovered() { + if (IContextMenuOption.super.isSelfOrChildHovered()) return true; + if (!isOpen() || this.menuList == null) return false; + return this.menuList.isSelfOrChildHovered(); + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return isValid() && getPanel() instanceof MenuPanel ? theme.getWidgetTheme(IThemeApi.MENU_OPTION) : theme.getButtonTheme(); + } + + public W menuList(ContextMenuList menuList) { + this.menuList = menuList; + return getThis(); + } + + public W direction(Direction direction) { + this.direction = direction; + return getThis(); + } + + public W requiresClick() { + this.requiresClick = true; + return getThis(); + } + + public W openUp() { + return direction(Direction.UP); + } + + public W openDown() { + return direction(Direction.DOWN); + } + + public W openLeftUp() { + return direction(Direction.LEFT_UP); + } + + public W openLeftDown() { + return direction(Direction.LEFT_DOWN); + } + + public W openRightUp() { + return direction(Direction.RIGHT_UP); + } + + public W openRightDown() { + return direction(Direction.RIGHT_DOWN); + } + + public W openCustom() { + return direction(Direction.UNDEFINED); + } + + public enum Direction { + UP(flex -> flex.bottomRel(1f)), + DOWN(flex -> flex.topRel(1f)), + LEFT_UP(flex -> flex.rightRel(1f).bottom(0)), + LEFT_DOWN(flex -> flex.rightRel(1f).top(0)), + RIGHT_UP(flex -> flex.leftRel(1f).bottom(0)), + RIGHT_DOWN(flex -> flex.leftRel(1f).top(0)), + UNDEFINED(flex -> {}); + + private final Consumer positioner; + + Direction(Consumer positioner) { + this.positioner = positioner; + } + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java new file mode 100644 index 000000000..0ab572871 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuList.java @@ -0,0 +1,81 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widgets.ListWidget; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Experimental +public class ContextMenuList> extends ListWidget { + + private final String name; + private ContextMenuButton source; + + public ContextMenuList(String name) { + this.name = name; + padding(2); + } + + public void close() { + if (this.source != null) { + this.source.closeMenu(false); + } + } + + @NotNull + @Override + public String getName() { + return name; + } + + public boolean isSelfOrChildHovered() { + if (isBelowMouse()) return true; + for (IWidget option : getTypeChildren()) { + if ((option instanceof IContextMenuOption menuOption && menuOption.isSelfOrChildHovered()) || option.isBelowMouse()) { + return true; + } + } + return false; + } + + @Override + public void onMouseLeaveArea() { + super.onMouseLeaveArea(); + checkClose(); + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + if (!child.flex().hasHeight()) { + child.flex().height(12); + } + if (!child.flex().hasWidth()) { + child.flex().widthRel(1f); + } + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getWidgetTheme(IThemeApi.CONTEXT_MENU); + } + + public void checkClose() { + if (this.source != null && !this.source.isBelowMouse() && !isSelfOrChildHovered()) { + this.source.closeMenu(true); + this.source.checkClose(); + } + } + + void setSource(ContextMenuButton menuButton) { + this.source = menuButton; + } + + protected ContextMenuButton getSource() { + return source; + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java new file mode 100644 index 000000000..7c34decff --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/ContextMenuOption.java @@ -0,0 +1,31 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.ITheme; +import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.theme.WidgetThemeEntry; +import com.cleanroommc.modularui.widget.DelegatingSingleChildWidget; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public class ContextMenuOption> extends DelegatingSingleChildWidget implements IContextMenuOption { + + @Override + protected void onChildAdd(IWidget child) { + if (!child.flex().hasHeight()) { + child.flex().height(12); + } + if (!child.flex().hasWidth()) { + child.flex().widthRel(1f); + } + /*if (child instanceof Widget widget && widget.getWidgetThemeOverride() == null) { + widget.widgetTheme(IThemeApi.MENU_OPTION); + }*/ + } + + @Override + protected WidgetThemeEntry getWidgetThemeInternal(ITheme theme) { + return theme.getWidgetTheme(IThemeApi.MENU_OPTION); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java new file mode 100644 index 000000000..0c577c703 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/IContextMenuOption.java @@ -0,0 +1,22 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.widget.WidgetTree; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.Objects; + +@ApiStatus.Experimental +public interface IContextMenuOption extends IWidget { + + default void closeParent() { + ContextMenuList menuList = WidgetTree.findParent(this, ContextMenuList.class); + Objects.requireNonNull(menuList); + menuList.close(); + } + + default boolean isSelfOrChildHovered() { + return isBelowMouse(); + } +} diff --git a/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java new file mode 100644 index 000000000..d93d9852f --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/widgets/menu/MenuPanel.java @@ -0,0 +1,36 @@ +package com.cleanroommc.modularui.widgets.menu; + +import com.cleanroommc.modularui.api.widget.IWidget; +import com.cleanroommc.modularui.screen.ModularPanel; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public class MenuPanel extends ModularPanel { + + public MenuPanel(ContextMenuList menuList) { + super(menuList.getName()); + fullScreenInvisible(); + child(menuList); + } + + public void openSubMenu(ContextMenuList menuList) { + child(menuList); + } + + @Override + protected void onChildAdd(IWidget child) { + super.onChildAdd(child); + child.scheduleResize(); + } + + @Override + public boolean isDraggable() { + return false; + } + + @Override + public boolean closeOnOutOfBoundsClick() { + return true; + } +} diff --git a/src/main/resources/assets/modularui/textures/gui/background/menu.png b/src/main/resources/assets/modularui/textures/gui/background/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..a30f24e36fd68ed85b758a9a8a1bc5e042bbfc53 GIT binary patch literal 602 zcmV-g0;TEX>4Tx04R}tkv&MmP!xqvQ$;Bi2MdZgWN4i%6curlDi*;)X)CnqVDi#GXws0R zxHt-~1qXi?s}3&Cx;nTDg5VE`yWphgA|>9J6k5di;PO7sd*^W9eSpxcGS%#f0jg#h z=|o)20!X5qQM?&0J6U6f~e-}`e7C4rtTK|Hf* z>74h8L#!+*#OK5l1~o|h$aUG}H_j!81)do)vgvu^5V2V5V!4Z1*-(k6iNlJjQNECK zS>e3JS*_MtyHEbYU_o2SaGh!l2`nLr6hz3Vqk<|dL}}MZF_EV8xQBn#@u$coldA$o zjs?`9LUR1zfAD*@W^roLO$x?=-WS{chyZ=NK&xTf-^aGyIsyF8z?IhV*P6iWC+Urj z7Cr(7w}Ff6jwbH`mpj17lP(#OBl)R>Vi9;hqi@OsL$^R+&7E8O9H$RJmS(kl0~{Oz z<0Z;o_jq@I_uT%y)1KcC*0plaO~Nq#00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4#NNd4#NS*Z>VGd000McNliru=?WJLGXokFCNTg202y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001&cL_t(I%VR7oEd0+v1;E0gPU}B&W o7&U0rpizTHszEHU@`O?V0M3;Mdx)t(GXMYp07*qoM6N<$g6akUg#Z8m literal 0 HcmV?d00001 From d752265267c991ef90e6710a4ce427a85643af96 Mon Sep 17 00:00:00 2001 From: brachy84 Date: Fri, 14 Nov 2025 13:42:15 +0100 Subject: [PATCH 2/3] 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 3/3] 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);