class_name LuneUiTheme extends RefCounted ## Champions of Luneraria UI theme. It extends the Saumwacht menu theme (fonts, buttons, ## focus rings, ForgedStyle plates) with the in-match look: dark blue-anthracite plates, ## bronze and gold only on what the player can act on, quiet team accents. HUD components ## pick their look through theme_type_variation; shared styles are built once and cached, ## never copied per control. const SLATE_TOP := Color(.075, .1, .135, .9) const SLATE_BOTTOM := Color(.05, .068, .094, .93) const EDGE := Color("2b3848") const EDGE_LIGHT := Color("455870") const TEXT := Color("ebe5d8") const BRIGHT := Color("f6eedc") const MUTED := Color("9aa4b0") const FAINT := Color("6a7480") const GOLD := Color("f2c96b") const BRONZE := SaumwachtTheme.BRONZE const BRONZE_LIGHT := SaumwachtTheme.BRONZE_LIGHT const BRONZE_DEEP := SaumwachtTheme.BRONZE_DEEP ## Team accents: Blau (cool, crescent moon), Rot (warm, sun disc), neutral grotto (violet). const TEAM: Array[Color] = [Color("6cb4ff"), Color("ff7b5e"), Color("b78cff")] const TEAM_DARK: Array[Color] = [Color("1c3f6a"), Color("722a1f"), Color("45305f")] const TEAM_NAMES: Array[String] = ["Blau", "Rot", "Grotte"] const TEAM_EMBLEMS: Array[String] = ["Mondsichel", "Sonnenscheibe", "Grotte"] ## Threat levels RUHIG, UMKÄMPFT, UNTER DRUCK, KRITISCH. const THREAT: Array[Color] = [Color("7cc9a6"), Color("e5c35e"), Color("ff9448"), Color("ff584a")] const CHAMPION := Color("e2a15e") const HERO := Color("f2c96b") const GOOD := Color("8fdcb4") const WARN := Color("f0c060") const DANGER := Color("ff6a55") const INFO := Color("a9c6e8") ## Lower bound for any HUD text: 14 logical px stay above 10 px at 1280×720. const MIN_FONT: int = 14 static var _theme: Theme static var _styles: Dictionary = {} static func shared() -> Theme: if _theme == null: _theme = build() return _theme static func font_ui(weight: float = 560.0, spacing: int = 0) -> FontVariation: return SaumwachtTheme.font(SaumwachtTheme.CANTARELL, weight, spacing) static func font_display(weight: float = 700.0, spacing: int = 1) -> FontVariation: return SaumwachtTheme.font(SaumwachtTheme.CINZEL, weight, spacing) static func plate(top: Color, bottom: Color, edge: Color, edge_width: float = 1.0, cut: float = 6.0, horizontal: float = 12.0, vertical: float = 10.0) -> StyleBox: var style: StyleBox = SaumwachtTheme.plate(top, bottom, edge, edge_width, cut) (style as ForgedStyle).inner = Color(1, 1, 1, .035) (style as ForgedStyle).inner_inset = 3.0 return SaumwachtTheme.margins(style, horizontal, vertical) static func build() -> Theme: var theme: Theme = SaumwachtTheme.build() var ui := font_ui() var ui_bold := font_ui(720.0) var caps := font_ui(700.0, 2) var display := font_display(700.0, 1) _label(theme, "HudText", ui, 15, TEXT) _label(theme, "HudMuted", ui, 14, MUTED) _label(theme, "HudCaps", caps, 14, BRONZE_LIGHT) _label(theme, "HudBadge", font_ui(720.0, 1), 14, TEXT) _label(theme, "HudLane", font_display(700.0, 1), 16, Color("f4e6c4")) _label(theme, "HudValue", ui_bold, 17, BRIGHT) _label(theme, "HudGold", ui_bold, 24, GOLD) _label(theme, "HudName", display, 18, Color("f4e6c4")) _label(theme, "HudTitle", display, 24, Color("f4e6c4")) _label(theme, "HudWave", font_display(700.0, 3), 30, Color("f4e6c4")) for type: String in ["HudWave", "HudTitle", "HudGold"]: theme.set_color("font_outline_color", type, Color(0, 0, 0, .55)) theme.set_constant("outline_size", type, 4) # Panels: one quiet plate for all regions, a heavier one for modals and the tooltip. theme.set_stylebox("panel", "HudPanel", plate(SLATE_TOP, SLATE_BOTTOM, EDGE, 1.0, 6.0, 12, 10)) theme.set_type_variation("HudPanel", "PanelContainer") theme.set_stylebox("panel", "HudPanelTight", plate(SLATE_TOP, SLATE_BOTTOM, EDGE, 1.0, 5.0, 8, 6)) theme.set_type_variation("HudPanelTight", "PanelContainer") var inset := StyleBoxFlat.new() inset.bg_color = Color(0, 0, 0, .28) inset.set_corner_radius_all(4) SaumwachtTheme.margins(inset, 8, 6) theme.set_stylebox("panel", "HudInset", inset) theme.set_type_variation("HudInset", "PanelContainer") var modal := plate(Color(.07, .09, .125, .98), Color(.045, .06, .085, .98), Color("5c4a33"), 1.5, 10.0, 22, 18) (modal as ForgedStyle).shadow = Color(0, 0, 0, .55) (modal as ForgedStyle).shadow_size = 16.0 theme.set_stylebox("panel", "HudModal", modal) theme.set_type_variation("HudModal", "PanelContainer") var banner := plate(Color(.16, .075, .05, .94), Color(.11, .05, .035, .95), Color("a0523a"), 1.5, 6.0, 16, 8) theme.set_stylebox("panel", "HudBannerDanger", banner) theme.set_type_variation("HudBannerDanger", "PanelContainer") var banner_own := plate(Color(.07, .1, .16, .94), Color(.05, .07, .11, .95), Color("5f86b8"), 1.5, 6.0, 16, 8) theme.set_stylebox("panel", "HudBannerOwn", banner_own) theme.set_type_variation("HudBannerOwn", "PanelContainer") var tooltip := plate(Color(.06, .08, .11, .98), Color(.045, .06, .085, .98), Color("6b5a44"), 1.0, 6.0, 12, 9) (tooltip as ForgedStyle).shadow = Color(0, 0, 0, .5) (tooltip as ForgedStyle).shadow_size = 8.0 theme.set_stylebox("panel", "HudTooltip", tooltip) theme.set_type_variation("HudTooltip", "PanelContainer") # Buttons. Secondary HUD buttons are compact; the gold PrimaryButton stays for Bereit. var focus: StyleBox = SaumwachtTheme.focus_ring(6.0) _button(theme, "HudButton", plate(Color("1b232d"), Color("131920"), Color("4a5a6c"), 1.0, 5.0, 12, 7), plate(Color("26303c"), Color("1a212a"), BRONZE_LIGHT, 1.0, 5.0, 12, 7), plate(Color("10151b"), Color("171d25"), BRONZE, 1.5, 5.0, 12, 7), plate(Color("141a20"), Color("10141a"), Color("2c333c"), 1.0, 5.0, 12, 7), focus) theme.set_font_size("font_size", "HudButton", 15) theme.set_color("font_disabled_color", "HudButton", FAINT) _button(theme, "HudIconButton", plate(Color("1b232d"), Color("131920"), Color("3c4a5a"), 1.0, 5.0, 9, 6), plate(Color("26303c"), Color("1a212a"), BRONZE_LIGHT, 1.0, 5.0, 9, 6), plate(Color("10151b"), Color("171d25"), BRONZE, 1.5, 5.0, 9, 6), plate(Color("141a20"), Color("10141a"), Color("2c333c"), 1.0, 5.0, 9, 6), focus) theme.set_font_size("font_size", "HudIconButton", 15) var primary_small: StyleBox = SaumwachtTheme.margins(SaumwachtTheme.plate(Color("e8c47e"), Color("b98a4a"), Color("fff0cc"), 1.5, 7.0), 16, 8) var primary_small_hover: StyleBox = SaumwachtTheme.margins(SaumwachtTheme.plate(Color("f6d898"), Color("cc9c56"), Color.WHITE, 1.5, 7.0), 16, 8) var primary_small_pressed: StyleBox = SaumwachtTheme.margins(SaumwachtTheme.plate(Color("b98a4a"), Color("d5aa66"), Color("fff0cc"), 2.0, 7.0), 16, 8) var primary_small_disabled: StyleBox = SaumwachtTheme.margins(SaumwachtTheme.plate(Color("4a4234"), Color("3a3328"), Color("6a5c46"), 1.0, 7.0), 16, 8) _button(theme, "HudPrimary", primary_small, primary_small_hover, primary_small_pressed, primary_small_disabled, focus) theme.set_font("font", "HudPrimary", font_ui(760.0, 1)) theme.set_font_size("font_size", "HudPrimary", 16) for state: String in ["font_color", "font_hover_color", "font_pressed_color", "font_focus_color", "font_hover_pressed_color"]: theme.set_color(state, "HudPrimary", SaumwachtTheme.INK) theme.set_color("font_disabled_color", "HudPrimary", Color("9c8f78")) # Cards (units, heroes, items) and the champion variant with its own copper language. var card_focus: StyleBox = SaumwachtTheme.focus_ring(7.0) _button(theme, "HudCard", plate(Color("19222c"), Color("10161d"), Color("34424f"), 1.0, 7.0, 6, 6), plate(Color("212c38"), Color("141b23"), BRONZE, 1.5, 7.0, 6, 6), plate(Color("2a2820"), Color("17171a"), BRONZE_LIGHT, 2.0, 7.0, 6, 6), plate(Color("12171d"), Color("0d1116"), Color("252c34"), 1.0, 7.0, 6, 6), card_focus) theme.set_stylebox("hover_pressed", "HudCard", theme.get_stylebox("pressed", "HudCard")) _button(theme, "HudChampionCard", plate(Color("261c14"), Color("15100c"), Color("6e4a2c"), 1.0, 7.0, 6, 6), plate(Color("302216"), Color("1b140e"), CHAMPION, 1.5, 7.0, 6, 6), plate(Color("3a2818"), Color("1e150e"), Color("ffc88c"), 2.0, 7.0, 6, 6), plate(Color("16110d"), Color("0f0c0a"), Color("3a2c20"), 1.0, 7.0, 6, 6), card_focus) _button(theme, "HudChip", plate(Color("1a232d"), Color("11171e"), Color("33404d"), 1.0, 4.0, 2, 2), plate(Color("222d39"), Color("151c24"), BRONZE, 1.0, 4.0, 2, 2), plate(Color("2c2a22"), Color("17171a"), BRONZE_LIGHT, 2.0, 4.0, 2, 2), plate(Color("12171d"), Color("0d1116"), Color("252c34"), 1.0, 4.0, 2, 2), SaumwachtTheme.focus_ring(4.0)) theme.set_stylebox("hover_pressed", "HudChip", theme.get_stylebox("pressed", "HudChip")) _button(theme, "HudRow", plate(Color(.09, .12, .16, .55), Color(.06, .08, .11, .55), Color("2a3645"), 1.0, 5.0, 10, 7), plate(Color(.13, .17, .22, .75), Color(.08, .11, .15, .75), BRONZE, 1.0, 5.0, 10, 7), plate(Color(.16, .15, .12, .8), Color(.09, .09, .1, .8), BRONZE_LIGHT, 1.5, 5.0, 10, 7), plate(Color(.09, .12, .16, .55), Color(.06, .08, .11, .55), Color("2a3645"), 1.0, 5.0, 10, 7), focus) # Toggles for lane assignment and target lanes: selected = bronze fill, never colour alone. var toggle_on := plate(Color("3a3222"), Color("262117"), BRONZE_LIGHT, 1.5, 5.0, 10, 6) (toggle_on as ForgedStyle).inner = Color(BRONZE_LIGHT, .35) _button(theme, "HudToggle", plate(Color("171e27"), Color("11161d"), Color("3a4756"), 1.0, 5.0, 10, 6), plate(Color("212a35"), Color("171d25"), BRONZE, 1.0, 5.0, 10, 6), toggle_on, plate(Color("11151a"), Color("0e1115"), Color("242a31"), 1.0, 5.0, 10, 6), focus) theme.set_stylebox("hover_pressed", "HudToggle", toggle_on) theme.set_font("font", "HudToggle", font_ui(700.0, 1)) theme.set_font_size("font_size", "HudToggle", 14) theme.set_color("font_color", "HudToggle", Color("cfd5dc")) theme.set_color("font_pressed_color", "HudToggle", Color("fff4dc")) theme.set_color("font_hover_pressed_color", "HudToggle", Color("fff4dc")) theme.set_color("font_disabled_color", "HudToggle", Color("59616b")) # Tabs: flat with a bronze underline for the open tab. var tab := StyleBoxFlat.new() tab.bg_color = Color(0, 0, 0, 0) tab.border_color = Color(0, 0, 0, 0) tab.border_width_bottom = 2 SaumwachtTheme.margins(tab, 12, 6) var tab_hover := tab.duplicate() as StyleBoxFlat tab_hover.bg_color = Color(1, 1, 1, .04) tab_hover.border_color = Color(BRONZE, .5) var tab_on := tab.duplicate() as StyleBoxFlat tab_on.bg_color = Color(BRONZE_LIGHT, .08) tab_on.border_color = BRONZE_LIGHT _button(theme, "HudTab", tab, tab_hover, tab_on, tab, SaumwachtTheme.focus_ring(3.0)) theme.set_stylebox("hover_pressed", "HudTab", tab_on) theme.set_font("font", "HudTab", caps) theme.set_font_size("font_size", "HudTab", 14) theme.set_color("font_color", "HudTab", MUTED) theme.set_color("font_hover_color", "HudTab", TEXT) theme.set_color("font_pressed_color", "HudTab", BRONZE_LIGHT) theme.set_color("font_hover_pressed_color", "HudTab", BRONZE_LIGHT) theme.set_color("font_disabled_color", "HudTab", FAINT) return theme static func _label(theme: Theme, type: String, face: Font, size: int, color: Color) -> void: theme.set_type_variation(type, "Label") theme.set_font("font", type, face) theme.set_font_size("font_size", type, maxi(MIN_FONT, size)) theme.set_color("font_color", type, color) static func _button(theme: Theme, type: String, normal: StyleBox, hover: StyleBox, pressed: StyleBox, disabled: StyleBox, focus: StyleBox) -> void: theme.set_type_variation(type, "Button") theme.set_stylebox("normal", type, normal) theme.set_stylebox("hover", type, hover) theme.set_stylebox("pressed", type, pressed) theme.set_stylebox("disabled", type, disabled) theme.set_stylebox("focus", type, focus) theme.set_color("font_color", type, TEXT) theme.set_color("font_hover_color", type, Color.WHITE) theme.set_color("font_focus_color", type, Color.WHITE) ## Rounded pill for StatusBadge; one cached style per colour and fill mode. static func badge_style(color: Color, filled: bool) -> StyleBox: var key := "badge:%s:%s" % [color.to_html(), filled] if not _styles.has(key): var style := StyleBoxFlat.new() style.bg_color = Color(color, .92) if filled else Color(color.darkened(.75), .85) style.border_color = color if not filled else color.lightened(.2) style.set_border_width_all(1) style.set_corner_radius_all(4) style.content_margin_left = 7 style.content_margin_right = 7 style.content_margin_top = 1 style.content_margin_bottom = 1 style.anti_aliasing = true _styles[key] = style return _styles[key] ## Small recessed square for item slots in cards (shared, 4 px padding). static func slot_style(active: bool = false) -> StyleBox: var key := "slot:%s" % active if not _styles.has(key): var style := StyleBoxFlat.new() style.bg_color = Color(0, 0, 0, .35) style.border_color = BRONZE if active else Color("33404d") style.set_border_width_all(1) style.set_corner_radius_all(3) style.set_content_margin_all(4) _styles[key] = style return _styles[key] ## Label factory used by every component so sizes and variations stay consistent. static func label(text_value: String, variation: String = "HudText", parent: Node = null, size: int = 0) -> Label: var result := Label.new() result.text = text_value result.theme_type_variation = variation result.mouse_filter = Control.MOUSE_FILTER_IGNORE if size > 0: result.add_theme_font_size_override("font_size", maxi(MIN_FONT, size)) if parent != null: parent.add_child(result) return result static func button(text_value: String, variation: String = "HudButton", parent: Node = null) -> Button: var result := Button.new() result.text = text_value result.theme_type_variation = variation result.focus_mode = Control.FOCUS_ALL result.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if parent != null: parent.add_child(result) return result static func team_color(team: int) -> Color: return TEAM[clampi(team, 0, 2)] static func team_name(team: int) -> String: return TEAM_NAMES[clampi(team, 0, 2)] ## Whole numbers with German thousands separators (12.000). static func number(value: float) -> String: var whole: int = int(round(value)) var negative: bool = whole < 0 var digits: String = str(absi(whole)) var result: String = "" while digits.length() > 3: result = "." + digits.right(3) + result digits = digits.left(digits.length() - 3) return ("-" if negative else "") + digits + result static func decimal(value: float, places: int = 1) -> String: return (("%." + str(places) + "f") % value).replace(".", ",")