Add a theme picker and an Original web theme (#42)
Build / test (push) Successful in 1m31s
Build / build (push) Successful in 1m13s

Settings has a Theme row listing Settings.THEMES: Classic (main_theme.tres,
still the default) and Original web (original_web_theme.tres), which looks like
MusicFrontend: orange-bordered buttons, a #333 header and popups, #424242
panels, #4e4e4e alternating rows, a yellow playing song and a light grey
player bar. Picking one sets the root window's theme at once and saves it as
theme_id in settings.cfg, and it is applied at start.

The popup backdrop and score graph follow a theme switch. The top bar has its
own TopBarBackground style. test_theme.gd checks that every theme has
everything main_theme.tres has. The screenshot tool takes a theme id.

Closes #42

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SansanandClaude Opus 5.5 committed 2026-10-12 20:50:12 +02:00
1 parent 70e970b24c
commit 94e8dfb602
15 files changed
+2497 -586

No files matched your search

+29
View File
@@ -15,6 +15,7 @@ const STYLES: Dictionary = {
"WindowBackground": "Panel",
"PopupBackground": "Panel",
"StepButton": "Button",
"TopBarBackground": "PanelContainer",
"PlayerList": "PanelContainer",
"PlayerCharacter": "PanelContainer",
"PlayerCharacterHover": "PanelContainer",
@@ -171,3 +172,31 @@ func test_japanese_fallback_keeps_the_line_height_of_godots_font() -> void:
assert_float(_theme.default_font.get_height(size)) \
.override_failure_message("Line height at size %d" % size) \
.is_equal(builtin.get_height(size))
func test_every_theme_has_everything_the_main_theme_has() -> void:
var data_types: Array[Theme.DataType] = [Theme.DATA_TYPE_COLOR, Theme.DATA_TYPE_CONSTANT,
Theme.DATA_TYPE_FONT, Theme.DATA_TYPE_FONT_SIZE, Theme.DATA_TYPE_ICON,
Theme.DATA_TYPE_STYLEBOX]
for entry: Dictionary in Settings.THEMES:
var other: Theme = load(entry.path)
assert_object(other).override_failure_message(entry.path + " is missing").is_not_null()
for theme_type: String in _theme.get_type_list():
assert_str(String(other.get_type_variation_base(theme_type))) \
.override_failure_message("%s: %s" % [entry.name, theme_type]) \
.is_equal(String(_theme.get_type_variation_base(theme_type)))
for data_type: Theme.DataType in data_types:
for item: String in _theme.get_theme_item_list(data_type, theme_type):
assert_bool(other.has_theme_item(data_type, item, theme_type)) \
.override_failure_message("%s has no %s/%s" % [entry.name,
theme_type, item]).is_true()
func test_every_theme_has_its_own_name_and_id() -> void:
var ids: Array = Settings.THEMES.map(func(entry: Dictionary) -> String: return entry.id)
var names: Array = Settings.THEMES.map(func(entry: Dictionary) -> String: return entry.name)
assert_str(Settings.THEMES[0].path).is_equal(THEME_PATH)
for list: Array in [ids, names]:
for value: String in list:
assert_int(list.count(value)).override_failure_message(value).is_equal(1)
+74
View File
@@ -0,0 +1,74 @@
extends MainWindowTestSuite
## Picking a theme in Settings switches the whole game's look at once and is saved.
var _settings: SettingsPopup
func before_test() -> void:
await super()
_settings = _window.get_node("SettingsPopup")
func after_test() -> void:
Settings.apply_theme(Settings.THEMES[0].id)
await super()
func _pick(theme_name: String) -> void:
var index: int = Settings.THEMES.map(
func(entry: Dictionary) -> String: return entry.name).find(theme_name)
_settings.theme_button.select(index)
_settings.theme_button.item_selected.emit(index)
await _runner.simulate_frames(1)
func test_picker_lists_every_theme_and_shows_the_one_in_use() -> void:
var names: Array[String] = []
for index: int in _settings.theme_button.item_count:
names.append(_settings.theme_button.get_item_text(index))
assert_array(names).contains_exactly(["Classic", "Original web"])
assert_str(_settings.theme_button.get_item_text(_settings.theme_button.selected)) \
.is_equal("Classic")
func test_picking_a_theme_switches_the_look_at_once() -> void:
var button: Button = _window.get_node("TopBar/%SyncButton")
var classic_text: Color = button.get_theme_color(&"font_color")
var panel: Panel = _window.get_node("SettingsPopup/SettingsPanel")
var popup_style: StyleBox = panel.get_theme_stylebox(&"panel")
await _pick("Original web")
assert_str(Settings.theme_id).is_equal("original_web")
assert_str(_window.get_tree().root.theme.resource_path) \
.is_equal("res://themes/original_web_theme.tres")
assert_object(panel.get_theme_stylebox(&"panel")).is_not_same(popup_style)
# The web version's buttons have orange text
assert_bool(button.get_theme_color(&"font_color") != classic_text).is_true()
func test_chosen_theme_is_saved() -> void:
await _pick("Original web")
var saved: ConfigFile = ConfigFile.new()
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
assert_str(saved.get_value("app", "theme_id")).is_equal("original_web")
func test_backdrop_and_graph_follow_the_theme() -> void:
var backdrop: PopupBackdrop = _window.get_node("PopupBackdrop")
var classic: Color = backdrop.color
await _pick("Original web")
assert_object(backdrop.color).is_equal(backdrop.get_theme_color(&"backdrop", &"Palette"))
assert_bool(backdrop.color != classic).is_true()
func test_unknown_theme_falls_back_to_the_default() -> void:
Settings.apply_theme("no_such_theme")
assert_str(Settings.theme_id).is_equal("classic")
assert_str(_window.get_tree().root.theme.resource_path) \
.is_equal("res://themes/main_theme.tres")
@@ -0,0 +1 @@
uid://de1hpddu212xs