From f6c754115a509f951e648afe408213438ac33f19 Mon Sep 17 00:00:00 2001 From: "Hermes (scopedvr-dev)" Date: Tue, 20 Oct 2026 12:08:10 +0000 Subject: [PATCH] =?UTF-8?q?U7=20(t=5F2336c58e):=20Feinschliff=20=E2=80=94?= =?UTF-8?q?=20Theme=20konsequent=20(Disabled/Fokus/Hover,=20LineEdit,=20Ta?= =?UTF-8?q?b-Hover,=20Splitter-Anfassen,=20Scrollbars,=20Farb-Token=20warn?= =?UTF-8?q?ing/badge=5Foutline),=20Abstands-Raster=204/8=20=C3=BCber=20UIS?= =?UTF-8?q?cale,=20Typografie-Raster=20(heading/body/small=20via=20ui=5Fsc?= =?UTF-8?q?ale.gd),=20Tooltips=20f=C3=BCr=20alle=20Kn=C3=B6pfe=20inkl.=20D?= =?UTF-8?q?ialoge,=20Leerzustand=20in=20der=20Bibliothek,=20Fenstertitel?= =?UTF-8?q?=20mit=20=E2=80=9E(ungespeichert)=E2=80=9C.=20Neue=20Suite=20te?= =?UTF-8?q?sts/test=5Ftheme.gd=20(35=20Checks).=20Keine=20Funktions=C3=A4n?= =?UTF-8?q?derung.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/inspector_panel.gd | 2 +- src/ui/main.gd | 32 +++-- src/ui/new_map_dialog.gd | 3 + src/ui/panels/assets_panel.gd | 64 +++++---- src/ui/panels/check_panel.gd | 6 +- src/ui/panels/editor_panel.gd | 20 ++- src/ui/panels/map_panel.gd | 2 + src/ui/panels/status_bar.gd | 16 ++- src/ui/panels/symmetry_panel.gd | 13 +- src/ui/panels/terrain_panel.gd | 29 +++-- src/ui/panels/toolbar.gd | 10 +- src/ui/resize_map_dialog.gd | 4 + src/ui/ui_scale.gd | 18 ++- tests/probe_u7_theme_render.gd | 61 +++++++++ tests/probe_u7_theme_render.gd.uid | 1 + tests/test_theme.gd | 203 +++++++++++++++++++++++++++++ tests/test_theme.gd.uid | 1 + theme/editor_theme.tres | 157 +++++++++++++++++++++- 18 files changed, 574 insertions(+), 68 deletions(-) create mode 100644 tests/probe_u7_theme_render.gd create mode 100644 tests/probe_u7_theme_render.gd.uid create mode 100644 tests/test_theme.gd create mode 100644 tests/test_theme.gd.uid diff --git a/src/ui/inspector_panel.gd b/src/ui/inspector_panel.gd index 87ff0f0..2e54207 100644 --- a/src/ui/inspector_panel.gd +++ b/src/ui/inspector_panel.gd @@ -287,7 +287,7 @@ func _swatch(c: Color, name: String) -> Button: sb.set_corner_radius_all(6) btn.add_theme_stylebox_override("normal", sb) var sb_hover: StyleBoxFlat = sb.duplicate() - sb_hover.bg_color = Color(minf(c.r + 0.08, 1.0), minf(c.g + 0.08, 1.0), minf(c.b + 0.08, 1.0)) + sb_hover.bg_color = c.lightened(0.08) # U7: Aufhellung statt Manuell-Mathe btn.add_theme_stylebox_override("hover", sb_hover) btn.add_theme_stylebox_override("pressed", sb_hover) btn.add_theme_stylebox_override("focus", StyleBoxEmpty.new()) diff --git a/src/ui/main.gd b/src/ui/main.gd index fbee0c4..0d33fc8 100644 --- a/src/ui/main.gd +++ b/src/ui/main.gd @@ -485,8 +485,9 @@ func _build_dialogs_and_overlays() -> void: # Dialog — der Editor bleibt bedienbar, das Panel liegt nur obenauf. help_overlay = PanelContainer.new() var overlay_style := StyleBoxFlat.new() - overlay_style.bg_color = Color(0.09, 0.11, 0.13, 0.97) # Slate, wie Statuszeile - overlay_style.border_color = Color(0.35, 0.55, 0.7) + # U7: Farben über das Theme (Editor/colors/help_bg, help_border). + overlay_style.bg_color = get_theme_color("help_bg", "Editor") + overlay_style.border_color = get_theme_color("help_border", "Editor") overlay_style.set_border_width_all(2) overlay_style.set_corner_radius_all(8) overlay_style.set_content_margin_all(UIScale.px(14)) @@ -505,8 +506,8 @@ func _build_dialogs_and_overlays() -> void: help_overlay.add_child(overlay_box) help_overlay_title = Label.new() help_overlay_title.text = "Kurzhilfe — alle Tasten auf einen Blick (H oder Esc schließt)" - help_overlay_title.add_theme_font_size_override("font_size", UIScale.f(20)) - help_overlay_title.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0)) + help_overlay_title.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + help_overlay_title.add_theme_color_override("font_color", get_theme_color("accent", "Editor")) overlay_box.add_child(help_overlay_title) help_overlay_text = RichTextLabel.new() help_overlay_text.bbcode_enabled = true @@ -591,7 +592,8 @@ func _build_center_viewport() -> SubViewportContainer: help_label.offset_left = UIScale.px(12) help_label.offset_bottom = UIScale.px(-8) help_label.offset_top = UIScale.px(-46) - help_label.modulate = Color(1, 1, 1, 0.65) + # U7: Hinweis-Farbe über Theme (Editor/colors/muted) statt modulate. + help_label.add_theme_color_override("font_color", get_theme_color("muted", "Editor")) # Karte t_297221b5: OHNE Umbruch würde die Mindestbreite des langen Texts # bei Stufe „Groß“ die 3D-Ansicht breiter zwingen als das Fenster ist — # die rechte Spalte rutschte aus dem Bildschirm. Mit Umbruch bleibt die @@ -632,8 +634,9 @@ func _rubberband_clear() -> void: func _draw_rubberband() -> void: if rubber_rect.size.x <= 0.0 or rubber_rect.size.y <= 0.0: return - rubber_overlay.draw_rect(rubber_rect, Color(0.45, 0.8, 1.0, 0.16), true) - rubber_overlay.draw_rect(rubber_rect, Color(0.45, 0.8, 1.0, 0.9), false, 2.0) + # U7: Rubberband-Farben über das Theme (Editor/colors/rubber_fill/_line). + rubber_overlay.draw_rect(rubber_rect, get_theme_color("rubber_fill", "Editor"), true) + rubber_overlay.draw_rect(rubber_rect, get_theme_color("rubber_line", "Editor"), false, 2.0) # ------------------------------------------------------------ Map-Aktionen -- func _start_with_new_map() -> void: @@ -854,8 +857,9 @@ func _replace_doc_in_place(result: Dictionary) -> void: func _update_banner_build(parent: Container) -> void: update_banner = PanelContainer.new() var style := StyleBoxFlat.new() - style.bg_color = Color(0.36, 0.25, 0.06, 0.97) # gedämpftes Bernstein - style.border_color = Color(0.95, 0.72, 0.15) + # U7: Banner-Farben über das Theme (Editor/colors/update_bg/_border). + style.bg_color = get_theme_color("update_bg", "Editor") + style.border_color = get_theme_color("update_border", "Editor") style.set_border_width_all(2) style.set_content_margin_all(UIScale.px(8)) style.set_corner_radius_all(6) @@ -864,7 +868,7 @@ func _update_banner_build(parent: Container) -> void: h.add_theme_constant_override("separation", UIScale.px(14)) update_banner.add_child(h) update_label = Label.new() - update_label.add_theme_font_size_override("font_size", UIScale.f(17)) + update_label.add_theme_font_size_override("font_size", UIScale.font_size()) h.add_child(update_label) var spacer := Control.new() spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -2079,15 +2083,19 @@ func _refresh_file_label() -> void: # Karte t_297221b5: bei egalisiertem Label (große Stufe, schmales Fenster) # steht der volle Name im Tooltip. file_label.tooltip_text = file_label.text - get_window().title = "%s — %s" % [APP_TITLE, map_name] + # U7 (Karte t_2336c58e): ungespeicherte Änderungen auch im FENSTERTITEL — + # so sieht man sie selbst dann, wenn die Datei-Leiste egalisiert ist. + get_window().title = "%s — %s%s" % [APP_TITLE, map_name, + " (ungespeichert)" if dirty else ""] func _set_status(text: String, is_error := false) -> void: if status_label == null or not is_instance_valid(status_label): return # UI wurde gerade neu aufgebaut (UI-Größe) — nichts anfassen status_label.text = text + # U7: Statusfarben über das Theme (Editor/colors/status_error/_ok). status_label.add_theme_color_override("font_color", - Color(1.0, 0.6, 0.5) if is_error else Color(0.75, 0.95, 0.75)) + get_theme_color("status_error" if is_error else "status_ok", "Editor")) func _fmt_int(value: int) -> String: diff --git a/src/ui/new_map_dialog.gd b/src/ui/new_map_dialog.gd index 368f5e2..8164e5d 100644 --- a/src/ui/new_map_dialog.gd +++ b/src/ui/new_map_dialog.gd @@ -39,6 +39,8 @@ func _init() -> void: size_option.add_item(label) size_option.select(1) # Mittel als Default size_option.custom_minimum_size = UIScale.v2(0, 40) + # U7: Klartext-Tooltips (Karte t_2336c58e). + size_option.tooltip_text = "Kantenlänge des Geländes — größer = mehr Platz zum Bauen" vbox.add_child(size_option) vbox.add_child(_make_label("Welches Wetter soll die Map haben?")) @@ -46,6 +48,7 @@ func _init() -> void: for label in _weather_labels: weather_option.add_item(label) weather_option.custom_minimum_size = UIScale.v2(0, 40) + weather_option.tooltip_text = "Licht und Stimmung der Karte (Tag/Abend/Regen/Nacht)" vbox.add_child(weather_option) var hint := _make_label("Die Map startet leer mit 4 Spawn-Punkten pro Team\n(Blau im Westen, Rot im Osten) — beides lässt sich später ändern.") diff --git a/src/ui/panels/assets_panel.gd b/src/ui/panels/assets_panel.gd index 4492ed7..afb3e03 100644 --- a/src/ui/panels/assets_panel.gd +++ b/src/ui/panels/assets_panel.gd @@ -44,6 +44,7 @@ var _recent_tiles: Dictionary = {} # id → Button (Zuletzt-Ansicht, frisch g var _recent_ids: Array = [] # Reihenfolge: neueste zuerst var selected_oid: String = "" # per Klick gewähltes Objekt var _id_font_normal: int = 0 # ID-Schriftgröße (skaliert), Normalzustand +var _empty_hint: Label # Leerzustand (U7): kein Treffer / leerer Bereich static func spec() -> Dictionary: @@ -75,7 +76,7 @@ func build() -> void: # „Alle auf" / „Alle zu" (t_59fc0f21): Klapp-Zustand aller Gruppen auf einmal. var group_btns := HBoxContainer.new() - group_btns.add_theme_constant_override("separation", UIScale.px(6)) + group_btns.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster library_open_all_btn = Button.new() library_open_all_btn.text = "Alle auf" library_open_all_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -93,8 +94,9 @@ func build() -> void: # Zähler „N von M sichtbar" (U4): M = Objekte im aktiven Bereich, N = davon # durch Suche/Kategorie sichtbar. library_counter = Label.new() - library_counter.add_theme_font_size_override("font_size", UIScale.f(11)) - library_counter.modulate = Color(1, 1, 1, 0.7) + # U7: Nebentext-Größe + Theme-Farbe (Editor/colors/field). + library_counter.add_theme_font_size_override("font_size", UIScale.small_font_size()) + library_counter.add_theme_color_override("font_color", theme_color("field")) library_counter.tooltip_text = "Sichtbare Kacheln im aktiven Bereich" add_child(library_counter) @@ -107,6 +109,14 @@ func build() -> void: library_box.size_flags_horizontal = Control.SIZE_EXPAND_FILL library_box.add_theme_constant_override("separation", UIScale.px(4)) scroll.add_child(library_box) + # Leerzustand (U7, „fertig aussehen"): freundlicher Klartext statt + # stummer leerer Fläche — Suchbegriff ohne Treffer oder leerer Bereich. + _empty_hint = Label.new() + _empty_hint.text = "Nichts gefunden — anderen Suchbegriff oder „Alle“ versuchen." + _empty_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _empty_hint.add_theme_color_override("font_color", theme_color("muted")) + _empty_hint.visible = false + scroll.add_child(_empty_hint) _recent_box = VBoxContainer.new() _recent_box.visible = false _recent_box.add_theme_constant_override("separation", UIScale.px(4)) @@ -230,7 +240,7 @@ func _fill_library() -> void: header.alignment = HORIZONTAL_ALIGNMENT_LEFT header.custom_minimum_size = UIScale.v2(0, 34) header.tooltip_text = "Gruppe „%s“ auf-/zuklappen" % g_label - header.add_theme_font_size_override("font_size", UIScale.f(18)) + header.add_theme_font_size_override("font_size", UIScale.heading_font_size()) header.add_theme_font_override("font", _library_header_font()) library_box.add_child(header) var group_box := VBoxContainer.new() @@ -239,8 +249,8 @@ func _fill_library() -> void: var grid := GridContainer.new() grid.columns = 2 grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL - grid.add_theme_constant_override("h_separation", UIScale.px(6)) - grid.add_theme_constant_override("v_separation", UIScale.px(6)) + grid.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster + grid.add_theme_constant_override("v_separation", UIScale.px(8)) # U7-Raster group_box.add_child(grid) library_box.add_child(group_box) header.toggled.connect(_on_library_group_toggled.bind(g_key, g_label, g_ids.size(), header, group_box)) @@ -424,6 +434,9 @@ func _apply_library_filter(query: String) -> void: visible_count += 1 if library_counter != null: library_counter.text = "%d von %d sichtbar" % [visible_count, scope] + # Leerzustand (U7): 0 Treffer → Klartext-Hinweis zeigen statt stummer Fläche. + if _empty_hint != null: + _empty_hint.visible = visible_count == 0 func _library_oid_matches(oid: String, q: String) -> bool: @@ -480,7 +493,7 @@ func _build_tile(oid: String) -> Button: var tile := VBoxContainer.new() tile.mouse_filter = Control.MOUSE_FILTER_IGNORE tile.set_anchors_preset(Control.PRESET_FULL_RECT) - tile.add_theme_constant_override("separation", UIScale.px(2)) + tile.add_theme_constant_override("separation", UIScale.px(4)) # U7-Raster btn.add_child(tile) # Vorschaubild: bleibt DIREKTES Kind der Kachel-VBox (test_library_ # browser_ui sucht es dort) mit gepinnter Min-Größe (test_ui_scale @@ -498,9 +511,10 @@ func _build_tile(oid: String) -> Button: tile.add_child(tr) var id_lbl := Label.new() id_lbl.text = oid - _id_font_normal = UIScale.f(9) + # U7: Tile-ID als Nebentext-Größe, Farbe über Theme (Editor/colors/title). + _id_font_normal = UIScale.small_font_size() id_lbl.add_theme_font_size_override("font_size", _id_font_normal) - id_lbl.modulate = Color(1, 1, 1, 0.6) + id_lbl.add_theme_color_override("font_color", theme_color("title")) id_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER id_lbl.clip_text = true id_lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS @@ -509,7 +523,7 @@ func _build_tile(oid: String) -> Button: tile.add_child(id_lbl) var lbl := Label.new() lbl.text = name_txt - lbl.add_theme_font_size_override("font_size", UIScale.f(11)) + lbl.add_theme_font_size_override("font_size", UIScale.small_font_size()) lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER lbl.clip_text = true lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS @@ -518,16 +532,19 @@ func _build_tile(oid: String) -> Button: if missing: # Klar sichtbar markieren (U4): Bild abgedunkelt + Warn-Badge # als Overlay am unteren Bildrand (immer innerhalb der Kachel). - tr.modulate = Color(1, 1, 1, 0.45) + # U7: Abdunkeln nur über Alpha (Farbe bleibt weiß), Badge-Farbe + # über Theme (Editor/colors/warning + badge_outline), Nebentext- + # Größe statt Mikroschrift (U7-Raster: zwei Größen + Überschrift). + tr.modulate.a = 0.45 tr.clip_contents = true var warn := Label.new() warn.text = "Modell fehlt" warn.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) - warn.offset_top = -UIScale.px(13) + warn.offset_top = -UIScale.px(18) warn.offset_bottom = -UIScale.px(1) - warn.add_theme_font_size_override("font_size", UIScale.f(9)) - warn.add_theme_color_override("font_color", Color(1, 0.62, 0.3)) - warn.add_theme_color_override("font_outline_color", Color(0, 0, 0)) + warn.add_theme_font_size_override("font_size", UIScale.small_font_size()) + warn.add_theme_color_override("font_color", theme_color("warning")) + warn.add_theme_color_override("font_outline_color", theme_color("badge_outline")) warn.add_theme_constant_override("outline_size", UIScale.px(3)) warn.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER warn.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -555,7 +572,9 @@ func _set_tile_hover(oid: String, on: bool) -> void: var id_lbl := _find_id_label(btn) if id_lbl == null: return - id_lbl.add_theme_font_size_override("font_size", UIScale.f(14) if on else _id_font_normal) + # U7-Raster: Hover-Stufe ist die Body-Schrift (klein → body), kein Wert ins Freie. + id_lbl.add_theme_font_size_override("font_size", + UIScale.font_size() if on else _id_font_normal) func _tile_button(oid: String) -> Button: @@ -622,11 +641,12 @@ func _apply_tile_selection() -> void: for other_oid in library_buttons: var btn := library_buttons[other_oid] as Button btn.button_pressed = str(other_oid) == selected_oid - btn.modulate = Color(1, 1, 1) if str(other_oid) == selected_oid else Color(1, 1, 1, 0.85) + # U7: Auswahl-Markierung nur über Alpha (Farbe bleibt weiß). + btn.modulate.a = 1.0 if str(other_oid) == selected_oid else 0.85 for recent_oid in _recent_tiles: var rbtn := _recent_tiles[recent_oid] as Button rbtn.button_pressed = str(recent_oid) == selected_oid - rbtn.modulate = Color(1, 1, 1) if str(recent_oid) == selected_oid else Color(1, 1, 1, 0.85) + rbtn.modulate.a = 1.0 if str(recent_oid) == selected_oid else 0.85 ## Der Bibliotheks-Button des aktiven Platzier-Objekts leuchtet auf @@ -682,14 +702,14 @@ func _fill_recent_box() -> void: _recent_tiles.clear() var head := Label.new() head.text = "Zuletzt benutzt" - head.add_theme_font_size_override("font_size", UIScale.f(18)) + head.add_theme_font_size_override("font_size", UIScale.heading_font_size()) head.add_theme_font_override("font", _library_header_font()) _recent_box.add_child(head) _recent_grid = GridContainer.new() _recent_grid.columns = 2 _recent_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL - _recent_grid.add_theme_constant_override("h_separation", UIScale.px(6)) - _recent_grid.add_theme_constant_override("v_separation", UIScale.px(6)) + _recent_grid.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster + _recent_grid.add_theme_constant_override("v_separation", UIScale.px(8)) # U7-Raster _recent_box.add_child(_recent_grid) var lib: Dictionary = main.manifest.get("objects", {}) for oid in _recent_ids: @@ -700,4 +720,4 @@ func _fill_recent_box() -> void: _recent_tiles[str(oid)] = btn if str(oid) == selected_oid: btn.button_pressed = true - btn.modulate = Color(1, 1, 1) + btn.modulate.a = 1.0 diff --git a/src/ui/panels/check_panel.gd b/src/ui/panels/check_panel.gd index 072bf57..027d77c 100644 --- a/src/ui/panels/check_panel.gd +++ b/src/ui/panels/check_panel.gd @@ -45,13 +45,13 @@ func build() -> void: add_child(spawn_edit_btn) spawn_panel = VBoxContainer.new() - spawn_panel.add_theme_constant_override("separation", UIScale.px(6)) + spawn_panel.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster spawn_panel.visible = false add_child(spawn_panel) spawn_panel.add_child(_field_label("Team:")) var team_row := HBoxContainer.new() - team_row.add_theme_constant_override("separation", UIScale.px(6)) + team_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster spawn_panel.add_child(team_row) var team_group := ButtonGroup.new() spawn_team_blue = _spawn_team_button("🔵 Team Blau", team_group, 0) @@ -93,6 +93,8 @@ func _spawn_team_button(text: String, group: ButtonGroup, team: int) -> Button: btn.toggle_mode = true btn.button_group = group btn.custom_minimum_size = UIScale.v2(0, 40) + # U7: Werkzeugname im Tooltip (Team, in das neue Zonen gemalt werden). + btn.tooltip_text = "Zonen für %s malen" % ("Team Blau" if team == 0 else "Team Rot") btn.pressed.connect(_on_spawn_team_changed.bind(team)) return btn diff --git a/src/ui/panels/editor_panel.gd b/src/ui/panels/editor_panel.gd index 345f400..5301ae5 100644 --- a/src/ui/panels/editor_panel.gd +++ b/src/ui/panels/editor_panel.gd @@ -16,7 +16,8 @@ var main # src/ui/main.gd (script-erweitert → untypisiert) func setup(p_main) -> void: main = p_main - add_theme_constant_override("separation", 8) + # U7-Abstands-Raster (4/8 px, über ui_scale skaliert): Panel-Grundabstand. + add_theme_constant_override("separation", UIScale.px(8)) ## Titel der Sektions-Überschrift (Registry-Titel als Default). @@ -24,20 +25,29 @@ func panel_title() -> String: return "Panel" +# --- Farb-Zugriff NUR über das Theme (Karte t_2336c58e/U7) ------------------ +## Farben kommen ausschließlich aus theme/editor_theme.tres (Abschnitt +## Editor/colors/…) — keine Farbwerte mehr im Panel-Code. Der Wert liest +## sich vom angeschlossenen Theme-Baum (main hängt theme auf das Fenster). + +func theme_color(cname: String) -> Color: + return get_theme_color(cname, "Editor") + + # --- Gemeinsame Bau-Helfer (aus main.gd übernommen, Logik identisch) -------- func _header_label(text: String) -> Label: var label := Label.new() label.text = text - label.add_theme_font_size_override("font_size", UIScale.f(19)) - label.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0)) + label.add_theme_font_size_override("font_size", UIScale.heading_font_size()) + label.add_theme_color_override("font_color", theme_color("accent")) return label func _field_label(text: String) -> Label: var label := Label.new() label.text = text - label.modulate = Color(1, 1, 1, 0.7) + label.add_theme_color_override("font_color", theme_color("field")) return label @@ -45,7 +55,7 @@ func _muted_label(text: String) -> Label: var label := Label.new() label.text = text label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - label.modulate = Color(1, 1, 1, 0.55) + label.add_theme_color_override("font_color", theme_color("muted")) return label diff --git a/src/ui/panels/map_panel.gd b/src/ui/panels/map_panel.gd index 4c4b08e..d2bdd53 100644 --- a/src/ui/panels/map_panel.gd +++ b/src/ui/panels/map_panel.gd @@ -49,6 +49,8 @@ func build() -> void: add_child(_field_label("Wetter")) prop_weather = OptionButton.new() + # U7: Klartext-Tooltip am Wetter-Auswahlfeld. + prop_weather.tooltip_text = "Licht und Stimmung der Karte (Tag/Abend/Regen/Nacht)" prop_weather.add_item("Mittag (sonnig, klar)") prop_weather.add_item("Abend (warmes Licht)") prop_weather.add_item("Regen (grau, kühl)") diff --git a/src/ui/panels/status_bar.gd b/src/ui/panels/status_bar.gd index 302882e..0501b6d 100644 --- a/src/ui/panels/status_bar.gd +++ b/src/ui/panels/status_bar.gd @@ -24,7 +24,9 @@ func build() -> void: # Karte 3/3 (t_11b5c25b): PERMANENTE Werkzeug-Anzeige — Sly sieht immer, # in welchem Werkzeug er ist, plus Hint für den Flugmodus (Umschalt+F). tool_label = Label.new() - tool_label.modulate = Color(0.55, 0.8, 1.0) + # U7: Werkzeug-Farbe über Theme (Editor/colors/accent). + tool_label.add_theme_color_override("font_color", + get_theme_color("accent", "Editor")) # Karte t_297221b5: Statuszeilen-Labels egalisiert (min-size-Druck bei 1.5×) tool_label.clip_text = true tool_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS @@ -33,20 +35,26 @@ func build() -> void: # Symmetrie-Anzeige (Karte t_f5935023, M1): Sly sieht in der Statuszeile # immer, ob/wie gespiegelt wird (Standard: Punkt). sym_label = Label.new() - sym_label.modulate = Color(0.55, 0.8, 1.0) + # U7: Symmetrie-Farbe über Theme (Editor/colors/accent). + sym_label.add_theme_color_override("font_color", + get_theme_color("accent", "Editor")) sym_label.clip_text = true sym_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS add_child(sym_label) stats_objects = Label.new() - stats_objects.modulate = Color(1, 1, 1, 0.75) + # U7: Nebentext-Farbe über Theme (Editor/colors/field). + stats_objects.add_theme_color_override("font_color", + get_theme_color("field", "Editor")) stats_objects.clip_text = true stats_objects.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS add_child(stats_objects) # Terrain-Anteil + Editor-FPS (Perf-Zähler, Karte t_9aba04f1) stats_terrain = Label.new() - stats_terrain.modulate = Color(1, 1, 1, 0.75) + # U7: Nebentext-Farbe über Theme (Editor/colors/field). + stats_terrain.add_theme_color_override("font_color", + get_theme_color("field", "Editor")) stats_terrain.clip_text = true stats_terrain.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS add_child(stats_terrain) diff --git a/src/ui/panels/symmetry_panel.gd b/src/ui/panels/symmetry_panel.gd index e65a60e..440eb81 100644 --- a/src/ui/panels/symmetry_panel.gd +++ b/src/ui/panels/symmetry_panel.gd @@ -51,6 +51,8 @@ func build() -> void: add_child(half_label) half_option = OptionButton.new() half_option.custom_minimum_size = UIScale.v2(0, 36) + # U7: Klartext-Tooltip — welche Seite als Vorlage dient. + half_option.tooltip_text = "Welche Hälfte die Vorlage ist (die andere wird angepasst)" half_option.item_selected.connect(_on_half_selected) add_child(half_option) _refresh_half_option() @@ -208,7 +210,8 @@ func _apply(quelle_hoch: bool) -> void: main.call("_after_structure_change") var report := str(res["report"]) report_label.text = report - report_label.modulate = Color(0.55, 0.9, 0.55) + # U7: Bericht-Farbe über Theme (Editor/colors/status_ok). + report_label.add_theme_color_override("font_color", theme_color("status_ok")) main.call("_set_status", report.split(".")[0] + ".") @@ -223,11 +226,15 @@ func _on_check_pressed() -> void: var rep := SymmetryOps.check(main.doc, mode) var text := str(rep["text"]) report_label.text = text - report_label.modulate = Color(0.55, 0.9, 0.55) if bool(rep["ok"]) else Color(1.0, 0.75, 0.35) + # U7: Bericht-Farben über Theme (Editor/colors/status_ok / warning). + report_label.add_theme_color_override("font_color", + theme_color("status_ok") if bool(rep["ok"]) else theme_color("warning")) main.call("_set_status", text) func _say(text: String, is_error := false) -> void: report_label.text = text - report_label.modulate = Color(1.0, 0.75, 0.35) if is_error else Color(0.55, 0.9, 0.55) + # U7: Bericht-Farben über Theme (Editor/colors/warning / status_ok). + report_label.add_theme_color_override("font_color", + theme_color("warning") if is_error else theme_color("status_ok")) main.call("_set_status", text, is_error) diff --git a/src/ui/panels/terrain_panel.gd b/src/ui/panels/terrain_panel.gd index 7348195..7425181 100644 --- a/src/ui/panels/terrain_panel.gd +++ b/src/ui/panels/terrain_panel.gd @@ -41,19 +41,23 @@ func build() -> void: add_child(terrain_edit_btn) terrain_panel = VBoxContainer.new() - terrain_panel.add_theme_constant_override("separation", UIScale.px(6)) + terrain_panel.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster terrain_panel.visible = false add_child(terrain_panel) terrain_panel.add_child(_field_label("Pinsel-Werkzeug:")) var mode_row := HBoxContainer.new() - mode_row.add_theme_constant_override("separation", UIScale.px(6)) + mode_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster terrain_panel.add_child(mode_row) var mode_group := ButtonGroup.new() - terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group) - terrain_mode_lower = _terrain_mode_button("Senken ⬇", mode_group) - terrain_mode_smooth = _terrain_mode_button("Glätten ≈", mode_group) - terrain_mode_paint = _terrain_mode_button("Malen 🖌", mode_group) + terrain_mode_raise = _terrain_mode_button("Heben ⬆", mode_group, + "Pinsel-Modus Heben: Boden unter dem Pinsel anheben (Taste 1)") + terrain_mode_lower = _terrain_mode_button("Senken ⬇", mode_group, + "Pinsel-Modus Senken: Boden unter dem Pinsel absenken (Taste 2)") + terrain_mode_smooth = _terrain_mode_button("Glätten ≈", mode_group, + "Pinsel-Modus Glätten: Höhenunterschiede verrutschen (Taste 3)") + terrain_mode_paint = _terrain_mode_button("Malen 🖌", mode_group, + "Pinsel-Modus Malen: Material auf den Boden malen (Taste 4)") terrain_mode_raise.button_pressed = true terrain_mode_raise.toggled.connect(_on_terrain_mode_changed) terrain_mode_lower.toggled.connect(_on_terrain_mode_changed) @@ -71,7 +75,7 @@ func build() -> void: terrain_panel.add_child(terrain_palette) terrain_palette.add_child(_field_label("Material:")) var mat_row := HBoxContainer.new() - mat_row.add_theme_constant_override("separation", UIScale.px(6)) + mat_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster terrain_palette.add_child(mat_row) var mat_group := ButtonGroup.new() var mat_labels := ["🌿 Gras", "🏖 Sand", "🪨 Fels"] @@ -91,7 +95,7 @@ func build() -> void: # Pinselgrößen-Presets (Karte t_603ec1a8): drei große Buttons + freier Slider. terrain_panel.add_child(_field_label("Pinselgröße:")) var size_row := HBoxContainer.new() - size_row.add_theme_constant_override("separation", UIScale.px(6)) + size_row.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster terrain_panel.add_child(size_row) var size_group := ButtonGroup.new() terrain_size_btn_small = _terrain_size_button("Klein\n(4 m)", size_group, 4.0) @@ -142,15 +146,15 @@ func build() -> void: add_child(build_edit_btn) build_panel = VBoxContainer.new() - build_panel.add_theme_constant_override("separation", UIScale.px(6)) + build_panel.add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster build_panel.visible = false add_child(build_panel) build_panel.add_child(_field_label("Bauteil:")) var build_grid := GridContainer.new() build_grid.columns = 2 - build_grid.add_theme_constant_override("h_separation", UIScale.px(6)) - build_grid.add_theme_constant_override("v_separation", UIScale.px(6)) + build_grid.add_theme_constant_override("h_separation", UIScale.px(8)) # U7-Raster + build_grid.add_theme_constant_override("v_separation", UIScale.px(8)) # U7-Raster build_panel.add_child(build_grid) var build_group := ButtonGroup.new() for kind_id in BuildOps.KINDS.keys(): @@ -190,12 +194,13 @@ func _on_terrain_btn_pressed() -> void: main.call("_set_tool_mode", "objects") -func _terrain_mode_button(text: String, group: ButtonGroup) -> Button: +func _terrain_mode_button(text: String, group: ButtonGroup, hinweis: String) -> Button: var btn := Button.new() btn.text = text btn.toggle_mode = true btn.button_group = group btn.custom_minimum_size = UIScale.v2(0, 40) + btn.tooltip_text = hinweis # U7: Werkzeugname + Taste im Tooltip return btn diff --git a/src/ui/panels/toolbar.gd b/src/ui/panels/toolbar.gd index 026ffa9..44cecb1 100644 --- a/src/ui/panels/toolbar.gd +++ b/src/ui/panels/toolbar.gd @@ -41,12 +41,14 @@ static func spec() -> Dictionary: func build() -> void: - add_theme_constant_override("separation", UIScale.px(10)) + add_theme_constant_override("separation", UIScale.px(8)) # U7-Raster (4/8) var file_menu := MenuButton.new() file_menu.text = "Datei" file_menu.flat = false file_menu.custom_minimum_size = UIScale.v2(90, 40) + # U7: Klartext-Tooltip am Datei-Menü (alle Map-Aktionen an einem Ort). + file_menu.tooltip_text = "Map-Aktionen: neu, öffnen, speichern, testen, Updates" var popup := file_menu.get_popup() popup.add_item("Neue Map…", 0) popup.add_item("Öffnen…", 1) @@ -156,7 +158,7 @@ func build() -> void: title_label = Label.new() title_label.text = " %s" % main.APP_TITLE - title_label.add_theme_font_size_override("font_size", UIScale.f(20)) + title_label.add_theme_font_size_override("font_size", UIScale.heading_font_size()) # Karte t_297221b5: bei großer Stufe darf der Titel EGALISIEREN statt die # Top-Bar aus dem Fenster zu drücken (Mindestbreite der Schrift skaliert mit). # Mindestbreite, damit er nicht KOMPLETT verschwindet (Ellipsis macht "Scop…"). @@ -174,7 +176,9 @@ func build() -> void: file_label = Label.new() file_label.text = "" - file_label.modulate = Color(1, 1, 1, 0.6) + # U7: Farbe über Theme (Editor/colors/title) statt modulate-Wert im Code. + file_label.add_theme_color_override("font_color", + get_theme_color("title", "Editor")) # Karte t_297221b5: Map-Pfad darf bei großer Stufe egalisiert werden # (Voller Name im Tooltip, sonst drückt die Zeile die Top-Bar aus dem Fenster). # Expand: bekommt zusammen mit dem Spacer seinen Anteil, egalisiert mit "…". diff --git a/src/ui/resize_map_dialog.gd b/src/ui/resize_map_dialog.gd index 19c9fed..27a3d5b 100644 --- a/src/ui/resize_map_dialog.gd +++ b/src/ui/resize_map_dialog.gd @@ -43,6 +43,8 @@ func _init() -> void: var btn := Button.new() btn.text = "%d m" % size btn.custom_minimum_size = UIScale.v2(0, 40) + # U7: Klartext-Tooltips (Karte t_2336c58e). + btn.tooltip_text = "Preset: Karte quadratisch %d × %d m" % [size, size] btn.pressed.connect(_on_preset.bind(float(size))) preset_h.add_child(btn) vbox.add_child(preset_h) @@ -69,6 +71,8 @@ func _init() -> void: drop_check = CheckBox.new() drop_check.text = "Draußen liegende Objekte/Bauteile/Spawn-Punkte beim Verkleinern entfernen" drop_check.button_pressed = true + # U7: Klartext-Tooltip (Karte t_2336c58e). + drop_check.tooltip_text = "An = Dinge außerhalb der neuen Grenze fliegen raus (Strg+Z holt sie zurück).\nAus = sie bleiben, aber die Map ist dann NICHT speicherbar." drop_check.toggled.connect(func(_p: bool) -> void: _update_preview()) vbox.add_child(drop_check) diff --git a/src/ui/ui_scale.gd b/src/ui/ui_scale.gd index f0a83ff..75f5fdc 100644 --- a/src/ui/ui_scale.gd +++ b/src/ui/ui_scale.gd @@ -17,6 +17,12 @@ extends RefCounted ## Für Tests/Probes kann per Umgebungsvariable SCOPEDVR_UI_CFG eine andere ## Config-Datei erzwungen werden (hermetische Läufe ohne Nutzer-Config). +## Typografie-Ebenen (Karte t_2336c58e/U7 — verbindlich): genau ZWEI Schrift- +## Größen (Body + Klein) plus eine Überschriften-Stufe. KEINE weiteren Größen +## im Code — alles über die drei Helfer unten, skaliert mit der UI-Stufe. +const HEADING_FONT_SIZE := 20 # Überschriften (Panel-/Gruppen-Header, Titel) +const SMALL_FONT_SIZE := 13 # Nebentext (Zähler, Badges, Tile-IDs) + const CFG_PATH := "user://scopedvr_editor.cfg" const CFG_SECTION := "ui" const CFG_KEY := "groesse" # "klein" | "mittel" | "gross" @@ -115,6 +121,16 @@ static func v2i(x: float, y: float) -> Vector2i: return Vector2i(px(x), px(y)) -## Standardschriftgröße (bisher fest 16). +## Standardschriftgröße (Body-Text, bisher fest 16). static func font_size() -> int: return px(BASE_FONT_SIZE) + + +## Überschriften-Größe (Panel-/Gruppen-Header, Titel) — U7-Raster. +static func heading_font_size() -> int: + return px(HEADING_FONT_SIZE) + + +## Nebentext-Größe (Zähler, Badges, Tile-IDs) — U7-Raster. +static func small_font_size() -> int: + return px(SMALL_FONT_SIZE) diff --git a/tests/probe_u7_theme_render.gd b/tests/probe_u7_theme_render.gd new file mode 100644 index 0000000..859633f --- /dev/null +++ b/tests/probe_u7_theme_render.gd @@ -0,0 +1,61 @@ +extends SceneTree +## Beweis-Probe (Karte t_2336c58e, WEGWERF — nicht Teil der Suite): +## U7 Feinschliff — Screenshot der GANZEN Oberfläche (vorher/nachher je +## Baumzustand). Bibliothek „Alle auf" + erste Gruppe offen, damit die +## Kacheln im Bild sind. +## Aufruf: xvfb-run -a godot4 --path . --rendering-driver opengl3 \ +## -s res://tests/probe_u7_theme_render.gd (mit ../scopedvr-client) + +func _initialize() -> void: + _run.call_deferred() + + +func _shot(pfad: String) -> void: + await process_frame + await RenderingServer.frame_post_draw + var img := root.get_viewport().get_texture().get_image() + img.save_png(pfad) + print("Bild: %s (%dx%d)" % [pfad, img.get_width(), img.get_height()]) + + +## „Modelle fehlen"-AcceptDialog schließen (kommt erst nach 2 Frames von +## utils/modelle_status.gd — mehrfach probieren, mit Wartezeit dazwischen). +func _dismiss_dialogs() -> void: + for i in 6: + await process_frame + var stack: Array = [root] + while not stack.is_empty(): + var n: Node = stack.pop_back() + if n is AcceptDialog: + (n as AcceptDialog).hide() + n.queue_free() + stack.append_array(n.get_children()) + + +func _run() -> void: + var dir := OS.get_environment("U7_DIR") + if dir.is_empty(): + dir = "/tmp/u7_beweise" + DirAccess.make_dir_recursive_absolute(dir) + var name := "u7_ui_%s.png" % str(OS.get_environment("U7_MODUS")) + var scene: PackedScene = load("res://scenes/main.tscn") + var main = scene.instantiate() + root.add_child(main) + await process_frame + await process_frame + await _dismiss_dialogs() + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) + DisplayServer.window_set_size(Vector2i(2328, 1310)) + await process_frame + await process_frame + # Bibliothek sichtbar füllen: „Alle auf" (der Klick feuert toggled an den + # Gruppen-Headern → Körper sichtbar; der cfg-Schrieb läuft in eine + # hermetische HOME-Umgebung, siehe Startkommando). + var open_btn: Button = main.library_open_all_btn + if open_btn != null: + open_btn.pressed.emit() + await process_frame + await process_frame + await _shot(dir.path_join(name)) + print("PROBE_U7_OK dir=%s" % dir) + quit(0) diff --git a/tests/probe_u7_theme_render.gd.uid b/tests/probe_u7_theme_render.gd.uid new file mode 100644 index 0000000..24ae615 --- /dev/null +++ b/tests/probe_u7_theme_render.gd.uid @@ -0,0 +1 @@ +uid://d178qhns0kc2s diff --git a/tests/test_theme.gd b/tests/test_theme.gd new file mode 100644 index 0000000..4e60b70 --- /dev/null +++ b/tests/test_theme.gd @@ -0,0 +1,203 @@ +extends SceneTree +## Editor-Test (headless): Theme-Konsistenz — Karte t_2336c58e (U7 Feinschliff). +## Ausführen: godot --headless --path . -s res://tests/test_theme.gd +## Exit 0 = alles grün. +## +## Was hier geprüft wird: +## 1. Theme-Ressource: main nutzt theme/editor_theme.tres (EINE Farbquelle), +## Theme-Schrift = UIScale.font_size(). +## 2. Theme-Inhalt: Hover/Fokus/Disabled (Button), Tab-Hover, LineEdit, +## Splitter-Anfassen (grabber_area), Scrollbars, Tooltip + die Farb-Token +## (accent, status_ok/error, muted, field, title, warning). +## 3. KEINE hartkodierten Farben im Code: Suche nach "Color(" in +## src/ui/panels/*.gd, src/ui/main.gd, src/ui/inspector_panel.gd. +## 4. Alle Knöpfe (Button/MenuButton/CheckBox) haben einen Tooltip. +## 5. Typografie-Raster: Überschrift > Body > Nebentext, alles über UIScale. +## 6. Fenstertitel zeigt ungespeicherte Änderungen an (U7 Kleinigkeit). + +const UIScale := preload("res://src/ui/ui_scale.gd") + +var failures := 0 +var checks := 0 +var main: Control + + +func _initialize() -> void: + _run_all.call_deferred() + + +func _check(cond: bool, label: String) -> void: + checks += 1 + if cond: + print(" ok: %s" % label) + else: + failures += 1 + print(" FAIL: %s" % label) + + +func _finish() -> void: + if failures == 0: + print("ALL OK: %d checks" % checks) + quit(0) + else: + print("FAILED: %d/%d checks" % [failures, checks]) + quit(1) + + +func _run_all() -> void: + var scene: PackedScene = load("res://scenes/main.tscn") + _check(scene != null, "scenes/main.tscn laedt") + if scene == null: + _finish() + return + main = scene.instantiate() + root.add_child(main) + # Zwei Frames: _ready + Layout laufen lassen (auch deferred UI-Arbeit). + await process_frame + await process_frame + + _test_theme_set() + _test_theme_content() + _test_no_hardcoded_colors() + _test_tooltips() + _test_typography() + _test_window_title_dirty() + + _finish() + + +## 1) main hängt genau die Theme-Datei aus dem Projekt an (eine Quelle). +func _test_theme_set() -> void: + print("Testgruppe: Theme-Ressource gesetzt") + _check(main.theme != null, "main.theme gesetzt") + if main.theme == null: + return + var theme_file: Theme = load("res://theme/editor_theme.tres") + _check(main.theme == theme_file, + "main nutzt theme/editor_theme.tres (eine Farbquelle)") + _check(int(main.theme.default_font_size) == UIScale.font_size(), + "Theme-Schrift = UIScale.font_size() (%d)" % UIScale.font_size()) + + +## 2) Die U7-Theme-Bausteine sind alle da (Hover/Fokus/Disabled, Splitter, +## Scrollbars, Tooltip, Farb-Token). +func _test_theme_content() -> void: + print("Testgruppe: Theme-Inhalt (Hover/Fokus/Disabled/Splitter/Farben)") + var th: Theme = main.theme + _check(th.has_stylebox("disabled", "Button"), "Button: Disabled-Style (ausgegraut)") + _check(th.has_stylebox("hover", "Button"), "Button: Hover-Style") + _check(th.has_stylebox("pressed", "Button"), "Button: Aktiv-Style") + _check(th.has_stylebox("focus", "Button"), "Button: Fokus-Rahmen") + _check(th.has_color("font_disabled_color", "Button"), + "Button: Disabled-Schriftfarbe (sichtbar ausgegraut)") + _check(th.has_color("font_placeholder_color", "LineEdit"), + "LineEdit: Platzhalter-Farbe") + _check(th.has_stylebox("focus", "LineEdit"), "LineEdit: Fokus-Rahmen") + _check(th.has_stylebox("tab_hovered", "TabContainer"), "Tabs: Hover-Zustand") + _check(th.has_stylebox("grabber_area", "HSplitContainer"), + "Splitter: Anfassen-Fläche (HSplit)") + _check(th.has_stylebox("grabber_area", "VSplitContainer"), + "Splitter: Anfassen-Fläche (VSplit)") + _check(th.has_stylebox("grabber_area_highlight", "HSplitContainer"), + "Splitter: Anfassen-Hervorhebung (HSplit)") + _check(th.has_stylebox("grabber", "VScrollBar"), "ScrollBar: Griff (V)") + _check(th.has_stylebox("grabber", "HScrollBar"), "ScrollBar: Griff (H)") + _check(th.has_stylebox("panel", "TooltipPanel"), "Tooltip: Panel-Style") + _check(th.has_color("accent", "Editor"), "Farb-Token: accent") + _check(th.has_color("status_ok", "Editor"), "Farb-Token: status_ok") + _check(th.has_color("status_error", "Editor"), "Farb-Token: status_error") + _check(th.has_color("muted", "Editor"), "Farb-Token: muted") + _check(th.has_color("field", "Editor"), "Farb-Token: field") + _check(th.has_color("title", "Editor"), "Farb-Token: title") + _check(th.has_color("warning", "Editor"), "Farb-Token: warning") + + +## 3) Keine Farbwerte mehr im Code — alles kommt aus dem Theme. +## Scan: src/ui/panels/*.gd (verbindlich) + main.gd + inspector_panel.gd. +## Achtung: nur "Color(" (Konstruktor) zählt — theme_color()/-> Color: sind +## Schlüsselwörter, keine Farbwerte. +func _test_no_hardcoded_colors() -> void: + print("Testgruppe: keine hartkodierten Farben im Code") + var dateien: Array = [] + var dir := DirAccess.open("res://src/ui/panels") + if dir != null: + dir.list_dir_begin() + var fname := dir.get_next() + while not fname.is_empty(): + if fname.ends_with(".gd"): + dateien.append("res://src/ui/panels/" + fname) + fname = dir.get_next() + dateien.append("res://src/ui/main.gd") + dateien.append("res://src/ui/inspector_panel.gd") + _check(dateien.size() > 5, "Farb-Scan: %d Skripte gefunden" % dateien.size()) + var farb_dateien := 0 + for pfad in dateien: + var text := FileAccess.get_file_as_string(pfad) + if text.contains("Color("): + farb_dateien += 1 + print(" hartkodierte Farbe in: %s" % pfad) + _check(farb_dateien == 0, + "kein 'Color(' in den Panel-Skripten/main.gd/Inspektor") + + +## 4) Jeder klickbare Knopf (Button/MenuButton/CheckBox/CheckButton) im +## gebauten UI trägt einen Tooltip — Werkzeugname/Tastenkürzel inklusive. +func _test_tooltips() -> void: + print("Testgruppe: alle Knöpfe haben Tooltips") + var ohne := 0 + var mit := 0 + var stack: Array = [main] + while not stack.is_empty(): + var n: Node = stack.pop_back() + if n is Button or n is MenuButton: + var str_text := str((n as Control).tooltip_text) + if str_text.strip_edges().is_empty(): + ohne += 1 + var ort := str(n.get_parent().name) + var elter := n.get_parent() + while elter != null and elter != main: + elter = elter.get_parent() + if elter != null: + ort = str(elter.name) + "/" + ort + print(" ohne Tooltip: %s (%s) text='%s' pfad=%s" % [ + n.name, n.get_class(), + str((n as Control).get("text")), ort]) + else: + mit += 1 + for c in n.get_children(): + stack.append(c) + _check(mit > 20, "Knöpfe mit Tooltip gefunden (%d)" % mit) + _check(ohne == 0, "kein Knopf ohne Tooltip (%d ohne)" % ohne) + + +## 5) Genau drei Typografie-Ebenen über UIScale: Überschrift > Body > Nebentext. +func _test_typography() -> void: + print("Testgruppe: Typografie-Raster über UIScale") + _check(UIScale.heading_font_size() > UIScale.font_size(), + "Überschrift (%d) größer als Body (%d)" % [ + UIScale.heading_font_size(), UIScale.font_size()]) + _check(UIScale.font_size() > UIScale.small_font_size(), + "Body (%d) größer als Nebentext (%d)" % [ + UIScale.font_size(), UIScale.small_font_size()]) + # Stufen-Skalierung wandert mit (Groß-Stufe, ohne UI-Rebuild zu brauchen): + UIScale.set_step_index(2) + _check(UIScale.heading_font_size() == UIScale.px(UIScale.HEADING_FONT_SIZE), + "Überschrift skaliert mit der UI-Stufe (Groß)") + _check(UIScale.small_font_size() == UIScale.px(UIScale.SMALL_FONT_SIZE), + "Nebentext skaliert mit der UI-Stufe (Groß)") + UIScale.set_step_index(0) # zurück auf Klein (wie der Test-Start) + + +## 6) Fenstertitel nennt ungespeicherte Änderungen (U7-Kleinigkeit). +func _test_window_title_dirty() -> void: + print("Testgruppe: Fenstertitel bei ungespeicherten Änderungen") + main.dirty = true + main.call("_refresh_file_label") + var titel_dirty := str(root.title) + _check(titel_dirty.contains("ungespeichert"), + "Titel bei dirty nennt „ungespeichert“ (%s)" % titel_dirty) + main.dirty = false + main.call("_refresh_file_label") + var titel_sauber := str(root.title) + _check(not titel_sauber.contains("ungespeichert"), + "Titel ohne dirty bleibt sauber (%s)" % titel_sauber) diff --git a/tests/test_theme.gd.uid b/tests/test_theme.gd.uid new file mode 100644 index 0000000..4e0db1a --- /dev/null +++ b/tests/test_theme.gd.uid @@ -0,0 +1 @@ +uid://3lhyjrmnyfew diff --git a/theme/editor_theme.tres b/theme/editor_theme.tres index 8295b38..47190e8 100644 --- a/theme/editor_theme.tres +++ b/theme/editor_theme.tres @@ -1,4 +1,4 @@ -[gd_resource type="Theme" load_steps=10 format=3] +[gd_resource type="Theme" load_steps=23 format=3] [sub_resource type="StyleBoxFlat" id="panel"] bg_color = Color(0.12, 0.14, 0.17, 1) @@ -23,6 +23,17 @@ content_margin_right = 10.0 content_margin_top = 4.0 content_margin_bottom = 4.0 +[sub_resource type="StyleBoxFlat" id="tab_hover"] +bg_color = Color(0.19, 0.24, 0.29, 1) +border_color = Color(0.35, 0.55, 0.7, 0.4) +border_width_top = 2 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +content_margin_left = 10.0 +content_margin_right = 10.0 +content_margin_top = 4.0 +content_margin_bottom = 4.0 + [sub_resource type="StyleBoxFlat" id="tab_unsel"] bg_color = Color(0.15, 0.18, 0.21, 1) corner_radius_top_left = 4 @@ -77,6 +88,60 @@ corner_radius_bottom_left = 4 content_margin_left = 8.0 content_margin_right = 8.0 +[sub_resource type="StyleBoxFlat" id="btn_disabled"] +bg_color = Color(0.16, 0.17, 0.19, 1) +border_color = Color(0.24, 0.26, 0.29, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="btn_focus"] +draw_center = false +border_color = Color(0.55, 0.8, 1, 0.85) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="line_normal"] +bg_color = Color(0.09, 0.11, 0.13, 1) +border_color = Color(0.3, 0.36, 0.42, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + +[sub_resource type="StyleBoxFlat" id="line_focus"] +bg_color = Color(0.1, 0.13, 0.15, 1) +border_color = Color(0.55, 0.8, 1, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 +content_margin_left = 8.0 +content_margin_right = 8.0 + [sub_resource type="StyleBoxFlat" id="tooltip"] bg_color = Color(0.08, 0.09, 0.11, 0.96) border_color = Color(0.35, 0.55, 0.7, 1) @@ -93,7 +158,64 @@ content_margin_right = 6.0 content_margin_top = 4.0 content_margin_bottom = 4.0 -[sub_resource type="StyleBoxEmpty" id="focus_empty"] +[sub_resource type="StyleBoxFlat" id="split_grab"] +bg_color = Color(0.22, 0.27, 0.32, 1) +corner_radius_top_left = 2 +corner_radius_top_right = 2 +corner_radius_bottom_right = 2 +corner_radius_bottom_left = 2 + +[sub_resource type="StyleBoxFlat" id="split_grab_hover"] +bg_color = Color(0.35, 0.55, 0.7, 0.6) +corner_radius_top_left = 2 +corner_radius_top_right = 2 +corner_radius_bottom_right = 2 +corner_radius_bottom_left = 2 + +[sub_resource type="StyleBoxFlat" id="scroll_bg"] +bg_color = Color(0.1, 0.12, 0.14, 0.4) + +[sub_resource type="StyleBoxFlat" id="scroll_grab"] +bg_color = Color(0.42, 0.49, 0.56, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="scroll_grab_hover"] +bg_color = Color(0.55, 0.63, 0.7, 0.8) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="scroll_grab_press"] +bg_color = Color(0.55, 0.8, 1, 0.85) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="StyleBoxFlat" id="progress_bg"] +bg_color = Color(0.09, 0.11, 0.13, 1) +border_color = Color(0.3, 0.36, 0.42, 1) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="progress_fill"] +bg_color = Color(0.55, 0.8, 1, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxEmpty" id="scroll_empty"] [resource] default_font_size = 16 @@ -103,6 +225,8 @@ Editor/colors/status_error = Color(1, 0.6, 0.5, 1) Editor/colors/muted = Color(1, 1, 1, 0.55) Editor/colors/field = Color(1, 1, 1, 0.7) Editor/colors/title = Color(1, 1, 1, 0.6) +Editor/colors/warning = Color(1, 0.62, 0.3, 1) +Editor/colors/badge_outline = Color(0, 0, 0, 1) Editor/colors/help_bg = Color(0.09, 0.11, 0.13, 0.97) Editor/colors/help_border = Color(0.35, 0.55, 0.7, 1) Editor/colors/update_bg = Color(0.36, 0.25, 0.06, 0.97) @@ -118,6 +242,7 @@ Editor/constants/selection_outline_width_partner_mm = 45 PanelContainer/styles/panel = SubResource("panel") TabContainer/styles/panel = SubResource("panel") TabContainer/styles/tab_selected = SubResource("tab_sel") +TabContainer/styles/tab_hovered = SubResource("tab_hover") TabContainer/styles/tab_unselected = SubResource("tab_unsel") TabContainer/styles/tabbar_background = SubResource("tabbar") TabContainer/colors/font_selected_color = Color(1, 1, 1, 1) @@ -125,6 +250,32 @@ TabContainer/colors/font_unselected_color = Color(1, 1, 1, 0.7) Button/styles/normal = SubResource("btn_normal") Button/styles/hover = SubResource("btn_hover") Button/styles/pressed = SubResource("btn_pressed") -Button/styles/focus = SubResource("focus_empty") +Button/styles/disabled = SubResource("btn_disabled") +Button/styles/focus = SubResource("btn_focus") +Button/colors/font_hover_color = Color(1, 1, 1, 1) +Button/colors/font_pressed_color = Color(0.8, 0.9, 1, 1) +Button/colors/font_disabled_color = Color(1, 1, 1, 0.35) +LineEdit/styles/normal = SubResource("line_normal") +LineEdit/styles/focus = SubResource("line_focus") +LineEdit/colors/font_color = Color(1, 1, 1, 1) +LineEdit/colors/font_placeholder_color = Color(1, 1, 1, 0.4) +HSplitContainer/styles/grabber_area = SubResource("split_grab") +HSplitContainer/styles/grabber_area_highlight = SubResource("split_grab_hover") +VSplitContainer/styles/grabber_area = SubResource("split_grab") +VSplitContainer/styles/grabber_area_highlight = SubResource("split_grab_hover") +ScrollContainer/styles/panel = SubResource("scroll_empty") +VScrollBar/styles/scroll = SubResource("scroll_bg") +VScrollBar/styles/grabber = SubResource("scroll_grab") +VScrollBar/styles/grabber_highlight = SubResource("scroll_grab_hover") +VScrollBar/styles/grabber_pressed = SubResource("scroll_grab_press") +HScrollBar/styles/scroll = SubResource("scroll_bg") +HScrollBar/styles/grabber = SubResource("scroll_grab") +HScrollBar/styles/grabber_highlight = SubResource("scroll_grab_hover") +HScrollBar/styles/grabber_pressed = SubResource("scroll_grab_press") +ProgressBar/styles/background = SubResource("progress_bg") +ProgressBar/styles/fill = SubResource("progress_fill") +ProgressBar/colors/font_color = Color(1, 1, 1, 1) +HSlider/styles/grabber_area = SubResource("progress_fill") +HSlider/styles/grabber_area_highlight = SubResource("progress_fill") TooltipPanel/styles/panel = SubResource("tooltip") TooltipLabel/colors/font_color = Color(1, 1, 1, 1)