package de.fullrisk.citrusDev.UI; import de.fullrisk.citrusDev.Config; import io.wispforest.owo.ui.base.BaseOwoScreen; import io.wispforest.owo.ui.component.UIComponents; import io.wispforest.owo.ui.container.FlowLayout; import io.wispforest.owo.ui.container.UIContainers; import io.wispforest.owo.ui.core.*; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FontDescription; import net.minecraft.resources.Identifier; import net.minecraft.sounds.SoundEvents; import de.fullrisk.citrusDev.UIHelper.ScalableLabel; import de.fullrisk.citrusDev.UIHelper.ScalableButton; import de.fullrisk.citrusDev.UIHelper.ScaleValue; import java.util.List; import static de.fullrisk.citrusDev.UI.Module_FPS.fpsContainer; import static de.fullrisk.citrusDev.UI.Module_FPS.toggle; public class MyScreen extends BaseOwoScreen { private static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five"); private static final Surface GLASS_PANEL = Surface.blur(5f, 15f) .and(Surface.flat(0x40FFFFFF)) .and(Surface.outline(0x64de4b)); private static final Surface DISABLED = Surface.blur(5f, 15f) .and(Surface.flat(0x401c1c1c)); private static final Surface FLAT = Surface.blur(5f, 15f) .and(Surface.flat(0x40FFFFFF)); private static final int PANEL_WIDTH = 250; private static final int PANEL_HEIGHT = 150; private static final int INNER_WIDTH = PANEL_WIDTH - 4; private static final int INNER_HEIGHT = PANEL_HEIGHT - 4; private static final int NAVBAR_HEIGHT = 24; private static final int DIVIDER_SIZE = 1; private static final int SIDEBAR_WIDTH = 34; private static final int ROW_HEIGHT = INNER_HEIGHT - NAVBAR_HEIGHT - DIVIDER_SIZE; private static final int CONTENT_WIDTH = INNER_WIDTH - SIDEBAR_WIDTH - DIVIDER_SIZE; private record TabDef(String label, Runnable onClick) {} private record SettingRow(String label, FlowLayout control) {} private FlowLayout content; private final String initialModule; public MyScreen() { this(null); } public MyScreen(String initialModule) { super(Component.literal("Citrus Dev")); this.initialModule = initialModule; Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_TOAST_IN, 1.5F, 2) ); } @Override protected OwoUIAdapter createAdapter() { return OwoUIAdapter.create(this, UIContainers::verticalFlow); } @Override protected void build(FlowLayout rootComponent) { rootComponent.gap(0); rootComponent .horizontalAlignment(HorizontalAlignment.CENTER) .verticalAlignment(VerticalAlignment.CENTER); FlowLayout panel = UIContainers.verticalFlow(Sizing.content(), Sizing.content()); panel.gap(0); panel.padding(Insets.of(2)); panel.horizontalAlignment(HorizontalAlignment.CENTER); panel.verticalAlignment(VerticalAlignment.CENTER); panel.horizontalSizing(Sizing.fixed(0)); panel.verticalSizing(Sizing.fixed(0)); panel.horizontalSizing().animate(300, Easing.CUBIC, Sizing.fixed(PANEL_WIDTH)).forwards(); panel.verticalSizing().animate(300, Easing.CUBIC, Sizing.fixed(PANEL_HEIGHT)).forwards(); panel.surface(GLASS_PANEL); FlowLayout navbar = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(NAVBAR_HEIGHT)); buildNavbar(navbar); panel.child(navbar); FlowLayout navDivider = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(DIVIDER_SIZE)); navDivider.surface(Surface.flat(0x80FFFFFF)); panel.child(navDivider); FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(ROW_HEIGHT)); row.gap(0); FlowLayout sidebar = buildSidebar(); row.child(sidebar); content = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH), Sizing.fixed(ROW_HEIGHT)); content.gap(6); content.padding(Insets.of(4)); content.horizontalAlignment(HorizontalAlignment.CENTER); content.verticalAlignment(VerticalAlignment.TOP); row.child(content); panel.child(row); FlowLayout sidebarDivider = UIContainers.verticalFlow(Sizing.fixed(DIVIDER_SIZE), Sizing.fixed(INNER_HEIGHT)); sidebarDivider.surface(Surface.flat(0x80FFFFFF)); sidebarDivider.positioning(Positioning.absolute(SIDEBAR_WIDTH, 0)); panel.child(sidebarDivider); var version = new ScalableLabel(Component.literal("Citrus-Dev v2.0")); version.horizontalTextAlignment(HorizontalAlignment.RIGHT); version.verticalTextAlignment(VerticalAlignment.BOTTOM); version.horizontalSizing(Sizing.fixed(202)); version.verticalSizing(Sizing.fixed(12)); version.positioning(Positioning.relative(125, 100)); version.scale().set(new ScaleValue(0.7f)); panel.child(version); rootComponent.child(panel); // Now that content is attached to the tree, decide what to show. if ("FPS".equals(initialModule)) { openModuleSettings("FPS", fpsModuleSettings()); } else { buildGeneralPanel(content); } } private void buildNavbar(FlowLayout navbar) { navbar.horizontalAlignment(HorizontalAlignment.RIGHT); navbar.verticalAlignment(VerticalAlignment.CENTER); navbar.padding(Insets.of(0, 0, 0, 24)); navbar.gap(16); navbar.child(UIComponents.texture( Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/citrus-logo.png"), 0, 0, 30, 30, 30, 30 )).horizontalAlignment(HorizontalAlignment.LEFT); List tabs = List.of( new TabDef("general", () -> { content.clearChildren(); content.horizontalAlignment(HorizontalAlignment.CENTER); content.verticalAlignment(VerticalAlignment.TOP); buildGeneralPanel(content); }), new TabDef("quests", () -> { content.clearChildren(); content.horizontalAlignment(HorizontalAlignment.CENTER); content.verticalAlignment(VerticalAlignment.TOP); buildQuestsPanel(content); }), new TabDef("settings", () -> { content.clearChildren(); content.horizontalAlignment(HorizontalAlignment.CENTER); content.verticalAlignment(VerticalAlignment.TOP); buildSettingsPanel(content); }) ); for (TabDef tab : tabs) { navbar.child(makeTab(tab.label(), tab.onClick())); } } private FlowLayout makeTab(String text, Runnable onClick) { FlowLayout tab = UIContainers.verticalFlow(Sizing.content(), Sizing.content()); tab.verticalAlignment(VerticalAlignment.CENTER); tab.horizontalAlignment(HorizontalAlignment.CENTER); tab.padding(Insets.of(2, 0, 2, 2)); tab.gap(2); var label = UIComponents.label(Component.literal(text) .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) .color(Color.ofRgb(0xCCCCCC)) .shadow(true);; tab.child(label); int underlineWidth = Math.max(text.length() * 6, 10); FlowLayout underline = UIContainers.horizontalFlow(Sizing.fixed(underlineWidth), Sizing.fixed(1)); underline.surface(Surface.flat(0x00FFFFFF)); tab.child(underline); tab.mouseEnter().subscribe(() -> { label.color(Color.ofRgb(0xFFFFFF)); underline.surface(Surface.flat(0xFFFFFFFF)); }); tab.mouseLeave().subscribe(() -> { label.color(Color.ofRgb(0xCCCCCC)); underline.surface(Surface.flat(0x00FFFFFF)); }); tab.mouseDown().subscribe((click, doubled) -> { Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2) ); onClick.run(); return true; }); return tab; } private FlowLayout buildSidebar() { FlowLayout sidebar = UIContainers.verticalFlow(Sizing.fixed(SIDEBAR_WIDTH), Sizing.fixed(ROW_HEIGHT)); sidebar.horizontalAlignment(HorizontalAlignment.CENTER); sidebar.verticalAlignment(VerticalAlignment.TOP); sidebar.padding(Insets.of(4)); sidebar.gap(2); sidebar.child(makeSidebarButton("-", 430)); sidebar.child(makeSidebarButton("+", 400)); return sidebar; } private ScalableButton makeSidebarButton(String symbol, int animateDelay) { var button = new ScalableButton(Component.literal(symbol), btn -> { Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2) ); }, 0x80FFFFFF, 0xA0FFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 1 ); button.horizontalSizing(Sizing.fixed(20)); button.verticalSizing(Sizing.fixed(20)); button.scale().set(new ScaleValue(0f)); button.scale().animate(animateDelay, Easing.CUBIC, new ScaleValue(1f)).forwards(); button.mouseEnter().subscribe(() -> button.scale().animate(120, Easing.CUBIC, new ScaleValue(1.1f)).forwards()); button.mouseLeave().subscribe(() -> button.scale().animate(120, Easing.CUBIC, new ScaleValue(1.0f)).forwards()); return button; } private void buildGeneralPanel(FlowLayout content) { FlowLayout iconButton = UIContainers.horizontalFlow(Sizing.content(), Sizing.content()); iconButton.gap(8); iconButton.verticalAlignment(VerticalAlignment.TOP); iconButton.horizontalAlignment(HorizontalAlignment.CENTER); iconButton.padding(Insets.of(6)); iconButton.horizontalSizing(Sizing.fixed(0)); iconButton.verticalSizing(Sizing.fixed(0)); iconButton.surface(toggle ? GLASS_PANEL : DISABLED); iconButton.child(UIComponents.texture( Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), 0, 0, 18, 12, 18, 12 )).horizontalAlignment(HorizontalAlignment.LEFT) .verticalAlignment(VerticalAlignment.CENTER); FlowLayout textStack = UIContainers.verticalFlow(Sizing.content(), Sizing.content()); textStack.gap(2); textStack.horizontalAlignment(HorizontalAlignment.LEFT); textStack.verticalAlignment(VerticalAlignment.CENTER); var fpsLabel = UIComponents.label( Component.literal("FPS") .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE))) ).color(Color.ofRgb(toggle ? 0xffffff : 0x797b86)); textStack.child(fpsLabel); textStack.child(UIComponents.label( Component.literal("Displays your FPS")) .color(Color.ofRgb(0xa3a3ac)) .maxWidth(130) ); iconButton.child(textStack); iconButton.child(UIComponents.spacer()); FlowLayout settingsButton = makeSmallButton( FLAT, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"), () -> { Config.get().lastModule = "FPS"; Config.get().save(); openModuleSettings("FPS", fpsModuleSettings()); } ); iconButton.child(settingsButton); iconButton.mouseEnter().subscribe(() -> iconButton.surface(toggle ? GLASS_PANEL : DISABLED)); iconButton.mouseLeave().subscribe(() -> iconButton.surface(toggle ? GLASS_PANEL : DISABLED)); iconButton.mouseDown().subscribe((click, doubled) -> { toggle = !toggle; fpsLabel.color(Color.ofRgb(toggle ? 0xffffff : 0x797b86)); iconButton.surface(toggle ? GLASS_PANEL : DISABLED); Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2) ); return true; }); iconButton.horizontalSizing().animate(150, Easing.CUBIC, Sizing.fixed(200)).forwards(); iconButton.verticalSizing().animate(150, Easing.CUBIC, Sizing.fixed(35)).forwards(); content.child(iconButton); } private void buildQuestsPanel(FlowLayout content) { content.child(UIComponents.label(Component.literal("Quests - coming soon"))); } private void buildSettingsPanel(FlowLayout content) { content.child(UIComponents.label(Component.literal("Settings"))); } /** * Opens a per-module settings view inside the "content" container, * replacing whatever was there before. A back arrow returns to General. */ private void openModuleSettings(String title, List rows) { content.clearChildren(); // re-assert alignment explicitly - clearChildren can reset it content.horizontalAlignment(HorizontalAlignment.LEFT); content.verticalAlignment(VerticalAlignment.TOP); FlowLayout header = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); header.verticalAlignment(VerticalAlignment.CENTER); header.horizontalAlignment(HorizontalAlignment.LEFT); header.gap(6); header.child(makeTextButton(FLAT, "<", () -> { content.clearChildren(); content.horizontalAlignment(HorizontalAlignment.CENTER); content.verticalAlignment(VerticalAlignment.TOP); buildGeneralPanel(content); })); header.child(UIComponents.label(Component.literal(title) .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) .color(Color.ofRgb(0xCCCCCC))); content.child(header); for (SettingRow row : rows) { content.child(makeSettingRow(row)); } } private FlowLayout makeSettingRow(SettingRow setting) { FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); row.verticalAlignment(VerticalAlignment.TOP); row.horizontalAlignment(HorizontalAlignment.LEFT); row.padding(Insets.of(2)); row.gap(6); row.child(UIComponents.label(Component.literal(setting.label())) .color(Color.ofRgb(0xa3a3ac))); row.child(UIComponents.spacer()); row.child(setting.control()); return row; } private List fpsModuleSettings() { return List.of( new SettingRow("Position", makeCyclingButton()) ); } private record ButtonState(String label, Runnable onSelect) {} private FlowLayout makeCyclingButton() { List states = List.of( new ButtonState("Off", () -> { Module_FPS.setPosition(1, 1); }), new ButtonState("Low", () -> { Module_FPS.setPosition(99, 1); }), new ButtonState("Medium", () -> { Module_FPS.setPosition(99, 99); }), new ButtonState("High", () -> { Module_FPS.setPosition(1, 99); }) ); int[] currentIndex = {0}; FlowLayout button = UIContainers.verticalFlow(Sizing.fixed(50), Sizing.fixed(16)); button.verticalAlignment(VerticalAlignment.CENTER); button.horizontalAlignment(HorizontalAlignment.CENTER); button.surface(GLASS_PANEL); var label = UIComponents.label(Component.literal(states.get(0).label())) .color(Color.ofRgb(0xFFFFFF)); button.child(label); button.mouseDown().subscribe((click, doubled) -> { Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2) ); currentIndex[0] = (currentIndex[0] + 1) % states.size(); ButtonState newState = states.get(currentIndex[0]); label.text(Component.literal(newState.label())); newState.onSelect().run(); return true; }); button.mouseEnter().subscribe(() -> { label.color(Color.ofRgb(0xFFFFFF)); }); button.mouseLeave().subscribe(() -> { label.color(Color.ofRgb(0xCCCCCC)); }); return button; } private FlowLayout makeSmallButton(Surface baseSurface, Identifier texture, Runnable onClick) { FlowLayout button = UIContainers.verticalFlow(Sizing.fixed(16), Sizing.fixed(16)); button.verticalAlignment(VerticalAlignment.CENTER); button.horizontalAlignment(HorizontalAlignment.CENTER); button.surface(baseSurface); button.child(UIComponents.texture(texture, 0, 0, 12, 12, 12, 12)) .horizontalAlignment(HorizontalAlignment.CENTER) .verticalAlignment(VerticalAlignment.CENTER); button.mouseEnter().subscribe(() -> button.surface( Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF)) )); button.mouseLeave().subscribe(() -> button.surface(baseSurface)); button.mouseDown().subscribe((click, doubled) -> { Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2) ); onClick.run(); return true; }); return button; } private FlowLayout makeTextButton(Surface baseSurface, String text, Runnable onClick) { FlowLayout button = UIContainers.verticalFlow(Sizing.content(), Sizing.content()); button.verticalAlignment(VerticalAlignment.CENTER); button.horizontalAlignment(HorizontalAlignment.CENTER); button.padding(Insets.of(2, 2, 4, 4)); button.surface(baseSurface); button.child(UIComponents.label(Component.literal(text)).color(Color.ofRgb(0xFFFFFF))); button.mouseEnter().subscribe(() -> button.surface( Surface.blur(5f, 15f).and(Surface.flat(0x70FFFFFF)).and(Surface.outline(0xFFFFFFFF)) )); button.mouseLeave().subscribe(() -> button.surface(baseSurface)); button.mouseDown().subscribe((click, doubled) -> { Minecraft.getInstance().getSoundManager().play( SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2) ); onClick.run(); return true; }); return button; } @Override public boolean isPauseScreen() { return false; } }