Reworked ENTIRE UI (God help me) and cleaned up MyScreen.java (from 700 lines to 400)

This commit is contained in:
2026-07-13 20:27:49 +02:00
parent 7dbf06698e
commit 0391cdd1ca
9 changed files with 664 additions and 746 deletions
@@ -44,6 +44,7 @@ public class Config {
public boolean effectsNameEnabled = true; public boolean effectsNameEnabled = true;
public float panelBlur = 5.0f; public float panelBlur = 5.0f;
public boolean darkMode = false;
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@@ -0,0 +1,434 @@
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 java.util.Arrays;
import java.util.List;
import java.util.Map;
public class MainUI extends BaseOwoScreen<FlowLayout> {
public static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five");
public static Surface PANEL_SURFACE = buildPanelSurface();
public static Surface GLASS_PANEL = buildGlassSurface();
public static Surface DISABLED = buildDisabledSurface();
public static Surface FLAT = buildFlatSurface();
private static Surface buildPanelSurface() {
float blur = Config.get().panelBlur;
int fill = Config.get().darkMode ? 0x601c1c1c : 0x40FFFFFF;
if (blur <= 0f) return Surface.flat(fill).and(Surface.outline(0x64de4b));
return Surface.blur(blur, 15f).and(Surface.flat(fill)).and(Surface.outline(0x64de4b));
}
private static Surface buildGlassSurface() {
int fill = Config.get().darkMode ? 0x70282828 : 0x40FFFFFF;
return Surface.blur(5f, 15f).and(Surface.flat(fill)).and(Surface.outline(0x64de4b));
}
private static Surface buildDisabledSurface() {
int fill = Config.get().darkMode ? 0x30050505 : 0x401c1c1c;
return Surface.blur(5f, 15f).and(Surface.flat(fill));
}
private static Surface buildFlatSurface() {
int fill = Config.get().darkMode ? 0x601c1c1c : 0x40FFFFFF;
return Surface.blur(5f, 15f).and(Surface.flat(fill));
}
public static void rebuildSurfaces() {
PANEL_SURFACE = buildPanelSurface();
GLASS_PANEL = buildGlassSurface();
DISABLED = buildDisabledSurface();
FLAT = buildFlatSurface();
if (panelRef != null) panelRef.surface(PANEL_SURFACE);
}
private static int text() { return Config.get().darkMode ? 0xFFd0d4da : 0xFFb0b6c0; }
private static int bright() { return 0xFFFFFF; }
private static int tabText() { return 0xCCCCCC; }
public static final int PANEL_WIDTH = 320;
public static final int PANEL_HEIGHT = 190;
public static final int INNER_WIDTH = PANEL_WIDTH - 4;
public static final int INNER_HEIGHT = PANEL_HEIGHT - 4;
public static final int NAVBAR_HEIGHT = 24;
public static final int DIVIDER_SIZE = 1;
public static final int SIDEBAR_WIDTH = 44;
public static final int ROW_HEIGHT = INNER_HEIGHT - NAVBAR_HEIGHT - DIVIDER_SIZE;
public static final int CONTENT_WIDTH = INNER_WIDTH - SIDEBAR_WIDTH - DIVIDER_SIZE;
private static final Map<String, String[]> MODULE_TAGS = Map.of(
"FPS", new String[]{"HUD"},
"Keystrokes", new String[]{"HUD"},
"Coordinates", new String[]{"HUD"},
"Fullbright", new String[]{"World"},
"Hitbox", new String[]{"Combat"},
"Effects", new String[]{"HUD"}
);
private static final String[] CATEGORIES = {"ALL", "HUD", "Combat", "World"};
private record TabDef(String label, Runnable onClick) {}
private FlowLayout content;
private final String initialModule;
private static FlowLayout panelRef;
private String activeCategory = "ALL";
private static String searchText = "";
private FlowLayout moduleListContainer;
private List<ModuleRows.Entry> allModuleRows;
public MainUI() { this(null); }
public MainUI(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<FlowLayout> 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());
panelRef = panel;
panel.gap(0);
panel.padding(Insets.of(2));
panel.horizontalAlignment(HorizontalAlignment.CENTER).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(PANEL_SURFACE);
FlowLayout navbar = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(NAVBAR_HEIGHT));
navbar.verticalAlignment(VerticalAlignment.CENTER);
navbar.horizontalAlignment(HorizontalAlignment.CENTER);
navbar.gap(16);
var logo = UIComponents.texture(Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/citrus-logo.png"), 0, 0, 30, 30, 30, 30);
logo.positioning(Positioning.absolute(4, (NAVBAR_HEIGHT - 30) / 2));
navbar.child(logo);
for (TabDef tab : List.of(
new TabDef("general", () -> switchToContent(c -> buildGeneralPanel(c))),
new TabDef("quests", () -> switchToContent(c -> QuestsPanel.build(c, CONTENT_WIDTH, ROW_HEIGHT))),
new TabDef("settings", () -> switchToContent(this::buildSettingsPanel))
)) {
navbar.child(makeTab(tab.label(), tab.onClick()));
}
panel.child(navbar);
FlowLayout navDividerSpacer = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(DIVIDER_SIZE));
panel.child(navDividerSpacer);
FlowLayout navDivider = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(DIVIDER_SIZE));
navDivider.surface(Surface.flat(0x80FFFFFF));
navDivider.positioning(Positioning.absolute(0, NAVBAR_HEIGHT));
panel.child(navDivider);
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(INNER_WIDTH), Sizing.fixed(ROW_HEIGHT));
row.gap(0);
row.child(buildSidebar());
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);
var version = new ScalableLabel(Component.literal("Citrus-Dev v2.0"));
version.horizontalTextAlignment(HorizontalAlignment.RIGHT);
version.verticalTextAlignment(VerticalAlignment.BOTTOM);
version.horizontalSizing(Sizing.fixed(CONTENT_WIDTH - 8));
version.verticalSizing(Sizing.fixed(12));
version.positioning(Positioning.relative(100, 100));
version.scale().set(new ScaleValue(0.7f));
panel.child(version);
rootComponent.child(panel);
if (initialModule != null) openModuleByKey(initialModule);
else buildGeneralPanel(content);
}
private void switchToContent(java.util.function.Consumer<FlowLayout> builder) {
content.clearChildren();
content.horizontalAlignment(HorizontalAlignment.CENTER);
content.verticalAlignment(VerticalAlignment.TOP);
builder.accept(content);
}
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(s -> s.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(tabText())).shadow(true);
tab.child(label);
FlowLayout underline = UIContainers.horizontalFlow(Sizing.fixed(Math.max(text.length() * 6, 10)), Sizing.fixed(1));
underline.surface(Surface.flat(0x00FFFFFF));
tab.child(underline);
tab.mouseEnter().subscribe(() -> { label.color(Color.ofRgb(bright())); underline.surface(Surface.flat(0xFFFFFFFF)); });
tab.mouseLeave().subscribe(() -> { label.color(Color.ofRgb(tabText())); 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;
}
// --- Sidebar ---
private static int sidebarBtnFill() { return Config.get().darkMode ? 0x803a3a3a : 0x80FFFFFF; }
private static int sidebarBtnHover() { return Config.get().darkMode ? 0xA0505050 : 0xA0FFFFFF; }
private static int sidebarBtnOutline() { return Config.get().darkMode ? 0xFF888888 : 0xFFFFFFFF; }
private static int sidebarBtnHoverOutline() { return Config.get().darkMode ? 0xFFcccccc : 0xFFFFFFFF; }
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));
sidebar.child(UIComponents.spacer());
// Edit button
FlowLayout editButton = UIContainers.verticalFlow(Sizing.fixed(36), Sizing.fixed(18));
editButton.horizontalAlignment(HorizontalAlignment.CENTER);
editButton.verticalAlignment(VerticalAlignment.CENTER);
editButton.surface(Surface.flat(sidebarBtnFill()).and(Surface.outline(sidebarBtnOutline())));
editButton.child(UIComponents.texture(Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/edit.png"), 0, 0, 16, 16, 16, 16));
editButton.mouseEnter().subscribe(() -> editButton.surface(Surface.flat(sidebarBtnHover()).and(Surface.outline(sidebarBtnHoverOutline()))));
editButton.mouseLeave().subscribe(() -> editButton.surface(Surface.flat(sidebarBtnFill()).and(Surface.outline(sidebarBtnOutline()))));
editButton.mouseDown().subscribe((click, doubled) -> {
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2));
DraggableHud.setEditMode(true);
Minecraft.getInstance().setScreen(null);
return true;
});
sidebar.child(editButton);
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)),
sidebarBtnFill(), sidebarBtnHover(), sidebarBtnOutline(), sidebarBtnHoverOutline(), 1);
button.horizontalSizing(Sizing.fixed(36));
button.verticalSizing(Sizing.fixed(18));
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;
}
// --- Category bar ---
private FlowLayout buildCategoryBar(Runnable onFilterChanged) {
FlowLayout bar = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(18));
bar.gap(4);
bar.verticalAlignment(VerticalAlignment.CENTER);
bar.horizontalAlignment(HorizontalAlignment.LEFT);
for (String cat : CATEGORIES) {
boolean isActive = cat.equals(activeCategory);
FlowLayout catButton = UIContainers.verticalFlow(Sizing.fixed(Math.max(cat.length() * 7 + 8, 30)), Sizing.fixed(16));
catButton.verticalAlignment(VerticalAlignment.CENTER);
catButton.horizontalAlignment(HorizontalAlignment.CENTER);
catButton.padding(Insets.of(2, 0, 2, 0));
catButton.surface(isActive
? Surface.flat(0x6064de4b).and(Surface.outline(0x64de4b))
: Surface.flat(0x30FFFFFF).and(Surface.outline(0x64de4b)));
var catLabel = UIComponents.label(Component.literal(cat).withStyle(s -> s.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(isActive ? bright() : tabText()))
.horizontalTextAlignment(HorizontalAlignment.CENTER);
catButton.child(catLabel);
catButton.mouseEnter().subscribe(() -> { if (!cat.equals(activeCategory)) catButton.surface(Surface.flat(0x50FFFFFF).and(Surface.outline(0xFFFFFFFF))); });
catButton.mouseLeave().subscribe(() -> { if (!cat.equals(activeCategory)) catButton.surface(Surface.flat(0x30FFFFFF).and(Surface.outline(0x64de4b))); });
catButton.mouseDown().subscribe((click, doubled) -> {
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2));
activeCategory = cat;
onFilterChanged.run();
return true;
});
bar.child(catButton);
}
bar.child(UIComponents.spacer());
FlowLayout searchWrapper = UIContainers.horizontalFlow(Sizing.fixed(90), Sizing.fixed(16));
searchWrapper.verticalAlignment(VerticalAlignment.CENTER);
searchWrapper.horizontalAlignment(HorizontalAlignment.RIGHT);
searchWrapper.padding(Insets.of(0, 4, 0, 4));
searchWrapper.surface(FLAT.and(Surface.outline(0x64de4b)));
var searchBox = UIComponents.textBox(Sizing.fixed(82));
searchBox.setValue(searchText);
searchBox.setTextColor(text());
searchBox.setHint(Component.literal("Search...").withStyle(s -> s.withColor(tabText())));
searchBox.setFilter(s -> true);
searchBox.setBordered(false);
searchBox.onChanged().subscribe(newText -> {
searchText = newText;
rebuildModuleList();
});
searchWrapper.child(searchBox);
bar.child(searchWrapper);
return bar;
}
private boolean matchesFilter(String moduleName) {
if (!activeCategory.equals("ALL")) {
String[] tags = MODULE_TAGS.get(moduleName);
if (tags == null || !Arrays.asList(tags).contains(activeCategory)) return false;
}
return searchText.isEmpty() || moduleName.toLowerCase().contains(searchText.toLowerCase());
}
// --- General panel ---
private void buildGeneralPanel(FlowLayout content) {
content.child(buildCategoryBar(() -> { content.clearChildren(); buildGeneralPanel(content); }));
allModuleRows = ModuleRows.buildAll(this::openModuleByKey);
moduleListContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
moduleListContainer.gap(4);
moduleListContainer.verticalAlignment(VerticalAlignment.TOP);
moduleListContainer.horizontalAlignment(HorizontalAlignment.CENTER);
rebuildModuleList();
content.child(UIContainers.verticalScroll(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(ROW_HEIGHT - 24), moduleListContainer));
}
private void rebuildModuleList() {
moduleListContainer.clearChildren();
for (ModuleRows.Entry entry : allModuleRows) {
if (matchesFilter(entry.name())) moduleListContainer.child(entry.row());
}
}
// --- Settings panel ---
private void buildSettingsPanel(FlowLayout content) {
FlowLayout rows = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
rows.gap(8);
rows.verticalAlignment(VerticalAlignment.TOP);
rows.horizontalAlignment(HorizontalAlignment.LEFT);
// Blur slider
rows.child(makeSettingRow("Panel Blur", SettingsControls.makeSlider(
FLAT, 80, 0.0, 15.0, Config.get().panelBlur,
value -> (int) value + "px",
value -> { Config.get().panelBlur = (float) (double) value; Config.get().save(); rebuildSurfaces(); }
)));
// Dark mode toggle
rows.child(makeSettingRow("Dark Mode", SettingsControls.makeCheckbox(
FLAT, GLASS_PANEL, Config.get().darkMode, value -> {
Config.get().darkMode = value;
Config.get().save();
rebuildSurfaces();
Minecraft.getInstance().setScreen(new MainUI());
}
)));
content.child(rows);
}
private FlowLayout makeSettingRow(String label, FlowLayout control) {
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16));
row.verticalAlignment(VerticalAlignment.CENTER);
row.horizontalAlignment(HorizontalAlignment.LEFT);
row.gap(4);
row.child(UIComponents.label(Component.literal(label).withStyle(s -> s.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(text())));
row.child(UIComponents.spacer());
row.child(control);
return row;
}
// --- Module settings ---
private void openModuleSettings(String title, List<ModuleSettings.SettingRow> rows) {
content.clearChildren();
content.horizontalAlignment(HorizontalAlignment.LEFT);
content.verticalAlignment(VerticalAlignment.TOP);
// Header with back button
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, "<", () -> switchToContent(this::buildGeneralPanel)));
header.child(UIComponents.label(Component.literal(title).withStyle(s -> s.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(bright())));
content.child(header);
// Setting rows
FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
rowsContainer.gap(1);
rowsContainer.verticalAlignment(VerticalAlignment.TOP);
rowsContainer.horizontalAlignment(HorizontalAlignment.LEFT);
for (ModuleSettings.SettingRow setting : rows) {
FlowLayout row = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16));
row.verticalAlignment(VerticalAlignment.CENTER);
row.horizontalAlignment(HorizontalAlignment.LEFT);
row.gap(4);
row.child(UIComponents.label(Component.literal(setting.label()).withStyle(s -> s.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(text())));
row.child(UIComponents.spacer());
row.child(setting.control());
rowsContainer.child(row);
}
content.child(UIContainers.verticalScroll(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(ROW_HEIGHT - 24), rowsContainer));
}
private void openModuleByKey(String key) {
switch (key) {
case "FPS" -> openModuleSettings("FPS", ModuleSettings.fps());
case "Key" -> openModuleSettings("Keystrokes", ModuleSettings.keystrokes());
case "Coords" -> openModuleSettings("Coordinates", ModuleSettings.coords());
case "Hitbox" -> openModuleSettings("Hitbox", ModuleSettings.hitbox(this::openModuleByKey));
case "Effects" -> openModuleSettings("Effects", ModuleSettings.effects());
default -> buildGeneralPanel(content);
}
}
@Override
public boolean isPauseScreen() { return false; }
}
@@ -1,730 +0,0 @@
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.Set;
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<FlowLayout> {
static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five");
static Surface PANEL_SURFACE = buildPanelSurface();
static Surface GLASS_PANEL = Surface.blur(5f, 15f)
.and(Surface.flat(0x40FFFFFF))
.and(Surface.outline(0x64de4b));
private static Surface DISABLED = Surface.blur(5f, 15f)
.and(Surface.flat(0x401c1c1c));
private static Surface FLAT = Surface.blur(5f, 15f)
.and(Surface.flat(0x40FFFFFF));
private static Surface buildPanelSurface() {
float blur = Config.get().panelBlur;
if (blur <= 0f) {
return Surface.flat(0x40FFFFFF)
.and(Surface.outline(0x64de4b));
}
return Surface.blur(blur, 15f)
.and(Surface.flat(0x40FFFFFF))
.and(Surface.outline(0x64de4b));
}
public static void rebuildSurfaces() {
PANEL_SURFACE = buildPanelSurface();
if (panelRef != null) {
panelRef.surface(PANEL_SURFACE);
}
}
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;
private static FlowLayout panelRef;
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<FlowLayout> 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());
panelRef = panel;
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(PANEL_SURFACE);
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<TabDef> 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));
sidebar.child(UIComponents.spacer());
FlowLayout editButton = UIContainers.verticalFlow(Sizing.fixed(20), Sizing.fixed(20));
editButton.horizontalAlignment(HorizontalAlignment.CENTER);
editButton.verticalAlignment(VerticalAlignment.CENTER);
editButton.surface(Surface.flat(0x80FFFFFF).and(Surface.outline(0xFFFFFFFF)));
editButton.child(UIComponents.texture(
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/edit.png"),
0, 0, 16, 16, 16, 16
));
editButton.mouseEnter().subscribe(() ->
editButton.surface(Surface.flat(0xA0FFFFFF).and(Surface.outline(0xFFFFFFFF))));
editButton.mouseLeave().subscribe(() ->
editButton.surface(Surface.flat(0x80FFFFFF).and(Surface.outline(0xFFFFFFFF))));
editButton.mouseDown().subscribe((click, doubled) -> {
Minecraft.getInstance().getSoundManager().play(
SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 2.0F, 2)
);
DraggableHud.setEditMode(true);
Minecraft.getInstance().setScreen(null);
return true;
});
sidebar.child(editButton);
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/fps.png"),
18, 18,
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/keystrokes.png"),
24, 24,
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("minecraft", "textures/item/map.png"),
18, 18,
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;
Config.get().coordsEnabled = value;
Config.get().save();
}
},
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/fullbright.png"),
18, 18,
MC_FIVE,
"Fullbright",
"Max brightness",
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"
);
FlowLayout hitboxRow = ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"),
18, 12,
MC_FIVE,
"hitbox",
"Customize hitboxes",
new ModuleRow.ToggleState() {
public boolean get() { return Module_Hitbox.isEnabled(); }
public void set(boolean value) { Module_Hitbox.setEnabled(value); }
},
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"),
() -> {
Config.get().lastModule = "Hitbox";
Config.get().save();
openModuleSettings("Hitbox", hitboxSettings());
},
"Module Enabled", "Hitbox color is now active",
"Module Disabled", "Hitbox color is now inactive"
);
FlowLayout effectsRow = ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/effect.png"),
18, 18,
MC_FIVE,
"Effects",
"Shows active effects",
new ModuleRow.ToggleState() {
public boolean get() { return Module_Effects.isEnabled(); }
public void set(boolean value) { Module_Effects.setEnabled(value); }
},
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png"),
() -> {
Config.get().lastModule = "Effects";
Config.get().save();
openModuleSettings("Effects", effectsSettings());
},
"Module Enabled", "Effects display is now active",
"Module Disabled", "Effects display is now inactive"
);
FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
rowsContainer.gap(4);
rowsContainer.verticalAlignment(VerticalAlignment.TOP);
rowsContainer.horizontalAlignment(HorizontalAlignment.CENTER);
rowsContainer.child(effectsRow);
rowsContainer.child(hitboxRow);
rowsContainer.child(coordinatesRow);
rowsContainer.child(keyStrokesRow);
rowsContainer.child(fpsRow);
rowsContainer.child(fullbrightRow);
var scrollArea = UIContainers.verticalScroll(
Sizing.fixed(CONTENT_WIDTH - 8),
Sizing.fixed(ROW_HEIGHT- 8),
rowsContainer
);
content.child(scrollArea);
}
private void buildSettingsPanel(FlowLayout content) {
FlowLayout rowsContainer = UIContainers.verticalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.content());
rowsContainer.gap(8);
rowsContainer.verticalAlignment(VerticalAlignment.TOP);
rowsContainer.horizontalAlignment(HorizontalAlignment.LEFT);
rowsContainer.padding(Insets.of(0));
FlowLayout blurSlider = SettingsControls.makeSlider(
FLAT,
80,
0.0, 15.0,
Config.get().panelBlur,
value -> (int) value + "px",
value -> {
Config.get().panelBlur = (float) (double) value;
Config.get().save();
rebuildSurfaces();
}
);
FlowLayout blurRow = UIContainers.horizontalFlow(Sizing.fixed(CONTENT_WIDTH - 8), Sizing.fixed(16));
blurRow.verticalAlignment(VerticalAlignment.CENTER);
blurRow.horizontalAlignment(HorizontalAlignment.LEFT);
blurRow.padding(Insets.of(0));
blurRow.gap(4);
blurRow.child(UIComponents.label(Component.literal("Panel Blur")
.withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(0xFF5c5c66)));
blurRow.child(UIComponents.spacer());
blurRow.child(blurSlider);
rowsContainer.child(blurRow);
content.child(rowsContainer);
}
private void openModuleSettings(String title, List<SettingRow> 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(0x80FFFFFF)));
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));
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());
case "Coords" -> openModuleSettings("Coordinates", CoordsSettings());
case "Hitbox" -> openModuleSettings("Hitbox", hitboxSettings());
case "Effects" -> openModuleSettings("Effects", effectsSettings());
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())
.withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(0xFF5c5c66)));
row.child(UIComponents.spacer());
row.child(setting.control());
return row;
}
private List<SettingRow> fpsModuleSettings() {
List<SettingsControls.ButtonState> 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("", SettingsControls.makeSectionHeader(CONTENT_WIDTH - 8, "Display Options")),
new SettingRow("Padding", paddingSlider)
);
}
private List<SettingRow> keyStrokesSettings() {
List<SettingsControls.ButtonState> 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<SettingRow> CoordsSettings() {
List<SettingsControls.ButtonState> 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);
FlowLayout biomeCheckbox = SettingsControls.makeCheckbox(
FLAT,
GLASS_PANEL,
Module_Coords.isBiomeEnabled(),
Module_Coords::setBiomeEnabled
);
FlowLayout sizeSlider = SettingsControls.makeSlider(
FLAT,
80,
0.0, 15.0,
Config.get().coordsP,
value -> (int) value + "px",
value -> Module_Coords.setCoordsPadding((int) value)
);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)),
new SettingRow("Show Biome", biomeCheckbox),
new SettingRow("Padding", sizeSlider)
);
}
private List<SettingRow> hitboxSettings() {
FlowLayout enabledCheckbox = SettingsControls.makeCheckbox(
FLAT,
GLASS_PANEL,
Module_Hitbox.isEnabled(),
value -> {
Module_Hitbox.setEnabled(value);
openModuleSettings("Hitbox", hitboxSettings());
}
);
List<SettingRow> rows = new java.util.ArrayList<>();
rows.add(new SettingRow("Custom Color", enabledCheckbox));
if (Module_Hitbox.isEnabled()) {
FlowLayout hueSlider = SettingsControls.makeSlider(
FLAT,
80,
0.0, 360.0,
Module_Hitbox.getHue(),
value -> (int) (double) value + "°",
value -> Module_Hitbox.setHue((int) (double) value)
);
rows.add(new SettingRow("Color", hueSlider));
}
return rows;
}
private List<SettingRow> effectsSettings() {
List<SettingsControls.ButtonState> positionStates = List.of(
new SettingsControls.ButtonState("L-TOP", () -> Module_Effects.setPosition(1, 1)),
new SettingsControls.ButtonState("R-TOP", () -> Module_Effects.setPosition(99, 1)),
new SettingsControls.ButtonState("R-BOTTOM", () -> Module_Effects.setPosition(99, 99)),
new SettingsControls.ButtonState("L-BOTTOM", () -> Module_Effects.setPosition(1, 99))
);
int currentIndex = positionIndex(Config.get().effectsX, Config.get().effectsY);
FlowLayout bgCheck = SettingsControls.makeCheckbox(
FLAT,
GLASS_PANEL,
Module_Effects.isBackgroundEnabled(),
Module_Effects::setBackgroundEnabled
);
FlowLayout timerCheck = SettingsControls.makeCheckbox(
FLAT,
GLASS_PANEL,
Module_Effects.isTimerEnabled(),
Module_Effects::setTimerEnabled
);
FlowLayout nameCheck = SettingsControls.makeCheckbox(
FLAT,
GLASS_PANEL,
Module_Effects.isNameEnabled(),
Module_Effects::setNameEnabled
);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, currentIndex)),
new SettingRow("Show Background", bgCheck),
new SettingRow("Show Timer", timerCheck),
new SettingRow("Show Name", nameCheck)
);
}
private int positionIndex(int x, int y) {
boolean right = x > 50;
boolean bottom = y > 50;
if (!right && !bottom) return 0;
if (right && !bottom) return 1;
if (right && bottom) return 2;
return 3;
}
@Override
public boolean isPauseScreen() {
return false;
}
}
@@ -81,7 +81,7 @@ public class QuestsPanel {
statusBar.padding(Insets.of(4)); statusBar.padding(Insets.of(4));
statusBar.verticalAlignment(VerticalAlignment.CENTER); statusBar.verticalAlignment(VerticalAlignment.CENTER);
statusBar.horizontalAlignment(HorizontalAlignment.LEFT); statusBar.horizontalAlignment(HorizontalAlignment.LEFT);
statusBar.surface(MyScreen.GLASS_PANEL); statusBar.surface(MainUI.GLASS_PANEL);
LabelComponent playtimeLabel = UIComponents.label(Component.literal(formatPlaytime(playtime))); LabelComponent playtimeLabel = UIComponents.label(Component.literal(formatPlaytime(playtime)));
playtimeLabel.color(Color.ofRgb(0xFFFFFF)); playtimeLabel.color(Color.ofRgb(0xFFFFFF));
@@ -112,7 +112,7 @@ public class QuestsPanel {
summaryRow.child(UIComponents.label( summaryRow.child(UIComponents.label(
Component.literal("quests " + unlockedCount + "/" + quests.size()) Component.literal("quests " + unlockedCount + "/" + quests.size())
.withStyle(style -> style.withFont(new FontDescription.Resource(MyScreen.MC_FIVE)))) .withStyle(style -> style.withFont(new FontDescription.Resource(MainUI.MC_FIVE))))
.color(Color.ofRgb(0xFF5c5c66))); .color(Color.ofRgb(0xFF5c5c66)));
content.child(summaryRow); content.child(summaryRow);
@@ -201,7 +201,7 @@ public class QuestsPanel {
textStack.verticalAlignment(VerticalAlignment.TOP); textStack.verticalAlignment(VerticalAlignment.TOP);
var titleLabel = UIComponents.label(Component.literal(quest.title()) var titleLabel = UIComponents.label(Component.literal(quest.title())
.withStyle(style -> style.withFont(new FontDescription.Resource(MyScreen.MC_FIVE)))) .withStyle(style -> style.withFont(new FontDescription.Resource(MainUI.MC_FIVE))))
.color(Color.ofRgb(unlocked ? 0xFFFFFF : 0x797b86)); .color(Color.ofRgb(unlocked ? 0xFFFFFF : 0x797b86));
textStack.child(titleLabel); textStack.child(titleLabel);
@@ -1,5 +1,6 @@
package de.fullrisk.citrusDev.UIHelper; package de.fullrisk.citrusDev.UIHelper;
import de.fullrisk.citrusDev.Config;
import io.wispforest.owo.ui.component.UIComponents; import io.wispforest.owo.ui.component.UIComponents;
import io.wispforest.owo.ui.container.FlowLayout; import io.wispforest.owo.ui.container.FlowLayout;
import io.wispforest.owo.ui.container.UIContainers; import io.wispforest.owo.ui.container.UIContainers;
@@ -18,6 +19,17 @@ public class ModuleRow {
void set(boolean value); void set(boolean value);
} }
private static int titleColor(boolean enabled) {
if (Config.get().darkMode) {
return enabled ? 0xffffff : 0xFF8a8e96;
}
return enabled ? 0xffffff : 0xFFaab0b8;
}
private static int descColor() {
return Config.get().darkMode ? 0xFFa0a4aa : 0xFF6a6e76;
}
public static FlowLayout build( public static FlowLayout build(
Surface enabledSurface, Surface enabledSurface,
Surface disabledSurface, Surface disabledSurface,
@@ -58,12 +70,12 @@ public class ModuleRow {
Component.literal(title) Component.literal(title)
.withStyle(style -> style.withFont(new FontDescription.Resource(titleFont))) .withStyle(style -> style.withFont(new FontDescription.Resource(titleFont)))
); );
titleLabel.color(Color.ofRgb(state.get() ? 0xffffff : 0x797b86)); titleLabel.color(Color.ofRgb(titleColor(state.get())));
textStack.child(titleLabel); textStack.child(titleLabel);
textStack.child(UIComponents.label(Component.literal(description)) textStack.child(UIComponents.label(Component.literal(description))
.color(Color.ofRgb(0xFF5c5c66)) .color(Color.ofRgb(descColor()))
.maxWidth(130)); .maxWidth(190));
row.child(textStack); row.child(textStack);
row.child(UIComponents.spacer()); row.child(UIComponents.spacer());
@@ -78,7 +90,7 @@ public class ModuleRow {
boolean newState = !state.get(); boolean newState = !state.get();
state.set(newState); state.set(newState);
titleLabel.color(Color.ofRgb(newState ? 0xffffff : 0x797b86)); titleLabel.color(Color.ofRgb(titleColor(newState)));
row.surface(newState ? enabledSurface : disabledSurface); row.surface(newState ? enabledSurface : disabledSurface);
if (newState) { if (newState) {
@@ -93,7 +105,7 @@ public class ModuleRow {
return true; return true;
}); });
row.horizontalSizing().animate(150, Easing.CUBIC, Sizing.fixed(200)).forwards(); row.horizontalSizing().animate(150, Easing.CUBIC, Sizing.fixed(260)).forwards();
row.verticalSizing().animate(150, Easing.CUBIC, Sizing.fixed(35)).forwards(); row.verticalSizing().animate(150, Easing.CUBIC, Sizing.fixed(35)).forwards();
return row; return row;
@@ -0,0 +1,82 @@
package de.fullrisk.citrusDev.UIHelper;
import de.fullrisk.citrusDev.Config;
import de.fullrisk.citrusDev.UI.*;
import io.wispforest.owo.ui.container.FlowLayout;
import net.minecraft.resources.Identifier;
import java.util.List;
import java.util.function.Consumer;
import static de.fullrisk.citrusDev.UI.MainUI.*;
public class ModuleRows {
public record Entry(String name, FlowLayout row) {}
private static final Identifier SETTINGS_ICON = Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/settings.png");
public static List<Entry> buildAll(Consumer<String> openSettingsByKey) {
return List.of(
entry("Effects", ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/effect.png"), 18, 18, MC_FIVE,
"Effects", "Shows active effects",
toggleState(Module_Effects::isEnabled, Module_Effects::setEnabled),
SETTINGS_ICON, () -> { Config.get().lastModule = "Effects"; Config.get().save(); openSettingsByKey.accept("Effects"); },
"Module Enabled", "Effects display is now active",
"Module Disabled", "Effects display is now inactive")),
entry("Hitbox", ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/block_culling_file.png"), 18, 12, MC_FIVE,
"Hitbox", "Customize hitboxes",
toggleState(Module_Hitbox::isEnabled, Module_Hitbox::setEnabled),
SETTINGS_ICON, () -> { Config.get().lastModule = "Hitbox"; Config.get().save(); openSettingsByKey.accept("Hitbox"); },
"Module Enabled", "Hitbox color is now active",
"Module Disabled", "Hitbox color is now inactive")),
entry("Coordinates", ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("minecraft", "textures/item/map.png"), 18, 18, MC_FIVE,
"Coordinates", "Shows your Position",
toggleState(Module_Coords::isEnabled, Module_Coords::setEnabled),
SETTINGS_ICON, () -> { Config.get().lastModule = "Coords"; Config.get().save(); openSettingsByKey.accept("Coords"); },
"Module Enabled", "Coordinates is now active",
"Module Disabled", "Coordinates is now inactive")),
entry("Keystrokes", ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/keystrokes.png"), 24, 24, MC_FIVE,
"Keystrokes", "Current Key Presses",
toggleState(Module_KeyStrokes::isEnabled, Module_KeyStrokes::setEnabled),
SETTINGS_ICON, () -> { Config.get().lastModule = "Key"; Config.get().save(); openSettingsByKey.accept("Key"); },
"Module Enabled", "Keystrokes is now active",
"Module Disabled", "KeyStrokes is now inactive")),
entry("FPS", ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/fps.png"), 18, 18, MC_FIVE,
"FPS", "Displays your FPS",
toggleState(Module_FPS::isEnabled, Module_FPS::setEnabled),
SETTINGS_ICON, () -> { Config.get().lastModule = "FPS"; Config.get().save(); openSettingsByKey.accept("FPS"); },
"Module Enabled", "FPS counter is now active",
"Module Disabled", "FPS counter is now inactive")),
entry("Fullbright", ModuleRow.build(
GLASS_PANEL, DISABLED, FLAT,
Identifier.fromNamespaceAndPath("citrus-dev", "textures/icons/fullbright.png"), 18, 18, MC_FIVE,
"Fullbright", "Max brightness",
toggleState(Module_Fullbright::isEnabled, Module_Fullbright::setEnabled),
SETTINGS_ICON, () -> { Config.get().lastModule = "Fullbright"; Config.get().save(); },
"Module Enabled", "Fullbright is now active",
"Module Disabled", "Fullbright is now inactive"))
);
}
private static Entry entry(String name, FlowLayout row) {
return new Entry(name, row);
}
private static ModuleRow.ToggleState toggleState(java.util.function.BooleanSupplier get, java.util.function.Consumer<Boolean> set) {
return new ModuleRow.ToggleState() {
public boolean get() { return get.getAsBoolean(); }
public void set(boolean value) { set.accept(value); }
};
}
}
@@ -0,0 +1,117 @@
package de.fullrisk.citrusDev.UIHelper;
import de.fullrisk.citrusDev.Config;
import de.fullrisk.citrusDev.UI.Module_Coords;
import de.fullrisk.citrusDev.UI.Module_Effects;
import de.fullrisk.citrusDev.UI.Module_FPS;
import de.fullrisk.citrusDev.UI.Module_Hitbox;
import de.fullrisk.citrusDev.UI.Module_KeyStrokes;
import io.wispforest.owo.ui.container.FlowLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static de.fullrisk.citrusDev.UI.MainUI.*;
public class ModuleSettings {
public record SettingRow(String label, FlowLayout control) {}
public static int positionIndex(int x, int y) {
boolean right = x > 50;
boolean bottom = y > 50;
if (!right && !bottom) return 0;
if (right && !bottom) return 1;
if (right && bottom) return 2;
return 3;
}
public static List<SettingRow> fps() {
var 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)
);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, positionIndex(Config.get().fpsX, Config.get().fpsY))),
new SettingRow("", SettingsControls.makeSectionHeader(CONTENT_WIDTH - 8, "Display Options")),
new SettingRow("Padding", paddingSlider)
);
}
public static List<SettingRow> keystrokes() {
var 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)
);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, positionIndex(Config.get().keyX, Config.get().keyY))),
new SettingRow("Size", sizeSlider)
);
}
public static List<SettingRow> coords() {
var 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))
);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, positionIndex(Config.get().fpsX, Config.get().fpsY))),
new SettingRow("Show Biome", SettingsControls.makeCheckbox(FLAT, GLASS_PANEL, Module_Coords.isBiomeEnabled(), Module_Coords::setBiomeEnabled)),
new SettingRow("Padding", SettingsControls.makeSlider(FLAT, 80, 0.0, 15.0, Config.get().coordsP, value -> (int) value + "px", value -> Module_Coords.setCoordsPadding((int) value)))
);
}
public static List<SettingRow> hitbox(Consumer<String> reopenSettings) {
List<SettingRow> rows = new ArrayList<>();
rows.add(new SettingRow("Custom Color", SettingsControls.makeCheckbox(
FLAT, GLASS_PANEL, Module_Hitbox.isEnabled(),
value -> { Module_Hitbox.setEnabled(value); reopenSettings.accept("Hitbox"); }
)));
if (Module_Hitbox.isEnabled()) {
rows.add(new SettingRow("Color", SettingsControls.makeSlider(
FLAT, 80, 0.0, 360.0, Module_Hitbox.getHue(),
value -> (int) (double) value + "\u00B0",
value -> Module_Hitbox.setHue((int) (double) value)
)));
}
return rows;
}
public static List<SettingRow> effects() {
var positionStates = List.of(
new SettingsControls.ButtonState("L-TOP", () -> Module_Effects.setPosition(1, 1)),
new SettingsControls.ButtonState("R-TOP", () -> Module_Effects.setPosition(99, 1)),
new SettingsControls.ButtonState("R-BOTTOM", () -> Module_Effects.setPosition(99, 99)),
new SettingsControls.ButtonState("L-BOTTOM", () -> Module_Effects.setPosition(1, 99))
);
return List.of(
new SettingRow("Position", SettingsControls.makeCyclingButton(GLASS_PANEL, positionStates, positionIndex(Config.get().effectsX, Config.get().effectsY))),
new SettingRow("Show Background", SettingsControls.makeCheckbox(FLAT, GLASS_PANEL, Module_Effects.isBackgroundEnabled(), Module_Effects::setBackgroundEnabled)),
new SettingRow("Show Timer", SettingsControls.makeCheckbox(FLAT, GLASS_PANEL, Module_Effects.isTimerEnabled(), Module_Effects::setTimerEnabled)),
new SettingRow("Show Name", SettingsControls.makeCheckbox(FLAT, GLASS_PANEL, Module_Effects.isNameEnabled(), Module_Effects::setNameEnabled))
);
}
}
@@ -11,6 +11,8 @@ import net.minecraft.network.chat.FontDescription;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import de.fullrisk.citrusDev.Config;
import java.util.List; import java.util.List;
import java.util.function.DoubleConsumer; import java.util.function.DoubleConsumer;
import java.util.function.DoubleFunction; import java.util.function.DoubleFunction;
@@ -21,9 +23,9 @@ public class SettingsControls {
private static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five"); private static final Identifier MC_FIVE = Identifier.fromNamespaceAndPath("citrus-dev", "minecraft_five");
private static final Surface GLASS_PANEL = Surface.blur(5f, 15f) private static int secColor() {
.and(Surface.flat(0x40FFFFFF)) return Config.get().darkMode ? 0xFFd0d4da : 0xFFb0b6c0;
.and(Surface.outline(0x64de4b)); }
public record ButtonState(String label, Runnable onSelect) {} public record ButtonState(String label, Runnable onSelect) {}
@@ -106,7 +108,7 @@ public class SettingsControls {
return true; return true;
}); });
button.mouseEnter().subscribe(() -> label.color(Color.ofRgb(0xFFFFFF))); button.mouseEnter().subscribe(() -> label.color(Color.ofRgb(0xFFFFFF)));
button.mouseLeave().subscribe(() -> label.color(Color.ofRgb(0xCCCCCC))); button.mouseLeave().subscribe(() -> label.color(Color.ofRgb(0xDDDDDD)));
return button; return button;
} }
@@ -198,7 +200,7 @@ public class SettingsControls {
var label = UIComponents.label(Component.literal(text.toUpperCase()) var label = UIComponents.label(Component.literal(text.toUpperCase())
.withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE)))) .withStyle(style -> style.withFont(new FontDescription.Resource(MC_FIVE))))
.color(Color.ofRgb(0xFF5c5c66)); .color(Color.ofRgb(secColor()));
row.child(label); row.child(label);
FlowLayout longLine = UIContainers.horizontalFlow(Sizing.fill(100), Sizing.fixed(1)); FlowLayout longLine = UIContainers.horizontalFlow(Sizing.fill(100), Sizing.fixed(1));
@@ -71,9 +71,9 @@ public class CitrusDevClient implements ClientModInitializer {
while (openScreenKey.consumeClick()) { while (openScreenKey.consumeClick()) {
if (client.player != null && client.player.isShiftKeyDown()) { if (client.player != null && client.player.isShiftKeyDown()) {
String lastModule = Config.get().lastModule; String lastModule = Config.get().lastModule;
client.setScreen(new MyScreen(lastModule)); client.setScreen(new MainUI(lastModule));
} else { } else {
client.setScreen(new MyScreen(null)); client.setScreen(new MainUI(null));
} }
} }