U7 (t_2336c58e): Feinschliff — Theme konsequent (Disabled/Fokus/Hover, LineEdit, Tab-Hover, Splitter-Anfassen, Scrollbars, Farb-Token warning/badge_outline), Abstands-Raster 4/8 über UIScale, Typografie-Raster (heading/body/small via ui_scale.gd), Tooltips für alle Knöpfe inkl. Dialoge, Leerzustand in der Bibliothek, Fenstertitel mit „(ungespeichert)“. Neue Suite tests/test_theme.gd (35 Checks). Keine Funktionsänderung.
This commit is contained in:
parent
0e1406a63e
commit
f6c754115a
18 changed files with 574 additions and 68 deletions
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 "…".
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
61
tests/probe_u7_theme_render.gd
Normal file
61
tests/probe_u7_theme_render.gd
Normal file
|
|
@ -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)
|
||||
1
tests/probe_u7_theme_render.gd.uid
Normal file
1
tests/probe_u7_theme_render.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d178qhns0kc2s
|
||||
203
tests/test_theme.gd
Normal file
203
tests/test_theme.gd
Normal file
|
|
@ -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)
|
||||
1
tests/test_theme.gd.uid
Normal file
1
tests/test_theme.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://3lhyjrmnyfew
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue