Theme (scenes/ui/medieval_theme.gd): - Procedural builder for an app-wide Theme: parchment buttons on dark-wood border, tan panels, ink text, gold focus ring. - Applied on the root Window in Main._ready, with a tree-walk that re-seeds every CanvasLayer's topmost Control (CanvasLayer interrupts the root-Window theme cascade in Godot 4). - Late-mounting popups + modals get themed via a child_entered_tree hook on each CanvasLayer. Build drawer thumbnails (scenes/ui/build_drawer_thumb.gd): - New BuildDrawerThumb Control that dispatches on tool_id and draws a recognisable silhouette of the entity each tool builds. 17 tools covered: chop/mine/dig_grave/no_roof (Designate), stone+wood walls, wood+stone floors, door, crate, bed, torch, 5 workbenches (Carpenter/Smelter/Millstone/Hearth/Cremation Pyre), stockpile, graveyard. - Replaces the flat ColorRect placeholder. _add_tool_btn signature changed from (label, color, callback) to (label, tool_id, callback).
198 lines
8.4 KiB
GDScript
198 lines
8.4 KiB
GDScript
class_name MedievalTheme extends RefCounted
|
|
## Builds a Theme resource implementing the "Medieval warm" palette:
|
|
## Panel: tan (198, 168, 128)
|
|
## Border: dark wood (90, 55, 30)
|
|
## Button: parchment (230, 210, 170), hover lighter, pressed inset shadow
|
|
## Text: ink black with off-white on dark panels
|
|
##
|
|
## Applied on Main scene root so it cascades to every Control descendant.
|
|
## Per-panel overrides are still possible — only nodes that don't explicitly
|
|
## override a style pick up the global Theme.
|
|
|
|
# ── palette ──────────────────────────────────────────────────────────────────
|
|
const C_PANEL := Color(0.776, 0.659, 0.502) # tan
|
|
const C_PANEL_DARK := Color(0.353, 0.216, 0.118) # dark wood
|
|
const C_BUTTON := Color(0.902, 0.824, 0.667) # parchment
|
|
const C_BUTTON_HOV := Color(0.961, 0.902, 0.769) # warm parchment
|
|
const C_BUTTON_PRESS:= Color(0.776, 0.694, 0.529) # pressed shadow
|
|
const C_BUTTON_DIS := Color(0.690, 0.620, 0.510) # disabled
|
|
const C_INK := Color(0.106, 0.078, 0.039)
|
|
const C_INK_DIM := Color(0.353, 0.275, 0.196)
|
|
const C_ACCENT := Color(0.580, 0.180, 0.110) # wax-seal red, for selected tabs
|
|
const C_ACCENT_GOLD := Color(0.831, 0.620, 0.149)
|
|
|
|
|
|
static func build() -> Theme:
|
|
var theme := Theme.new()
|
|
# Default font size — small UI, but legible on phone.
|
|
theme.default_font_size = 14
|
|
|
|
# ── Button (drives OptionButton + MenuButton too) ─────────────────────────
|
|
theme.set_stylebox("normal", "Button", _btn_box(C_BUTTON, false))
|
|
theme.set_stylebox("hover", "Button", _btn_box(C_BUTTON_HOV, false))
|
|
theme.set_stylebox("pressed", "Button", _btn_box(C_BUTTON_PRESS, true))
|
|
theme.set_stylebox("disabled", "Button", _btn_box(C_BUTTON_DIS, false))
|
|
theme.set_stylebox("focus", "Button", _focus_box())
|
|
theme.set_color("font_color", "Button", C_INK)
|
|
theme.set_color("font_hover_color", "Button", C_INK)
|
|
theme.set_color("font_pressed_color", "Button", C_INK)
|
|
theme.set_color("font_disabled_color", "Button", C_INK_DIM)
|
|
theme.set_constant("h_separation", "Button", 6)
|
|
|
|
# OptionButton inherits Button styling automatically via class fallback in
|
|
# Godot, but we set explicit copies in case overrides land.
|
|
theme.set_stylebox("normal", "OptionButton", _btn_box(C_BUTTON, false))
|
|
theme.set_stylebox("hover", "OptionButton", _btn_box(C_BUTTON_HOV, false))
|
|
theme.set_stylebox("pressed", "OptionButton", _btn_box(C_BUTTON_PRESS, true))
|
|
theme.set_stylebox("focus", "OptionButton", _focus_box())
|
|
theme.set_color("font_color", "OptionButton", C_INK)
|
|
|
|
# ── CheckBox ──────────────────────────────────────────────────────────────
|
|
theme.set_color("font_color", "CheckBox", C_INK)
|
|
theme.set_color("font_hover_color", "CheckBox", C_INK)
|
|
theme.set_color("font_pressed_color", "CheckBox", C_INK)
|
|
|
|
# ── PanelContainer ────────────────────────────────────────────────────────
|
|
theme.set_stylebox("panel", "PanelContainer", _panel_box())
|
|
|
|
# ── Panel (raw) — same look as PanelContainer ─────────────────────────────
|
|
theme.set_stylebox("panel", "Panel", _panel_box())
|
|
|
|
# ── PopupMenu (recipe picker) ─────────────────────────────────────────────
|
|
theme.set_stylebox("panel", "PopupMenu", _panel_box())
|
|
theme.set_stylebox("hover", "PopupMenu", _btn_box(C_BUTTON_HOV, false))
|
|
theme.set_color("font_color", "PopupMenu", C_INK)
|
|
theme.set_color("font_hover_color", "PopupMenu", C_INK)
|
|
|
|
# ── Label — defaults to ink-on-tan; per-label modulate still works ────────
|
|
theme.set_color("font_color", "Label", C_INK)
|
|
|
|
# ── SpinBox / LineEdit ────────────────────────────────────────────────────
|
|
theme.set_stylebox("normal", "LineEdit", _btn_box(C_BUTTON, true))
|
|
theme.set_stylebox("focus", "LineEdit", _focus_box())
|
|
theme.set_color("font_color", "LineEdit", C_INK)
|
|
theme.set_color("caret_color", "LineEdit", C_INK)
|
|
|
|
# ── Slider (audio sliders in SettingsMenu) ────────────────────────────────
|
|
theme.set_stylebox("slider", "HSlider", _slider_track())
|
|
theme.set_stylebox("grabber_area", "HSlider", _slider_fill())
|
|
theme.set_stylebox("grabber_area_highlight", "HSlider", _slider_fill())
|
|
|
|
# ── ScrollContainer scrollbar ─────────────────────────────────────────────
|
|
theme.set_stylebox("scroll", "VScrollBar", _scrollbar_track())
|
|
theme.set_stylebox("grabber", "VScrollBar", _btn_box(C_PANEL_DARK, false))
|
|
theme.set_stylebox("grabber_pressed","VScrollBar", _btn_box(C_PANEL_DARK, true))
|
|
|
|
# ── HSeparator / VSeparator (used between bill rows in WorkbenchPanel) ────
|
|
var sep := StyleBoxFlat.new()
|
|
sep.bg_color = C_PANEL_DARK
|
|
sep.content_margin_top = 1
|
|
sep.content_margin_bottom = 1
|
|
theme.set_stylebox("separator", "HSeparator", sep)
|
|
theme.set_stylebox("separator", "VSeparator", sep)
|
|
|
|
return theme
|
|
|
|
|
|
# ── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
static func _btn_box(fill: Color, pressed: bool) -> StyleBoxFlat:
|
|
var s := StyleBoxFlat.new()
|
|
s.bg_color = fill
|
|
s.border_color = C_PANEL_DARK
|
|
s.border_width_left = 1
|
|
s.border_width_right = 1
|
|
s.border_width_top = 1
|
|
s.border_width_bottom = 1
|
|
s.corner_radius_top_left = 4
|
|
s.corner_radius_top_right = 4
|
|
s.corner_radius_bottom_left = 4
|
|
s.corner_radius_bottom_right = 4
|
|
if pressed:
|
|
# Inset shadow on top + left (pressed-in look).
|
|
s.shadow_color = Color(0, 0, 0, 0.35)
|
|
s.shadow_size = 0
|
|
# Visually offset content slightly down/right when pressed.
|
|
s.content_margin_top = 4
|
|
s.content_margin_left = 7
|
|
s.content_margin_right = 5
|
|
s.content_margin_bottom = 2
|
|
else:
|
|
# Subtle drop shadow for depth.
|
|
s.shadow_color = Color(0, 0, 0, 0.20)
|
|
s.shadow_size = 2
|
|
s.shadow_offset = Vector2(0, 1)
|
|
s.content_margin_top = 3
|
|
s.content_margin_left = 6
|
|
s.content_margin_right = 6
|
|
s.content_margin_bottom = 3
|
|
return s
|
|
|
|
|
|
static func _panel_box() -> StyleBoxFlat:
|
|
var s := StyleBoxFlat.new()
|
|
s.bg_color = C_PANEL
|
|
s.border_color = C_PANEL_DARK
|
|
s.border_width_left = 2
|
|
s.border_width_right = 2
|
|
s.border_width_top = 2
|
|
s.border_width_bottom = 2
|
|
s.corner_radius_top_left = 6
|
|
s.corner_radius_top_right = 6
|
|
s.corner_radius_bottom_left = 6
|
|
s.corner_radius_bottom_right = 6
|
|
s.shadow_color = Color(0, 0, 0, 0.35)
|
|
s.shadow_size = 4
|
|
s.shadow_offset = Vector2(0, 2)
|
|
s.content_margin_top = 8
|
|
s.content_margin_left = 10
|
|
s.content_margin_right = 10
|
|
s.content_margin_bottom = 8
|
|
return s
|
|
|
|
|
|
static func _focus_box() -> StyleBoxFlat:
|
|
var s := StyleBoxFlat.new()
|
|
s.bg_color = Color(0, 0, 0, 0) # transparent
|
|
s.border_color = C_ACCENT_GOLD
|
|
s.border_width_left = 2
|
|
s.border_width_right = 2
|
|
s.border_width_top = 2
|
|
s.border_width_bottom = 2
|
|
s.corner_radius_top_left = 4
|
|
s.corner_radius_top_right = 4
|
|
s.corner_radius_bottom_left = 4
|
|
s.corner_radius_bottom_right = 4
|
|
return s
|
|
|
|
|
|
static func _slider_track() -> StyleBoxFlat:
|
|
var s := StyleBoxFlat.new()
|
|
s.bg_color = C_PANEL_DARK
|
|
s.corner_radius_top_left = 4
|
|
s.corner_radius_top_right = 4
|
|
s.corner_radius_bottom_left = 4
|
|
s.corner_radius_bottom_right = 4
|
|
s.content_margin_top = 4
|
|
s.content_margin_bottom = 4
|
|
return s
|
|
|
|
|
|
static func _slider_fill() -> StyleBoxFlat:
|
|
var s := StyleBoxFlat.new()
|
|
s.bg_color = C_ACCENT_GOLD
|
|
s.corner_radius_top_left = 4
|
|
s.corner_radius_top_right = 4
|
|
s.corner_radius_bottom_left = 4
|
|
s.corner_radius_bottom_right = 4
|
|
return s
|
|
|
|
|
|
static func _scrollbar_track() -> StyleBoxFlat:
|
|
var s := StyleBoxFlat.new()
|
|
s.bg_color = Color(C_PANEL_DARK.r, C_PANEL_DARK.g, C_PANEL_DARK.b, 0.3)
|
|
s.corner_radius_top_left = 3
|
|
s.corner_radius_top_right = 3
|
|
s.corner_radius_bottom_left = 3
|
|
s.corner_radius_bottom_right = 3
|
|
return s
|