class_name SaumwachtTheme extends RefCounted ## Shared menu theme: basalt plates, bronze edges, Cinzel for titles, Alegreya for prose, ## Cantarell for controls. Components pick a look through theme_type_variation. const NIGHT := Color("0b0f14") const BASALT := Color("151a20") const BASALT_DEEP := Color("0f1317") const PARCHMENT := Color("efe6d2") const MUTED := Color("a9a293") const FAINT := Color("7d7466") const BRONZE := Color("b7874a") const BRONZE_LIGHT := Color("e4bd7c") const BRONZE_DEEP := Color("6b4f2e") const FOCUS := Color("f6e2b0") const EMBER := Color("ff9a52") const SPRING := Color("7fe0c4") const INK := Color("1a120a") const CINZEL := preload("res://assets/fonts/Cinzel-VF.ttf") const ALEGREYA := preload("res://assets/fonts/Alegreya-VF.ttf") const ALEGREYA_ITALIC := preload("res://assets/fonts/Alegreya-Italic-VF.ttf") const CANTARELL := preload("res://assets/fonts/Cantarell-VF.otf") const ForgedStyle = preload("res://scripts/ui/theme/forged_style.gd") static var _shared: Theme static func shared() -> Theme: if _shared == null: _shared = build() return _shared static func font(base: Font, weight: float, spacing: int = 0) -> FontVariation: var variation := FontVariation.new() variation.base_font = base variation.variation_opentype = {"wght": weight} variation.spacing_glyph = spacing return variation static func plate(top: Color, bottom: Color, edge: Color, edge_width: float = 1.5, cut: float = 7.0) -> StyleBox: var style := ForgedStyle.new() style.fill_top = top style.fill_bottom = bottom style.edge = edge style.edge_width = edge_width style.chamfer = cut style.inner = Color(1, 1, 1, .045) style.set_content_margin_all(12) return style static func focus_ring(cut: float = 7.0) -> StyleBox: # Keyboard focus: a bright ring outside the control plus a soft halo, distinct from hover. var style := ForgedStyle.new() style.draw_fill = false style.edge = FOCUS style.edge_width = 2.5 style.inner = Color(FOCUS, .55) style.inner_inset = 4.5 style.chamfer = cut + 4.0 style.grow = 4.0 style.shadow = Color(FOCUS, .22) style.shadow_size = 5.0 return style static func margins(style: StyleBox, horizontal: float, vertical: float) -> StyleBox: style.content_margin_left = horizontal style.content_margin_right = horizontal style.content_margin_top = vertical style.content_margin_bottom = vertical return style static func build() -> Theme: var theme := Theme.new() var ui := font(CANTARELL, 560.0) theme.default_font = ui theme.default_font_size = 17 var display := font(CINZEL, 700.0, 2) var display_medium := font(CINZEL, 600.0, 1) var caps := font(CANTARELL, 700.0, 2) var prose := font(ALEGREYA, 480.0) var prose_italic := font(ALEGREYA_ITALIC, 460.0) for type: String in ["Label", "Button", "CheckButton", "OptionButton"]: theme.set_color("font_color", type, PARCHMENT) theme.set_color("font_shadow_color", "Label", Color(0, 0, 0, .55)) theme.set_constant("shadow_offset_y", "Label", 1) theme.set_constant("shadow_outline_size", "Label", 2) _label(theme, "TitleLabel", display, 78, Color("f4e6c4")) theme.set_color("font_outline_color", "TitleLabel", Color(0, 0, 0, .5)) theme.set_constant("outline_size", "TitleLabel", 6) _label(theme, "SubtitleLabel", font(CINZEL, 600.0, 7), 19, BRONZE_LIGHT) _label(theme, "HeadingLabel", display_medium, 34, Color("f4e6c4")) _label(theme, "SectionLabel", font(CINZEL, 700.0, 3), 16, BRONZE_LIGHT) _label(theme, "EyebrowLabel", caps, 14, BRONZE_LIGHT) _label(theme, "BodyLabel", prose, 20, PARCHMENT) _label(theme, "LoreLabel", prose_italic, 21, Color("ddd3bd")) _label(theme, "MutedLabel", ui, 16, MUTED) _label(theme, "CardTitleLabel", display, 29, Color("f4e6c4")) _label(theme, "CardFactLabel", ui, 17, Color("d8cfbc")) _label(theme, "BadgeLabel", caps, 13, INK) # Secondary buttons are the default look; primary and quiet buttons are variations. var secondary := margins(plate(Color("1d232b"), Color("141920"), Color("6b5a44")), 20, 11) var secondary_hover := margins(plate(Color("27303a"), Color("1a2028"), BRONZE_LIGHT), 20, 11) var secondary_pressed := margins(plate(Color("10141a"), Color("171c23"), BRONZE, 2.0), 20, 11) var secondary_disabled := margins(plate(Color("15191e"), Color("111418"), Color("3a352e")), 20, 11) _button(theme, "Button", secondary, secondary_hover, secondary_pressed, secondary_disabled, focus_ring()) theme.set_color("font_hover_color", "Button", Color.WHITE) theme.set_color("font_pressed_color", "Button", BRONZE_LIGHT) theme.set_color("font_disabled_color", "Button", FAINT) theme.set_color("font_focus_color", "Button", Color.WHITE) var primary := margins(plate(Color("e8c47e"), Color("b98a4a"), Color("fff0cc"), 1.5, 9.0), 30, 15) (primary as ForgedStyle).inner = Color(INK, .35) (primary as ForgedStyle).highlight = Color(1, 1, 1, .45) (primary as ForgedStyle).shadow = Color(0, 0, 0, .45) (primary as ForgedStyle).shadow_size = 8.0 var primary_hover := margins(plate(Color("f6d898"), Color("cc9c56"), Color.WHITE, 1.5, 9.0), 30, 15) (primary_hover as ForgedStyle).inner = Color(INK, .35) (primary_hover as ForgedStyle).highlight = Color(1, 1, 1, .6) (primary_hover as ForgedStyle).shadow = Color(.95, .7, .35, .35) (primary_hover as ForgedStyle).shadow_size = 12.0 var primary_pressed := margins(plate(Color("b98a4a"), Color("d5aa66"), Color("fff0cc"), 2.0, 9.0), 30, 15) (primary_pressed as ForgedStyle).inner = Color(INK, .45) var primary_disabled := margins(plate(Color("5a4c38"), Color("463a2a"), Color("7d6a4c"), 1.5, 9.0), 30, 15) _button(theme, "PrimaryButton", primary, primary_hover, primary_pressed, primary_disabled, focus_ring(9.0)) theme.set_type_variation("PrimaryButton", "Button") theme.set_font("font", "PrimaryButton", display) theme.set_font_size("font_size", "PrimaryButton", 22) for state: String in ["font_color", "font_hover_color", "font_pressed_color", "font_focus_color", "font_hover_pressed_color"]: theme.set_color(state, "PrimaryButton", INK) theme.set_color("font_disabled_color", "PrimaryButton", Color("2a2218")) var quiet := margins(StyleBoxEmpty.new(), 10, 7) var quiet_hover := margins(plate(Color(1, 1, 1, .05), Color(1, 1, 1, .02), Color(0, 0, 0, 0), 0.0, 5.0), 10, 7) _button(theme, "QuietButton", quiet, quiet_hover, quiet_hover, quiet, focus_ring(5.0)) theme.set_type_variation("QuietButton", "Button") theme.set_color("font_color", "QuietButton", MUTED) theme.set_color("font_hover_color", "QuietButton", PARCHMENT) theme.set_font_size("font_size", "QuietButton", 16) # Segmented choices: the pressed state is the selected option (toggle mode). var choice := margins(plate(Color("171c23"), Color("12161b"), Color("463d31")), 16, 10) var choice_hover := margins(plate(Color("212831"), Color("171c23"), BRONZE), 16, 10) var choice_on := margins(plate(Color("2e2a22"), Color("1d1b17"), BRONZE_LIGHT, 2.0), 16, 10) (choice_on as ForgedStyle).inner = Color(BRONZE_LIGHT, .35) _button(theme, "ChoiceButton", choice, choice_hover, choice_on, choice, focus_ring()) theme.set_type_variation("ChoiceButton", "Button") theme.set_stylebox("hover_pressed", "ChoiceButton", choice_on) theme.set_color("font_color", "ChoiceButton", Color("cfc6b2")) theme.set_color("font_pressed_color", "ChoiceButton", Color("fff4dc")) theme.set_color("font_hover_pressed_color", "ChoiceButton", Color("fff4dc")) theme.set_constant("h_separation", "ChoiceButton", 12) theme.set_constant("icon_max_width", "ChoiceButton", 18) # Faction cards: a heavier plate; selected adds a double bronze frame and rivets. var card := margins(plate(Color("1a1f26"), Color("111419"), Color("4a4034"), 1.5, 12.0), 18, 16) var card_hover := margins(plate(Color("222932"), Color("151a20"), BRONZE, 1.5, 12.0), 18, 16) var card_on := margins(plate(Color("2a2720"), Color("16171a"), BRONZE_LIGHT, 2.5, 12.0), 18, 16) (card_on as ForgedStyle).inner = Color(BRONZE_LIGHT, .55) (card_on as ForgedStyle).inner_inset = 5.0 (card_on as ForgedStyle).ornaments = true (card_on as ForgedStyle).ornament_color = BRONZE_LIGHT (card_on as ForgedStyle).shadow = Color(0, 0, 0, .5) (card_on as ForgedStyle).shadow_size = 10.0 _button(theme, "FactionCard", card, card_hover, card_on, card, focus_ring(12.0)) theme.set_type_variation("FactionCard", "Button") theme.set_stylebox("hover_pressed", "FactionCard", card_on) var panel := margins(plate(Color(.078, .094, .114, .95), Color(.055, .066, .08, .96), Color("5c4a33"), 1.5, 12.0), 30, 26) (panel as ForgedStyle).ornaments = true (panel as ForgedStyle).ornament_color = Color("8a6a3c") (panel as ForgedStyle).shadow = Color(0, 0, 0, .45) (panel as ForgedStyle).shadow_size = 18.0 theme.set_stylebox("panel", "MenuPanel", panel) theme.set_type_variation("MenuPanel", "PanelContainer") var caption := margins(plate(Color(.06, .075, .09, .82), Color(.05, .06, .075, .86), Color("4a4034"), 1.0, 6.0), 16, 9) theme.set_stylebox("panel", "CaptionPanel", caption) theme.set_type_variation("CaptionPanel", "PanelContainer") var badge := margins(plate(BRONZE_LIGHT, BRONZE, Color("fff0cc"), 1.0, 4.0), 9, 3) theme.set_stylebox("panel", "BadgePanel", badge) theme.set_type_variation("BadgePanel", "PanelContainer") theme.set_stylebox("panel", "PanelContainer", panel) var on_icon: Texture2D = load("res://assets/ui/controls/toggle_on.svg") var off_icon: Texture2D = load("res://assets/ui/controls/toggle_off.svg") for type: String in ["CheckButton"]: theme.set_icon("checked", type, on_icon) theme.set_icon("unchecked", type, off_icon) theme.set_icon("checked_disabled", type, on_icon) theme.set_icon("unchecked_disabled", type, off_icon) theme.set_stylebox("normal", type, margins(StyleBoxEmpty.new(), 6, 6)) theme.set_stylebox("hover", type, margins(plate(Color(1, 1, 1, .04), Color(1, 1, 1, .02), Color(0, 0, 0, 0), 0.0, 5.0), 6, 6)) theme.set_stylebox("pressed", type, margins(StyleBoxEmpty.new(), 6, 6)) theme.set_stylebox("hover_pressed", type, margins(plate(Color(1, 1, 1, .04), Color(1, 1, 1, .02), Color(0, 0, 0, 0), 0.0, 5.0), 6, 6)) theme.set_stylebox("focus", type, focus_ring(5.0)) theme.set_color("font_hover_color", type, Color.WHITE) theme.set_color("font_pressed_color", type, PARCHMENT) theme.set_color("font_hover_pressed_color", type, Color.WHITE) theme.set_color("font_focus_color", type, Color.WHITE) theme.set_constant("h_separation", type, 14) var track := StyleBoxFlat.new() track.bg_color = Color("0c0f13") track.border_color = Color("5c4a33") track.set_border_width_all(1) track.content_margin_top = 3 track.content_margin_bottom = 3 theme.set_stylebox("slider", "HSlider", track) var fill := StyleBoxFlat.new() fill.bg_color = BRONZE fill.content_margin_top = 3 fill.content_margin_bottom = 3 theme.set_stylebox("grabber_area", "HSlider", fill) theme.set_stylebox("grabber_area_highlight", "HSlider", fill) theme.set_icon("grabber", "HSlider", load("res://assets/ui/controls/choice_on.svg")) theme.set_icon("grabber_highlight", "HSlider", load("res://assets/ui/controls/choice_on.svg")) theme.set_stylebox("focus", "HSlider", focus_ring(4.0)) var rule := StyleBoxLine.new() rule.color = Color("3a3226") rule.thickness = 1 theme.set_stylebox("separator", "HSeparator", rule) theme.set_constant("separation", "HSeparator", 14) var scroll := StyleBoxFlat.new() scroll.bg_color = Color(1, 1, 1, .03) var grab := StyleBoxFlat.new() grab.bg_color = Color("5c4a33") for key: String in ["grabber", "grabber_highlight", "grabber_pressed"]: theme.set_stylebox(key, "VScrollBar", grab) theme.set_stylebox("scroll", "VScrollBar", scroll) var tooltip := margins(plate(Color("1d232b"), Color("141920"), BRONZE), 12, 8) theme.set_stylebox("panel", "TooltipPanel", tooltip) theme.set_color("font_color", "TooltipLabel", PARCHMENT) 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, 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_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)