package org.saturnclient.config; import java.util.List; import org.saturnclient.config.property.ColorProperty; import org.saturnclient.config.property.IntProperty; import org.saturnclient.config.property.Property; import org.saturnclient.config.property.SelectProperty; public class Theme { public static ConfigManager config; // Main UI background (a near-neutral dark, so the warm world showing through // doesn't muddy it), translucent over the world public static final ColorProperty BACKGROUND = Property.color(0xD1131417); // Default text color public static final ColorProperty FOREGROUND = Property.color(0xFFECEFF3); // Widget default background (normal state), translucent over the world public static final ColorProperty PRIMARY = Property.color(0xB81E2025); // Widget text color public static final ColorProperty PRIMARY_FG = Property.color(0xFFD5DAE1); // Saturn brand color, a pale frost (hovered / enabled / selected) public static final ColorProperty ACCENT = Property.color(0xFFD3DCE6); // Text/icons shown on accent background public static final ColorProperty ACCENT_FG = Property.color(0xFF15181C); // Misc colors: the one warm touch, Saturn's butterscotch public static final ColorProperty SCROLL = Property.color(0xFFD9B98A); // UI metrics public static final IntProperty BG_RADIUS = Property.integer(12).range(0, 24); public static final IntProperty WIDGET_RADIUS = Property.integer(10).range(0, 20); // Font public static final SelectProperty FONT = Property.font(2); /** * A full set of theme colors that can be applied in one go. */ public record Preset(String name, int background, int foreground, int primary, int primaryFg, int accent, int accentFg, int scroll) { public void apply() { BACKGROUND.value = background; FOREGROUND.value = foreground; PRIMARY.value = primary; PRIMARY_FG.value = primaryFg; ACCENT.value = accent; ACCENT_FG.value = accentFg; SCROLL.value = scroll; } public boolean isActive() { return BACKGROUND.value == background && FOREGROUND.value == foreground && PRIMARY.value == primary && PRIMARY_FG.value == primaryFg && ACCENT.value == accent && ACCENT_FG.value == accentFg && SCROLL.value == scroll; } } public static final List PRESETS = List.of( new Preset("Saturn", 0xD1131417, 0xFFECEFF3, 0xB81E2025, 0xFFD5DAE1, 0xFFD3DCE6, 0xFF15181C, 0xFFD9B98A), new Preset("Saturn Gold", 0xD112100D, 0xFFF2E6D2, 0xB81C1915, 0xFFEAD9B8, 0xFFE2BF7D, 0xFF1A140A, 0xFFE2BF7D), new Preset("Midnight", 0xD10E1117, 0xFFE4EAF5, 0xB8171C26, 0xFFC9D4E8, 0xFF8FB3FF, 0xFF0A1224, 0xFF8FB3FF), new Preset("Amethyst", 0xD1120F17, 0xFFEDE4F7, 0xB81C1724, 0xFFD8C9EB, 0xFFC79BFF, 0xFF1A0F2A, 0xFFC79BFF), new Preset("Emerald", 0xD10D1210, 0xFFE2F2EA, 0xB816201B, 0xFFC4E0D2, 0xFF7FD1A8, 0xFF0A1F15, 0xFF7FD1A8)); public static void init(ConfigManager parent) { config = new ConfigManager(parent, "UI Style"); config.property("Background", BACKGROUND, "Panels and screens"); config.property("Foreground", FOREGROUND, "Text"); config.property("Primary", PRIMARY, "Buttons and cards"); config.property("Primary Foreground", PRIMARY_FG, "Text on buttons and cards"); config.property("Accent", ACCENT, "Selected and enabled items"); config.property("Accent Foreground", ACCENT_FG, "Text on the accent color"); config.property("Scrollbar Color", SCROLL, "The scrollbar's handle"); config.property("Background Radius", BG_RADIUS, "Corner rounding of panels"); config.property("Widget Radius", WIDGET_RADIUS, "Corner rounding of buttons and cards"); config.property("Font", FONT, "Font of every Saturn screen"); } /** * primary by default, accent if true */ public static int getBg(boolean hover) { return hover ? ACCENT.value : PRIMARY.value; } /** * (Foreground) primary by default, accent if true */ public static int getFg(boolean hover) { return hover ? ACCENT_FG.value : PRIMARY_FG.value; } /** * Background for panels drawn over the world, as translucent as the * background color is */ public static int getPanelBg() { return BACKGROUND.value; } /** * Background for popups, which sit over other panels: the background color, * kept nearly solid so what's under it doesn't show through */ public static int getPopupBg() { return withAlpha(0.97f, BACKGROUND.value | 0xFF000000); } /** * Faint outline for panels, cards and inputs */ public static int getBorder() { return withAlpha(0.08f, FOREGROUND.value); } /** * Secondary text: descriptions, counts and placeholders */ public static int getMutedFg() { return withAlpha(0.65f, FOREGROUND.value); } /** * Font for secondary text. Inter bold is too heavy there, so it becomes * regular Inter; other fonts are kept. */ public static int getBodyFont() { return FONT.value == 2 ? 1 : FONT.value; } /** * Multiplies existing alpha with 0–255 alpha. */ public static int withAlpha(int alpha, int color) { alpha = Math.max(0, Math.min(255, alpha)); int originalAlpha = (color >>> 24) & 0xFF; // Merge (multiply) alphas int merged = (originalAlpha * alpha) / 255; return (color & 0x00FFFFFF) | (merged << 24); } /** * Multiplies existing alpha with 0.0–1.0 alpha. */ public static int withAlpha(float alpha, int color) { alpha = Math.max(0f, Math.min(1f, alpha)); int originalAlpha = (color >>> 24) & 0xFF; int merged = (int) (originalAlpha * alpha); return (color & 0x00FFFFFF) | (merged << 24); } }