package de.fullrisk.citrusDev.UI; import de.fullrisk.citrusDev.Config; import de.fullrisk.citrusDev.UIHelper.*; 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.client.PlaytimeTracker; import java.util.List; import java.util.concurrent.Flow; import static de.fullrisk.citrusDev.UI.Module_FPS.fpsContainer; import static de.fullrisk.citrusDev.UI.Module_FPS.toggle; public class MyScreen extends BaseOwoScreen { static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five"); 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 record QuestData(String title, String description, int requiredPlaytime) { boolean isUnlocked() { return PlaytimeTracker.getCurrentPlaytimeSeconds() >= requiredPlaytime; } } 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(2); 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); if (initialModule != null) { openModuleByKey(initialModule); } 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); QuestsPanel.build(content, CONTENT_WIDTH, ROW_HEIGHT); }), 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 fpsRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), 18, 12, MC_FIVE, "FPS", "Displays your FPS", new ModuleRow.ToggleState() { public boolean get() { return toggle; } public void set(boolean value) { toggle = value; } }, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"), () -> { Config.get().lastModule = "FPS"; Config.get().save(); openModuleSettings("FPS", fpsModuleSettings()); }, "Module Enabled", "FPS counter is now active", "Module Disabled", "FPS counter is now inactive" ); FlowLayout keyStrokesRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), 18, 12, MC_FIVE, "Keystrokes", "Current Key Presses", new ModuleRow.ToggleState() { public boolean get() { return Module_KeyStrokes.isEnabled(); } public void set(boolean value) { Module_KeyStrokes.setEnabled(value); } }, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"), () -> { Config.get().lastModule = "Key"; Config.get().save(); openModuleSettings("Key", keyStrokesSettings()); }, "Module Enabled", "Keystrokes is now active", "Module Disabled", "KeyStrokes is now inactive" ); FlowLayout coordinatesRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), 18, 12, MC_FIVE, "Coordinates", "Shows your Position", new ModuleRow.ToggleState() { public boolean get() { return Module_Coords.toggle; } public void set(boolean value) { Module_Coords.toggle = value; } }, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"), () -> { Config.get().lastModule = "Coords"; Config.get().save(); openModuleSettings("Coords", CoordsSettings()); }, "Module Enabled", "Keystrokes is now active", "Module Disabled", "Keystrokes is now inactive" ); FlowLayout fullbrightRow = ModuleRow.build( GLASS_PANEL, DISABLED, FLAT, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), 18, 12, MC_FIVE, "Fullbright", "Max brightness in dark areas", new ModuleRow.ToggleState() { public boolean get() { return Module_Fullbright.isEnabled(); } public void set(boolean value) { Module_Fullbright.setEnabled(value); } }, Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"), () -> { Config.get().lastModule = "Fullbright"; Config.get().save(); }, "Module Enabled", "Fullbright is now active", "Module Disabled", "Fullbright is now inactive" ); content.child(coordinatesRow); content.child(keyStrokesRow); content.child(fpsRow); content.child(fullbrightRow); } private void buildSettingsPanel(FlowLayout content) { FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); rowsContainer.gap(2); rowsContainer.verticalAlignment(VerticalAlignment.TOP); rowsContainer.horizontalAlignment(HorizontalAlignment.LEFT); rowsContainer.padding(Insets.of(0)); FlowLayout editButton = SettingsControls.makeTextButton(GLASS_PANEL, "Edit Positions", () -> { DraggableHud.setEditMode(true); Minecraft.getInstance().setScreen(null); }); editButton.horizontalSizing(Sizing.fixed(CONTENT_WIDTH - 8)); rowsContainer.child(editButton); FlowLayout hintRow = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); hintRow.padding(Insets.of(4, 0, 0, 0)); hintRow.child(UIComponents.label(Component.literal("Drag HUD modules to reposition. Press ESC to stop.")) .color(Color.ofRgb(0x797b86))); rowsContainer.child(hintRow); content.child(rowsContainer); } private void openModuleSettings(String title, List rows) { content.clearChildren(); 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(4); header.child(SettingsControls.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); FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content()); rowsContainer.gap(1); rowsContainer.verticalAlignment(VerticalAlignment.TOP); rowsContainer.horizontalAlignment(HorizontalAlignment.LEFT); rowsContainer.padding(Insets.of(0)); for (SettingRow row : rows) { FlowLayout settingRow = makeSettingRow(row); settingRow.verticalSizing(Sizing.fixed(16)); // pin each row to a compact fixed height rowsContainer.child(settingRow); } var scrollArea = UIContainers.verticalScroll( Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(ROW_HEIGHT - 24), rowsContainer ); content.child(scrollArea); } private void openModuleByKey(String key) { switch (key) { case "FPS" -> openModuleSettings("FPS", fpsModuleSettings()); case "Key" -> openModuleSettings("Keystrokes", keyStrokesSettings()); default -> buildGeneralPanel(content); } } private FlowLayout makeSettingRow(SettingRow setting) { FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16)); row.verticalAlignment(VerticalAlignment.CENTER); row.horizontalAlignment(HorizontalAlignment.LEFT); row.padding(Insets.of(0)); row.gap(4); 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() { List positionStates = List.of( new SettingsControls.ButtonState("L-TOP", () -> Module_FPS.setPosition(1, 1)), new SettingsControls.ButtonState("R-TOP", () -> Module_FPS.setPosition(99, 1)), new SettingsControls.ButtonState("R-BOTTOM", () -> Module_FPS.setPosition(99, 99)), new SettingsControls.ButtonState("L-BOTTOM", () -> Module_FPS.setPosition(1, 99)) ); FlowLayout paddingSlider = SettingsControls.makeSlider( FLAT, 80, 0.0, 15.0, Config.get().fpsSize, value -> (int) value + "px", value -> Module_FPS.setFpsPadding((int) value) ); int currentIndex = positionIndex(Config.get().fpsX, Config.get().fpsY); return List.of( new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)), new SettingRow("Size", paddingSlider) ); } private List keyStrokesSettings() { List positionStates = List.of( new SettingsControls.ButtonState("L-TOP", () -> Module_KeyStrokes.setPosition(1, 1)), new SettingsControls.ButtonState("R-TOP", () -> Module_KeyStrokes.setPosition(99, 1)), new SettingsControls.ButtonState("R-BOTTOM", () -> Module_KeyStrokes.setPosition(99, 99)), new SettingsControls.ButtonState("L-BOTTOM", () -> Module_KeyStrokes.setPosition(1, 99)) ); FlowLayout sizeSlider = SettingsControls.makeSlider( FLAT, 80, 10.0, 30.0, Config.get().keySize, value -> (int) value + "px", value -> Module_KeyStrokes.setKeySize((int) value) ); int currentIndex = positionIndex(Config.get().keyX, Config.get().keyY); return List.of( new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)), new SettingRow("Size", sizeSlider) ); } private List CoordsSettings() { List positionStates = List.of( new SettingsControls.ButtonState("L-TOP", () -> Module_Coords.setPosition(1, 1)), new SettingsControls.ButtonState("R-TOP", () -> Module_Coords.setPosition(99, 1)), new SettingsControls.ButtonState("R-BOTTOM", () -> Module_Coords.setPosition(99, 99)), new SettingsControls.ButtonState("L-BOTTOM", () -> Module_Coords.setPosition(1, 99)) ); int currentIndex = positionIndex(Config.get().fpsX, Config.get().fpsY); return List.of( new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)) ); } private int positionIndex(int x, int y) { boolean right = x > 50; boolean bottom = y > 50; if (!right && !bottom) return 0; // L-TOP if (right && !bottom) return 1; // R-TOP if (right && bottom) return 2; // R-BOTTOM return 3; // L-BOTTOM } @Override public boolean isPauseScreen() { return false; } }