Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
247 lines
12 KiB
GDScript
247 lines
12 KiB
GDScript
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)
|