U4 t_69687afc: Assets-Bereich Unity-artig ausgebaut — Kategorien-Leiste (Alle + Gruppen aus LibraryBrowser, Summe = Gesamtzahl), Suche Name+ID, Zaehler "N von M sichtbar", Zuletzt benutzt (cfg [library] zuletzt_benutzt, Chip + Ansicht), Kacheln mit 96-px-Vorschau + ID + Name, Hover vergroessert die ID, fehlende GLB = abgedunkeltes Bild + orange Badge "Modell fehlt" (GlbMeshStore.fehlt), Klick = auswaehlen (Signal object_selected genau 1x je Wechsel), Doppelklick = Werkzeug starten wie bisher (nochmal = abbrechen), Spaltenzahl breitenabhaengig (180 px je Kachel, ueber ui_scale), Dock-Groesse bleibt in [fenster] gemerkt (U1). Klapp-Gruppen + Alle auf/zu + gepinnte Masse (Suchfeld 36, Kachel 126, Thumb 96) unveraendert. Suite tests/test_assets_panel.gd 75 checks ALL OK; test_library_browser_ui 30, test_library 101, test_ui_layout 63, test_gui_smoke 92, test_inspector 49, test_playtest 11, test_object_mirror 74, test_ui_scale OK — alle unveraendert gruen. Renderproben unter /opt/data/scopedvr/work/u4_assets_beweise/. test_playtest_spawn ist VOR dieser Karte rot (U1-Regression _test_start_reload, siehe Kommentar auf t_eaccbfed) — nicht von U4 beruehrt.
This commit is contained in:
parent
69ffe23a71
commit
8f0e011a07
3 changed files with 788 additions and 48 deletions
|
|
@ -1,13 +1,30 @@
|
|||
extends "res://src/ui/panels/editor_panel.gd"
|
||||
## Objekt-Bibliothek / Assets-Bereich (aus main.gd ausgezogen, Karte t_eaccbfed
|
||||
## U1 — Logik unverändert, Karten t_3a998ae4/t_59fc0f21): Suche, Klapp-Gruppen,
|
||||
## Kacheln. Der Bereich wohnt jetzt UNTEN unter der 3D-Ansicht (Unity-
|
||||
## Projekt-Fenster-Platz); Klick verhält sich noch wie vorher (U5 ändert das).
|
||||
## Objekt-Bibliothek / Assets-Bereich (aus main.gd ausgezogen, Karte t_eaccbfed;
|
||||
## Unity-artiger Ausbau Karte t_69687afc/U4): Kategorien-Leiste, Suche, Zuletzt
|
||||
## benutzt, Vorschau-Kacheln mit Name + ID, Hover vergrößert die ID, fehlende
|
||||
## Modelle werden markiert (nicht stillschweigend), Klick wählt aus,
|
||||
## Doppelklick startet das Platzier-Werkzeug wie bisher.
|
||||
## Der Bereich wohnt UNTEN unter der 3D-Ansicht (Unity-Projekt-Fenster-Platz).
|
||||
##
|
||||
## Kompatibilität (main.gd bindet diese Felder — NICHT umbenennen):
|
||||
## library_search, library_buttons, _library_group_rows,
|
||||
## library_open_all_btn, library_close_all_btn,
|
||||
## _on_library_pressed(), _apply_library_filter(),
|
||||
## _on_library_expand_all(), _on_library_collapse_all(), highlight_library().
|
||||
## Die Klapp-Gruppen bleiben als Körper der „Alle"-Ansicht erhalten; die
|
||||
## Kategorien-Leiste filtert dieselben Zeilen (Mechanik wie die Suche).
|
||||
|
||||
signal object_selected(oid: String) # Klick auf eine Kachel (nur bei Wechsel)
|
||||
signal object_activated(oid: String) # Doppelklick — Werkzeug-Start (U5 koppelt)
|
||||
|
||||
const LIBRARY_CFG_SECTION := "library" # Abschnitt in user://scopedvr_editor.cfg
|
||||
const RECENT_CFG_KEY := "zuletzt_benutzt" # selbiger Abschnitt, Liste von IDs
|
||||
const RECENT_MAX := 12 # gemerkte Objekte (neueste zuerst)
|
||||
const CAT_ALLE := "alle" # Pseudo-Kategorie: alles
|
||||
const CAT_ZULETZT := "zuletzt" # Pseudo-Kategorie: zuletzt benutzt
|
||||
|
||||
var library_box: VBoxContainer
|
||||
var library_buttons: Dictionary = {} # id → Button
|
||||
var library_buttons: Dictionary = {} # id → Button (Haupt-Grid, kanonisch)
|
||||
## Bibliothek-Gruppen (t_59fc0f21): Klapp-Zustand + Zeilen für Suche/Alle auf/zu.
|
||||
var library_search: LineEdit
|
||||
var library_open_all_btn: Button
|
||||
|
|
@ -16,6 +33,18 @@ var _library_group_open: Dictionary = {} # Gruppen-Key → auf/zu
|
|||
var _library_group_rows: Array = [] # {key, header, box, ids}
|
||||
var _library_bold_font: Font = null
|
||||
|
||||
# --- U4 neu: Kategorien, Zähler, Zuletzt benutzt, Auswahl -------------------
|
||||
var library_counter: Label # „N von M sichtbar"
|
||||
var _cat_chips: HFlowContainer # Kategorien-Leiste
|
||||
var _cat_buttons: Dictionary = {} # Key → Chip-Button
|
||||
var _cat_active: String = CAT_ALLE # aktive Kategorie (Chip)
|
||||
var _recent_box: VBoxContainer # Zuletzt-benutzt-Ansicht (im Scroll)
|
||||
var _recent_grid: GridContainer
|
||||
var _recent_tiles: Dictionary = {} # id → Button (Zuletzt-Ansicht, frisch gebaut)
|
||||
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
|
||||
|
||||
|
||||
static func spec() -> Dictionary:
|
||||
return {"titel": "Objekt-Bibliothek", "gruppe": "unten", "order": 10}
|
||||
|
|
@ -25,16 +54,15 @@ func panel_title() -> String:
|
|||
return "Objekt-Bibliothek"
|
||||
|
||||
|
||||
## Bau (aus der alten main.gd/_build_left_panel übernommen, Logik identisch):
|
||||
## Suche → „Alle auf/zu“ → Kachel-Liste (library_box) → _fill_library().
|
||||
## Der Bereich wohnt jetzt UNTEN (Registry-Gruppe "unten"); Klick verhält sich
|
||||
## noch wie vorher (U5 ändert das Verhalten).
|
||||
## Bau: Überschrift → Suche → Kategorien-Leiste → Alle auf/zu → Zähler →
|
||||
## Klapp-Gruppen-Körper („Alle") + Zuletzt-benutzt-Ansicht.
|
||||
func build() -> void:
|
||||
add_child(_header_label("Objekt-Bibliothek"))
|
||||
add_child(_muted_label("Objekt anklicken, dann in die Karte klicken — der Modus bleibt an bis ESC (Rechte Maustaste = Flug)."))
|
||||
add_child(_muted_label("Klick = auswählen, Doppelklick = Werkzeug starten. Ohne GLB wird der Platzhalter gesetzt und die Kachel markiert (Rechte Maustaste = Flug)."))
|
||||
|
||||
# Suche (t_59fc0f21): tippen filtert live nach Name/ID — Gruppen mit
|
||||
# Treffern klappen auf, leere Gruppen verschwinden.
|
||||
# Treffern klappen auf, leere Gruppen verschwinden. Größe ist von
|
||||
# test_ui_scale gepinnt (36 → 45 bei „Mittel") — nicht ändern.
|
||||
library_search = LineEdit.new()
|
||||
library_search.placeholder_text = "Suchen (Name oder ID)…"
|
||||
library_search.clear_button_enabled = true
|
||||
|
|
@ -43,6 +71,8 @@ func build() -> void:
|
|||
library_search.text_submitted.connect(func(_t: String) -> void: library_search.release_focus())
|
||||
add_child(library_search)
|
||||
|
||||
_build_category_chips()
|
||||
|
||||
# „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))
|
||||
|
|
@ -60,6 +90,14 @@ func build() -> void:
|
|||
group_btns.add_child(library_close_all_btn)
|
||||
add_child(group_btns)
|
||||
|
||||
# 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)
|
||||
library_counter.tooltip_text = "Sichtbare Kacheln im aktiven Bereich"
|
||||
add_child(library_counter)
|
||||
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
scroll.custom_minimum_size = UIScale.v2(0, 280)
|
||||
|
|
@ -69,9 +107,101 @@ 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)
|
||||
_recent_box = VBoxContainer.new()
|
||||
_recent_box.visible = false
|
||||
_recent_box.add_theme_constant_override("separation", UIScale.px(4))
|
||||
library_box.add_child(_recent_box)
|
||||
_fill_library()
|
||||
_recent_ids = _recent_load()
|
||||
_update_recent_chip()
|
||||
_apply_library_filter(library_search.text)
|
||||
|
||||
|
||||
# ------------------------------------------------------------ Kategorien ----
|
||||
|
||||
## Kategorien-Leiste (U4): „Alle" + eine Chip je Bibliotheks-Gruppe
|
||||
## (LibraryBrowser.groups_in_order — KEINE eigene Kategorieliste) +
|
||||
## „Zuletzt benutzt". Chips zeigen Name + Anzahl; Summe der Gruppen-Chips =
|
||||
## Gesamtzahl (test_assets_panel rechnet nach).
|
||||
func _build_category_chips() -> void:
|
||||
_cat_chips = HFlowContainer.new()
|
||||
_cat_chips.add_theme_constant_override("h_separation", UIScale.px(4))
|
||||
_cat_chips.add_theme_constant_override("v_separation", UIScale.px(4))
|
||||
add_child(_cat_chips)
|
||||
var bg := ButtonGroup.new()
|
||||
var groups := LibraryBrowser.groups_in_order(main.manifest)
|
||||
_cat_add_chip(CAT_ALLE, "Alle", _manifest_count(), bg)
|
||||
for group in groups:
|
||||
var g_key: String = group["key"]
|
||||
var g_ids: Array = group["ids"]
|
||||
if g_ids.is_empty():
|
||||
continue
|
||||
_cat_add_chip(g_key, str(group["label"]), g_ids.size(), bg)
|
||||
_cat_add_chip(CAT_ZULETZT, "Zuletzt benutzt", 0, bg)
|
||||
(_cat_buttons[CAT_ZULETZT] as Button).visible = false # taucht auf, sobald benutzt
|
||||
|
||||
|
||||
func _manifest_count() -> int:
|
||||
return (main.manifest.get("objects", {}) as Dictionary).size()
|
||||
|
||||
|
||||
func _cat_add_chip(key: String, label: String, count: int, bg: ButtonGroup) -> void:
|
||||
var chip := Button.new()
|
||||
chip.toggle_mode = true
|
||||
chip.button_group = bg
|
||||
chip.button_pressed = key == _cat_active
|
||||
chip.text = "%s (%d)" % [label, count] if key != CAT_ZULETZT else label
|
||||
chip.custom_minimum_size = UIScale.v2(0, 28)
|
||||
chip.tooltip_text = "Zeigt nur die Gruppe „%s“" % label if key != CAT_ALLE and key != CAT_ZULETZT else "Zeigt alle Objekte" if key == CAT_ALLE else "Die zuletzt ausgewählten/benutzten Objekte"
|
||||
chip.set_meta("cat_key", key)
|
||||
chip.set_meta("cat_count", count)
|
||||
chip.toggled.connect(_on_cat_chip_toggled.bind(key))
|
||||
_cat_chips.add_child(chip)
|
||||
_cat_buttons[key] = chip
|
||||
|
||||
|
||||
func _on_cat_chip_toggled(on: bool, key: String) -> void:
|
||||
if not on:
|
||||
return # Radio-Gruppe: der neue Chip drückt sich selbst an
|
||||
_select_category(key)
|
||||
|
||||
|
||||
## Kategorie wechseln: Zustand merken (kein Config-Zwang — Chips sind
|
||||
## Ansichtsfilter, der Klapp-Zustand je Gruppe bleibt in der cfg).
|
||||
func _select_category(key: String) -> void:
|
||||
if key != CAT_ZULETZT and not _cat_buttons.has(key):
|
||||
return
|
||||
_cat_active = key
|
||||
for chip_key in _cat_buttons:
|
||||
var chip := _cat_buttons[chip_key] as Button
|
||||
chip.set_pressed_no_signal(str(chip_key) == _cat_active)
|
||||
_apply_library_filter(library_search.text)
|
||||
|
||||
|
||||
func _update_recent_chip() -> void:
|
||||
var chip := _cat_buttons.get(CAT_ZULETZT) as Button
|
||||
if chip == null:
|
||||
return
|
||||
chip.visible = not _recent_ids.is_empty()
|
||||
chip.text = "Zuletzt benutzt (%d)" % _recent_ids.size()
|
||||
|
||||
|
||||
## Objekte des aktiven Bereichs (M im Zähler): Alle → Gesamtkatalog,
|
||||
## Kategorie → deren IDs, Zuletzt → gemerkte Liste.
|
||||
func _scope_ids() -> Array:
|
||||
var lib: Dictionary = main.manifest.get("objects", {})
|
||||
if _cat_active == CAT_ALLE:
|
||||
return lib.keys()
|
||||
if _cat_active == CAT_ZULETZT:
|
||||
return _recent_ids.duplicate()
|
||||
for row in _library_group_rows:
|
||||
if str(row["key"]) == _cat_active:
|
||||
return (row["ids"] as Array).duplicate()
|
||||
return []
|
||||
|
||||
|
||||
# -------------------------------------------------------- Klapp-Gruppen ----
|
||||
|
||||
func _fill_library() -> void:
|
||||
library_buttons.clear()
|
||||
_library_group_rows.clear()
|
||||
|
|
@ -81,6 +211,7 @@ func _fill_library() -> void:
|
|||
# - STANDARD EINGEKLAPPT — nur die erste Gruppe ist offen (Übersicht!),
|
||||
# - Klapp-Zustand je Gruppe wird in user://scopedvr_editor.cfg gemerkt,
|
||||
# - Objekte als Kacheln mit 96-px-Vorschaubild (2 Spalten je Gruppe).
|
||||
# U4: der Körper bleibt („Alle“-Ansicht), die Kategorien-Leiste filtert ihn.
|
||||
var first_open: String = ""
|
||||
for group in LibraryBrowser.groups_in_order(main.manifest):
|
||||
var g_key: String = group["key"]
|
||||
|
|
@ -115,7 +246,41 @@ func _fill_library() -> void:
|
|||
header.toggled.connect(_on_library_group_toggled.bind(g_key, g_label, g_ids.size(), header, group_box))
|
||||
for oid in g_ids:
|
||||
_add_library_button(str(oid), grid)
|
||||
_library_group_rows.append({"key": g_key, "header": header, "box": group_box, "ids": g_ids, "label": g_label, "count": g_ids.size()})
|
||||
_library_group_rows.append({"key": g_key, "header": header, "box": group_box, "ids": g_ids, "label": g_label, "count": g_ids.size(), "grid": grid})
|
||||
if not resized.is_connected(_on_panel_resized):
|
||||
resized.connect(_on_panel_resized)
|
||||
_on_panel_resized()
|
||||
|
||||
|
||||
## Unity-Projekt-Fenster-Feeling (U4): die Spaltenzahl passt sich der BREITE
|
||||
## des unteren Bereichs an (schmaler Dock = wenige Spalten, breiter = viele).
|
||||
## Kachel-Zielbreite ≈ 180 px (skaliert über ui_scale).
|
||||
func _on_panel_resized() -> void:
|
||||
_apply_grid_columns(size.x)
|
||||
|
||||
|
||||
func _apply_grid_columns(width: float) -> void:
|
||||
var cols := clampi(int(width / float(UIScale.px(180))), 1, 12)
|
||||
for row in _library_group_rows:
|
||||
((row["grid"] as GridContainer)).columns = cols
|
||||
if _recent_grid != null:
|
||||
_recent_grid.columns = cols
|
||||
|
||||
|
||||
## Kompletter Neubau des Bibliotheks-Körpers (Probe/Umbau): alte Zeilen frei
|
||||
## geben, dann frisch füllen und den Filter anwenden. Die Zuletzt-Ansicht
|
||||
## (_recent_box) überlebt den Neubau — sie wird nur aus dem Baum genommen und
|
||||
## wieder eingehängt, damit die Referenz NICHT auf ein freigegebenes Objekt
|
||||
## zeigt (sonst „previously freed" im nächsten Filterlauf).
|
||||
func _rebuild_library() -> void:
|
||||
if _recent_box != null and _recent_box.get_parent() == library_box:
|
||||
library_box.remove_child(_recent_box)
|
||||
for c in library_box.get_children():
|
||||
library_box.remove_child(c)
|
||||
(c as Node).free()
|
||||
if _recent_box != null and _recent_box.get_parent() == null:
|
||||
library_box.add_child(_recent_box)
|
||||
_fill_library()
|
||||
_apply_library_filter(library_search.text)
|
||||
|
||||
|
||||
|
|
@ -132,12 +297,18 @@ func _on_library_group_toggled(on: bool, g_key: String, g_label: String, count:
|
|||
|
||||
func _on_library_expand_all() -> void:
|
||||
for row in _library_group_rows:
|
||||
(row["header"] as Button).button_pressed = true
|
||||
var header := row["header"] as Button
|
||||
header.button_pressed = true
|
||||
# toggled feuert → box sichtbar; Filter danach neu anwenden, damit
|
||||
# ein aktiver Kategorie-Filter/Suche nicht weggeblasen wird.
|
||||
_apply_library_filter(library_search.text)
|
||||
|
||||
|
||||
func _on_library_collapse_all() -> void:
|
||||
for row in _library_group_rows:
|
||||
(row["header"] as Button).button_pressed = false
|
||||
var header := row["header"] as Button
|
||||
header.button_pressed = false
|
||||
_apply_library_filter(library_search.text)
|
||||
|
||||
|
||||
func _library_header_font() -> Font:
|
||||
|
|
@ -155,6 +326,8 @@ func _library_load_open_states() -> Dictionary:
|
|||
var out := {}
|
||||
if cfg.has_section(LIBRARY_CFG_SECTION):
|
||||
for key in cfg.get_section_keys(LIBRARY_CFG_SECTION):
|
||||
if str(key) == RECENT_CFG_KEY:
|
||||
continue
|
||||
out[str(key)] = bool(cfg.get_value(LIBRARY_CFG_SECTION, str(key), true))
|
||||
return out
|
||||
|
||||
|
|
@ -166,12 +339,20 @@ func _library_save_open_state(g_key: String, on: bool) -> void:
|
|||
cfg.save("user://scopedvr_editor.cfg")
|
||||
|
||||
|
||||
## Live-Suche (t_59fc0f21): leer = alles normal sichtbar (Klapp-Zustand wie
|
||||
## gemerkt); Text = nur Treffer (Name de/en, ID — case-insensitive), Gruppen
|
||||
## mit Treffern bleiben offen, leere verschwinden.
|
||||
# ----------------------------------------------------------------- Suche ----
|
||||
|
||||
## Live-Filter (t_59fc0f21, U4 erweitert): Suche (Name de/en, ID — case-
|
||||
## insensitive) geht VOR der Kategorie (sucht im ganzen Katalog); ohne Suche
|
||||
## zeigt die aktive Kategorie nur ihre Gruppe, „Alle" den Klapp-Zustand wie
|
||||
## gemerkt, „Zuletzt" die Gemerkten. Danach Zähler „N von M sichtbar".
|
||||
func _apply_library_filter(query: String) -> void:
|
||||
var q := query.strip_edges().to_lower()
|
||||
var searching := not q.is_empty()
|
||||
var visible_count := 0
|
||||
# Suche gilt immer für den GANZEN Katalog; ohne Suche zählt der aktive Bereich.
|
||||
var scope: int = _manifest_count() if not q.is_empty() else _scope_ids().size()
|
||||
_recent_box.visible = false
|
||||
if searching:
|
||||
for row in _library_group_rows:
|
||||
var ids: Array = row["ids"]
|
||||
var any := false
|
||||
|
|
@ -179,24 +360,62 @@ func _apply_library_filter(query: String) -> void:
|
|||
var btn := library_buttons.get(str(oid)) as Button
|
||||
if btn == null:
|
||||
continue
|
||||
var vis := true
|
||||
if searching:
|
||||
vis = _library_oid_matches(str(oid), q)
|
||||
var vis := _library_oid_matches(str(oid), q)
|
||||
btn.visible = vis
|
||||
any = any or vis
|
||||
if vis:
|
||||
visible_count += 1
|
||||
var header := row["header"] as Button
|
||||
var box := row["box"] as VBoxContainer
|
||||
header.visible = any
|
||||
if searching:
|
||||
# Treffer-Gruppen bleiben auf — auch wenn sie zugeklappt waren
|
||||
# (Pfeil zeigt dann offen, kosmetisch konsistent).
|
||||
box.visible = any
|
||||
if any:
|
||||
header.text = _library_header_text(true, str(row["label"]), int(row["count"]))
|
||||
elif _cat_active == CAT_ZULETZT:
|
||||
for row in _library_group_rows:
|
||||
(row["header"] as Button).visible = false
|
||||
(row["box"] as VBoxContainer).visible = false
|
||||
for oid in row["ids"]:
|
||||
var btn := library_buttons.get(str(oid)) as Button
|
||||
if btn != null:
|
||||
btn.visible = false
|
||||
_fill_recent_box()
|
||||
_recent_box.visible = true
|
||||
for oid in _recent_ids:
|
||||
if _recent_tiles.has(str(oid)):
|
||||
visible_count += 1
|
||||
else:
|
||||
for row in _library_group_rows:
|
||||
var in_cat: bool = _cat_active == CAT_ALLE or str(row["key"]) == _cat_active
|
||||
var ids: Array = row["ids"]
|
||||
var header := row["header"] as Button
|
||||
var box := row["box"] as VBoxContainer
|
||||
header.visible = in_cat
|
||||
if not in_cat:
|
||||
box.visible = false
|
||||
for oid in ids:
|
||||
var btn := library_buttons.get(str(oid)) as Button
|
||||
if btn != null:
|
||||
btn.visible = false
|
||||
continue
|
||||
var is_open := bool(_library_group_open.get(str(row["key"]), false))
|
||||
if _cat_active != CAT_ALLE:
|
||||
# Kategorie-Ansicht zeigt DIESE Gruppe geöffnet (wie die
|
||||
# Suche ihre Treffer-Gruppen öffnet) — ohne den gemerkten
|
||||
# Klapp-Zustand zu überschreiben.
|
||||
is_open = true
|
||||
box.visible = is_open
|
||||
header.text = _library_header_text(is_open, str(row["label"]), int(row["count"]))
|
||||
for oid in ids:
|
||||
var btn := library_buttons.get(str(oid)) as Button
|
||||
if btn != null:
|
||||
btn.visible = is_open
|
||||
if is_open:
|
||||
visible_count += 1
|
||||
if library_counter != null:
|
||||
library_counter.text = "%d von %d sichtbar" % [visible_count, scope]
|
||||
|
||||
|
||||
func _library_oid_matches(oid: String, q: String) -> bool:
|
||||
|
|
@ -210,18 +429,35 @@ func _library_oid_matches(oid: String, q: String) -> bool:
|
|||
return false
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Kacheln ----
|
||||
|
||||
## Kachel in ein Grid hängen. canonical = Haupt-Grid (landet in
|
||||
## library_buttons, wird von interaction/_highlight_library gefunden);
|
||||
## Zuletzt-benutzt-Kacheln sind eigene frische Buttons (kein Dict-Konflikt).
|
||||
func _add_library_button(oid: String, parent: Container) -> void:
|
||||
var btn := _build_tile(oid)
|
||||
parent.add_child(btn)
|
||||
library_buttons[oid] = btn
|
||||
|
||||
|
||||
func _build_tile(oid: String) -> Button:
|
||||
var lib: Dictionary = main.manifest.get("objects", {})
|
||||
var entry: Dictionary = lib.get(oid, {})
|
||||
var poly := int(entry.get("poly", 0))
|
||||
var name_txt := LibraryBrowser.entry_label(oid, entry)
|
||||
var missing := _tile_is_missing(oid, entry)
|
||||
var btn := Button.new()
|
||||
btn.toggle_mode = true
|
||||
btn.button_pressed = false
|
||||
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
btn.custom_minimum_size = UIScale.v2(0, 126)
|
||||
btn.tooltip_text = "%s · %d Polys · %s" % [name_txt, poly, oid]
|
||||
btn.pressed.connect(_on_library_pressed.bind(oid))
|
||||
if missing:
|
||||
btn.tooltip_text += " · MODELL FEHLT (Platzhalter-Kiste)"
|
||||
btn.pressed.connect(_on_tile_pressed.bind(oid))
|
||||
btn.gui_input.connect(_on_tile_gui_input.bind(oid))
|
||||
btn.mouse_entered.connect(_on_tile_hover.bind(oid, true))
|
||||
btn.mouse_exited.connect(_on_tile_hover.bind(oid, false))
|
||||
var tex := LibraryBrowser.load_thumb(oid)
|
||||
if tex == null:
|
||||
# Kein Thumbnail → Text-Zeile wie früher (Fehlertoleranz, t_3a998ae4).
|
||||
|
|
@ -229,13 +465,21 @@ func _add_library_button(oid: String, parent: Container) -> void:
|
|||
btn.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
btn.custom_minimum_size = UIScale.v2(0, 44)
|
||||
else:
|
||||
# Kachel (t_59fc0f21): 96-px-Vorschaubild oben, Name klein darunter.
|
||||
# Kachel (t_59fc0f21 + U4): 96-px-Vorschaubild oben, ID + Name darunter.
|
||||
# Kinder ignorieren die Maus, damit die Klicks am Button ankommen.
|
||||
# Reihenfolge Bild → ID → Name: der Name bleibt das LETZTE Label
|
||||
# (test_library_browser_ui liest das letzte Label als Namen).
|
||||
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))
|
||||
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
|
||||
# sucht THUMB_DISPLAY_PX × Stufenfaktor). Die „Modell fehlt“-Marke
|
||||
# hängt als Badge-KIND am TextureRect — der Button ist kein Container,
|
||||
# eine Extra-Zeile unter dem Bild würde unter die Kachelkante rutschen
|
||||
# und von der nächsten Reihe überdeckt.
|
||||
var tr := TextureRect.new()
|
||||
tr.texture = tex
|
||||
tr.custom_minimum_size = UIScale.v2(LibraryBrowser.THUMB_DISPLAY_PX, LibraryBrowser.THUMB_DISPLAY_PX)
|
||||
|
|
@ -244,6 +488,17 @@ func _add_library_button(oid: String, parent: Container) -> void:
|
|||
tr.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
tile.add_child(tr)
|
||||
var id_lbl := Label.new()
|
||||
id_lbl.text = oid
|
||||
_id_font_normal = UIScale.f(9)
|
||||
id_lbl.add_theme_font_size_override("font_size", _id_font_normal)
|
||||
id_lbl.modulate = Color(1, 1, 1, 0.6)
|
||||
id_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
id_lbl.clip_text = true
|
||||
id_lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
id_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
id_lbl.set_meta("tile_id_label", true)
|
||||
tile.add_child(id_lbl)
|
||||
var lbl := Label.new()
|
||||
lbl.text = name_txt
|
||||
lbl.add_theme_font_size_override("font_size", UIScale.f(11))
|
||||
|
|
@ -252,27 +507,189 @@ func _add_library_button(oid: String, parent: Container) -> void:
|
|||
lbl.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
tile.add_child(lbl)
|
||||
parent.add_child(btn)
|
||||
library_buttons[oid] = btn
|
||||
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)
|
||||
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_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_constant_override("outline_size", UIScale.px(3))
|
||||
warn.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
warn.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
tr.add_child(warn)
|
||||
return btn
|
||||
|
||||
|
||||
func _on_library_pressed(oid: String) -> void:
|
||||
## Fehlendes Modell (U4): nur Katalog-Objekte MIT glb-Eintrag können fehlen;
|
||||
## kuratierte Primitive (ohne glb) bauen ihren Platzhalter lokal — nie „fehlt".
|
||||
func _tile_is_missing(oid: String, entry: Dictionary) -> bool:
|
||||
if str(entry.get("glb", "")).is_empty():
|
||||
return false
|
||||
return GlbMeshStore.fehlt(oid)
|
||||
|
||||
|
||||
## Hover (U4): die ID wird GRÖSSER, beim Verlassen wieder normal.
|
||||
func _on_tile_hover(oid: String, entered: bool) -> void:
|
||||
_set_tile_hover(oid, entered)
|
||||
|
||||
|
||||
func _set_tile_hover(oid: String, on: bool) -> void:
|
||||
var btn := _tile_button(oid)
|
||||
if btn == null:
|
||||
return
|
||||
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)
|
||||
|
||||
|
||||
func _tile_button(oid: String) -> Button:
|
||||
if library_buttons.has(oid):
|
||||
return library_buttons[oid] as Button
|
||||
if _recent_tiles.has(oid):
|
||||
return _recent_tiles[oid] as Button
|
||||
return null
|
||||
|
||||
|
||||
func _find_id_label(btn: Button) -> Label:
|
||||
var stack: Array = [btn]
|
||||
while not stack.is_empty():
|
||||
var node: Node = stack.pop_back()
|
||||
if node is Label and (node as Label).has_meta("tile_id_label"):
|
||||
return node as Label
|
||||
for c in node.get_children():
|
||||
stack.push_back(c)
|
||||
return null
|
||||
|
||||
|
||||
# --------------------------------------------------- Klick / Doppelklick ----
|
||||
|
||||
## Klick = auswählen (U4). Das Platzieren selbst startet NICHT mehr hier —
|
||||
## das übernimmt der Doppelklick (bzw. U5 das Einmal-Platzieren).
|
||||
func _on_tile_pressed(oid: String) -> void:
|
||||
_select_tile(oid)
|
||||
|
||||
|
||||
## Doppelklick = „gesetzt" haken: Werkzeug startet wie bisher (U4-Vertrag;
|
||||
## das Einmal-Platzieren kommt in U5).
|
||||
func _on_tile_gui_input(event: InputEvent, oid: String) -> void:
|
||||
if event is InputEventMouseButton and event.pressed \
|
||||
and event.button_index == MOUSE_BUTTON_LEFT and event.double_click:
|
||||
_activate_tile(oid)
|
||||
|
||||
|
||||
func _select_tile(oid: String) -> void:
|
||||
var changed := selected_oid != oid
|
||||
selected_oid = oid
|
||||
_apply_tile_selection()
|
||||
_record_recent(oid)
|
||||
if changed:
|
||||
object_selected.emit(oid)
|
||||
|
||||
|
||||
## Doppelklick-Aktion: Werkzeug-Start 1:1 wie der alte Bibliotheks-Klick
|
||||
## (interaction.start_placement, Auto-Rückwechsel auf Objekt-Werkzeug).
|
||||
func _activate_tile(oid: String) -> void:
|
||||
_select_tile(oid)
|
||||
if str(main.get("tool_mode")) != "objects":
|
||||
main.call("_set_tool_mode", "objects") # Bibliothek-Klick wechselt automatisch zurück
|
||||
if main.interaction.placing and main.interaction.place_oid == oid:
|
||||
# Nochmal geklickt → Modus beenden (An/Aus ohne Umwege).
|
||||
# Nochmal (Doppel-)geklickt → Modus beenden (An/Aus ohne Umwege).
|
||||
main.interaction.cancel_placement()
|
||||
main.call("_set_status", "Platzieren abgebrochen.")
|
||||
return
|
||||
for other_oid in library_buttons:
|
||||
(library_buttons[other_oid] as Button).button_pressed = false
|
||||
library_buttons[oid].button_pressed = true
|
||||
main.interaction.start_placement(oid)
|
||||
object_activated.emit(oid)
|
||||
|
||||
|
||||
## Der Bibliothek-Button des aktiven Platzier-Objekts leuchtet auf.
|
||||
func highlight_library(oid: String) -> void:
|
||||
## Auswahll-Marke je Kachel: ausgewählt = voll hell + Rahmen-Akzent.
|
||||
func _apply_tile_selection() -> void:
|
||||
for other_oid in library_buttons:
|
||||
var btn := library_buttons[other_oid] as Button
|
||||
btn.button_pressed = other_oid == oid
|
||||
btn.modulate = Color(1, 1, 1) if oid == other_oid else Color(1, 1, 1, 0.85)
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
## Der Bibliotheks-Button des aktiven Platzier-Objekts leuchtet auf
|
||||
## (Weitersenden von interaction.gd — nur Anzeige, KEIN Signal).
|
||||
func highlight_library(oid: String) -> void:
|
||||
selected_oid = oid
|
||||
_apply_tile_selection()
|
||||
if not oid.is_empty():
|
||||
_record_recent(oid)
|
||||
|
||||
|
||||
# -------------------------------------------------------- Zuletzt benutzt ----
|
||||
|
||||
func _recent_load() -> Array:
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.load("user://scopedvr_editor.cfg")
|
||||
var raw: Variant = cfg.get_value(LIBRARY_CFG_SECTION, RECENT_CFG_KEY, [])
|
||||
var lib: Dictionary = main.manifest.get("objects", {})
|
||||
var out: Array = []
|
||||
for v in (raw as Array):
|
||||
var oid := str(v)
|
||||
if lib.has(oid): # nur IDs, die es wirklich gibt
|
||||
out.append(oid)
|
||||
return out
|
||||
|
||||
|
||||
func _recent_save() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.load("user://scopedvr_editor.cfg")
|
||||
cfg.set_value(LIBRARY_CFG_SECTION, RECENT_CFG_KEY, _recent_ids)
|
||||
cfg.save("user://scopedvr_editor.cfg")
|
||||
|
||||
|
||||
## Auswahl/Benutzung merken (neueste zuerst, Ring mit RECENT_MAX Einträgen).
|
||||
func _record_recent(oid: String) -> void:
|
||||
if oid.is_empty():
|
||||
return
|
||||
_recent_ids.erase(oid)
|
||||
_recent_ids.insert(0, oid)
|
||||
while _recent_ids.size() > RECENT_MAX:
|
||||
_recent_ids.pop_back()
|
||||
_recent_save()
|
||||
_update_recent_chip()
|
||||
if _cat_active == CAT_ZULETZT:
|
||||
_fill_recent_box()
|
||||
|
||||
|
||||
func _fill_recent_box() -> void:
|
||||
if _recent_box == null:
|
||||
return
|
||||
for c in _recent_box.get_children():
|
||||
(c as Node).queue_free()
|
||||
_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_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_box.add_child(_recent_grid)
|
||||
var lib: Dictionary = main.manifest.get("objects", {})
|
||||
for oid in _recent_ids:
|
||||
if not lib.has(str(oid)):
|
||||
continue
|
||||
var btn := _build_tile(str(oid))
|
||||
_recent_grid.add_child(btn)
|
||||
_recent_tiles[str(oid)] = btn
|
||||
if str(oid) == selected_oid:
|
||||
btn.button_pressed = true
|
||||
btn.modulate = Color(1, 1, 1)
|
||||
|
|
|
|||
322
tests/test_assets_panel.gd
Normal file
322
tests/test_assets_panel.gd
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
extends SceneTree
|
||||
## Editor-Test (headless): Assets-Bereich Unity-artig — Karte t_69687afc (U4).
|
||||
## Ausführen: godot --headless --path . --script res://tests/test_assets_panel.gd
|
||||
## Exit 0 = alles grün.
|
||||
##
|
||||
## Instanziert die ECHTE Main-UI (Muster tests/test_library_browser_ui.gd) und prüft:
|
||||
## 1. Kategorien-Leiste: ein Chip je Bibliotheks-Gruppe (keine eigene Liste),
|
||||
## Summe der Gruppen-Anzahlen = Gesamtzahl des Manifests, „Alle" = Gesamtzahl.
|
||||
## 2. Suche findet NAME und ID (case-insensitive), Zähler „N von M sichtbar".
|
||||
## 3. Kategorie-Chip filtert auf genau diese Gruppe (Zähler M = Gruppengröße).
|
||||
## 4. Fehlendes Modell (glb-Eintrag, keine Quelle) → Kachel trägt die
|
||||
## Markierung „Modell fehlt" + Tooltip-Hinweis; Primitive ohne glb nie.
|
||||
## 5. Auswahl-Signal: Klick (Selection) löst object_selected GENAU EINMAL je
|
||||
## NEUER Auswahl aus (Wiederholung derselben Kachel kein zweites Signal).
|
||||
## 6. Hover vergrößert die ID-Schrift auf der Kachel.
|
||||
## 7. Doppelklick-Aktion startet das Platzier-Werkzeug wie bisher und nochmal
|
||||
## auf derselben Kachel bricht ab (alter An/Aus-Vertrag).
|
||||
## 8. „Zuletzt benutzt" wird je Auswahl in user://scopedvr_editor.cfg
|
||||
## gemerkt, überlebt Neu-Instanz und taucht als Chip + Ansicht auf.
|
||||
|
||||
const GLB_QUELLEN_SETTING := "scopedvr/editor/glb_quellen"
|
||||
|
||||
var failures := 0
|
||||
var checks := 0
|
||||
var main: Control
|
||||
var assets # src/ui/panels/assets_panel.gd
|
||||
var selected_count := 0
|
||||
var activated_count := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run_all.call_deferred()
|
||||
|
||||
|
||||
func _run_all() -> void:
|
||||
_clear_library_cfg_section()
|
||||
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)
|
||||
await process_frame
|
||||
await process_frame
|
||||
assets = main.assets_mod
|
||||
_check(assets != null, "Assets-Panel über Registry angemeldet")
|
||||
if assets == null:
|
||||
_finish()
|
||||
return
|
||||
assets.object_selected.connect(_on_selected)
|
||||
assets.object_activated.connect(_on_activated)
|
||||
|
||||
_test_kategorien()
|
||||
_test_suche_und_zaehler()
|
||||
_test_kategorie_filter()
|
||||
_test_fehlendes_modell()
|
||||
_test_auswahl_signal()
|
||||
_test_hover()
|
||||
_test_doppelklick()
|
||||
await _test_zuletzt_persistenz()
|
||||
|
||||
_finish()
|
||||
|
||||
|
||||
func _on_selected(_oid: String) -> void:
|
||||
selected_count += 1
|
||||
|
||||
|
||||
func _on_activated(_oid: String) -> void:
|
||||
activated_count += 1
|
||||
|
||||
|
||||
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 _check(cond: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if cond:
|
||||
print(" ok: %s" % label)
|
||||
else:
|
||||
failures += 1
|
||||
print(" FAIL: %s" % label)
|
||||
|
||||
|
||||
func _clear_library_cfg_section() -> void:
|
||||
# Nur UNSEREN Abschnitt leeren — [test]/[update]/[fenster] bleiben.
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.load("user://scopedvr_editor.cfg")
|
||||
if cfg.has_section("library"):
|
||||
cfg.erase_section("library")
|
||||
cfg.save("user://scopedvr_editor.cfg")
|
||||
|
||||
|
||||
# ------------------------------------------------------------- 1 Kategorien ----
|
||||
|
||||
func _test_kategorien() -> void:
|
||||
print("Testgruppe: Kategorien-Leiste (aus der Bibliothek, Summe = Gesamtzahl)")
|
||||
var lib: Dictionary = main.manifest.get("objects", {})
|
||||
var groups := LibraryBrowser.groups_in_order(main.manifest)
|
||||
var chip_keys: Array = assets._cat_buttons.keys()
|
||||
_check(chip_keys.has("alle"), "Chip „Alle“ existiert")
|
||||
_check(chip_keys.has("zuletzt"), "Chip „Zuletzt benutzt“ angelegt (noch versteckt)")
|
||||
var alle_chip: Button = assets._cat_buttons.get("alle")
|
||||
_check(int(alle_chip.get_meta("cat_count")) == lib.size(),
|
||||
"„Alle“ zeigt Gesamtzahl (%d)" % int(alle_chip.get_meta("cat_count")))
|
||||
var sum := 0
|
||||
var group_keys := {}
|
||||
for group in groups:
|
||||
if (group["ids"] as Array).is_empty():
|
||||
continue
|
||||
group_keys[str(group["key"])] = true
|
||||
var chip: Button = assets._cat_buttons.get(str(group["key"]))
|
||||
_check(chip != null, "Chip für Gruppe „%s“ existiert" % str(group["label"]))
|
||||
if chip == null:
|
||||
continue
|
||||
sum += int(chip.get_meta("cat_count"))
|
||||
_check(chip.text.contains(str(group["label"])), "Chip-Text trägt Gruppenname (%s)" % chip.text)
|
||||
_check(chip_keys.size() == group_keys.size() + 2, "keine erfundenen Kategorien (%d Chips für %d Gruppen + Alle/Zuletzt)" % [chip_keys.size(), group_keys.size()])
|
||||
_check(sum == lib.size(), "Summe der Gruppen-Anzahlen = Gesamtzahl (%d = %d)" % [sum, lib.size()])
|
||||
_check(alle_chip.button_pressed, "„Alle“ ist Start-Kategorie")
|
||||
_check(not (assets._cat_buttons.get("zuletzt") as Button).visible, "Zuletzt-Chip startet versteckt (noch nichts benutzt)")
|
||||
|
||||
|
||||
# ---------------------------------------------------- 2 Suche + Zähler ----
|
||||
|
||||
func _zaehler_n() -> int:
|
||||
var text: String = assets.library_counter.text
|
||||
var vor: String = text.split(" ")[0]
|
||||
return int(vor)
|
||||
|
||||
|
||||
func _zaehler_m() -> int:
|
||||
var text: String = assets.library_counter.text
|
||||
var teile: PackedStringArray = text.split(" ")
|
||||
return int(teile[2])
|
||||
|
||||
|
||||
func _test_suche_und_zaehler() -> void:
|
||||
print("Testgruppe: Suche (Name + ID) und Zähler „N von M sichtbar“")
|
||||
var lib_size := (main.manifest.get("objects", {}) as Dictionary).size()
|
||||
# Namens-Suche (deutsch, case egal):
|
||||
main.library_search.text = "PILZ"
|
||||
assets._apply_library_filter("PILZ")
|
||||
var pilze := 0
|
||||
for oid in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||||
var btn := main.library_buttons.get(str(oid)) as Button
|
||||
if btn != null and btn.visible:
|
||||
pilze += 1
|
||||
_check(pilze == 6, "„PILZ“ (Name, groß) findet 6 Kacheln (ist %d)" % pilze)
|
||||
_check(assets.library_counter.text == "%d von %d sichtbar" % [6, lib_size],
|
||||
"Zähler bei Suche: „%s“" % assets.library_counter.text)
|
||||
# ID-Suche (Kurz-ID):
|
||||
main.library_search.text = ""
|
||||
assets._apply_library_filter("watchtower")
|
||||
var wt := main.library_buttons.get("watchtower_wood") as Button
|
||||
_check(wt != null and wt.visible, "ID-Suche „watchtower“ findet Wachturm")
|
||||
main.library_search.text = ""
|
||||
assets._apply_library_filter("")
|
||||
_check(assets.library_counter.text == "%d von %d sichtbar" % [assets._library_group_rows[0]["count"], lib_size],
|
||||
"Zähler bei „Alle“ (1 Gruppe offen): „%s“" % assets.library_counter.text)
|
||||
|
||||
|
||||
# -------------------------------------------------- 3 Kategorie-Filter ----
|
||||
|
||||
func _test_kategorie_filter() -> void:
|
||||
print("Testgruppe: Kategorie-Chip filtert auf die Gruppe")
|
||||
assets._select_category("pilze")
|
||||
var sichtbar := 0
|
||||
for oid in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||||
var btn := main.library_buttons.get(str(oid)) as Button
|
||||
if btn != null and btn.visible:
|
||||
sichtbar += 1
|
||||
_check(sichtbar == 6, "Kategorie „Pilze“: 6 Kacheln sichtbar (ist %d)" % sichtbar)
|
||||
_check(assets.library_counter.text == "6 von 6 sichtbar", "Zähler in Kategorie: „%s“" % assets.library_counter.text)
|
||||
var pilze_chip: Button = assets._cat_buttons.get("pilze")
|
||||
_check(pilze_chip.button_pressed, "Pilze-Chip gedrückt")
|
||||
assets._select_category("alle")
|
||||
_check((assets._cat_buttons.get("alle") as Button).button_pressed, "Zurück auf „Alle“")
|
||||
|
||||
|
||||
# -------------------------------------------------- 4 Fehlendes Modell ----
|
||||
|
||||
func _test_fehlendes_modell() -> void:
|
||||
print("Testgruppe: Fehlendes Modell wird markiert (nicht stillschweigend)")
|
||||
# Hermetisch: GLB-Quellen auf ein garantiert leeres Lager umlenken →
|
||||
# JEDES Katalog-Objekt mit glb-Eintrag gilt als fehlend, egal wie die
|
||||
# Umgebung aussieht. (Leere Liste greift nicht — quellen() fällt auf
|
||||
# DEFAULT_QUELLEN zurück.)
|
||||
var alter_cache: Dictionary = GlbMeshStore._cache.duplicate()
|
||||
GlbMeshStore._cache.clear()
|
||||
ProjectSettings.set_setting(GLB_QUELLEN_SETTING, ["/nonexistent_glb_lager_u4/assets/objekte"])
|
||||
var fehlt_oid := ""
|
||||
for oid in (main.manifest.get("objects", {}) as Dictionary).keys():
|
||||
var entry: Dictionary = (main.manifest.get("objects", {}) as Dictionary).get(oid, {})
|
||||
if not str(entry.get("glb", "")).is_empty():
|
||||
fehlt_oid = str(oid)
|
||||
break
|
||||
_check(not fehlt_oid.is_empty(), "Katalog hat glb-Objekte (Probe: %s)" % fehlt_oid)
|
||||
if fehlt_oid.is_empty():
|
||||
ProjectSettings.set_setting(GLB_QUELLEN_SETTING, GlbMeshStore.DEFAULT_QUELLEN)
|
||||
return
|
||||
_check(GlbMeshStore.fehlt(fehlt_oid), "GlbMeshStore.fehlt ohne Lager = true (%s)" % fehlt_oid)
|
||||
_check(assets._tile_is_missing(fehlt_oid, main.manifest.get("objects", {}).get(fehlt_oid, {})),
|
||||
"_tile_is_missing(%s) = true ohne Quellen" % fehlt_oid)
|
||||
_check(not assets._tile_is_missing("crate_wood", main.manifest.get("objects", {}).get("crate_wood", {})),
|
||||
"Primitive ohne glb-Eintrag gelten NIE als fehlend (crate_wood)")
|
||||
var probe_grid := GridContainer.new()
|
||||
root.add_child(probe_grid)
|
||||
var tile: Button = assets._build_tile(fehlt_oid)
|
||||
probe_grid.add_child(tile)
|
||||
var warn: Label = null
|
||||
var dim_thumb := false
|
||||
var stack: Array = [tile]
|
||||
while not stack.is_empty():
|
||||
var node: Node = stack.pop_back()
|
||||
if node is Label and (node as Label).text == "Modell fehlt":
|
||||
warn = node
|
||||
if node is TextureRect and (node as TextureRect).modulate.a < 0.9:
|
||||
dim_thumb = true
|
||||
for c in node.get_children():
|
||||
stack.push_back(c)
|
||||
_check(warn != null, "Kachel trägt Warn-Label „Modell fehlt“")
|
||||
_check(dim_thumb, "Vorschaubild bei fehlendem Modell abgedunkelt")
|
||||
_check(tile.tooltip_text.contains("MODELL FEHLT"), "Tooltip nennt das fehlende Modell")
|
||||
probe_grid.queue_free()
|
||||
ProjectSettings.set_setting(GLB_QUELLEN_SETTING, GlbMeshStore.DEFAULT_QUELLEN)
|
||||
GlbMeshStore._cache = alter_cache
|
||||
|
||||
|
||||
# ------------------------------------------------------- 5 Auswahl-Signal ----
|
||||
|
||||
func _test_auswahl_signal() -> void:
|
||||
print("Testgruppe: Auswahl-Signal genau einmal je neuer Auswahl")
|
||||
selected_count = 0
|
||||
assets._select_tile("crate_wood")
|
||||
_check(assets.selected_oid == "crate_wood", "selected_oid gesetzt")
|
||||
_check(selected_count == 1, "1. Auswahl → genau 1 Signal (ist %d)" % selected_count)
|
||||
assets._select_tile("crate_wood")
|
||||
_check(selected_count == 1, "gleiche Kachel nochmal → KEIN zweites Signal (ist %d)" % selected_count)
|
||||
assets._select_tile("crate_metal")
|
||||
_check(selected_count == 2, "Wechsel → 2. Signal (ist %d)" % selected_count)
|
||||
var sel_btn := main.library_buttons.get("crate_metal") as Button
|
||||
_check(sel_btn != null and sel_btn.modulate.a > 0.95, "gewählte Kachel voll sichtbar")
|
||||
var unsel_btn := main.library_buttons.get("crate_wood") as Button
|
||||
_check(unsel_btn != null and unsel_btn.modulate.a < 0.95, "andere Kachel abgedimmt")
|
||||
|
||||
|
||||
# --------------------------------------------------------------- 6 Hover ----
|
||||
|
||||
func _test_hover() -> void:
|
||||
print("Testgruppe: Hover zeigt die ID größer")
|
||||
var btn := main.library_buttons.get("crate_metal") as Button
|
||||
var id_lbl_normal: int = assets._id_font_normal
|
||||
assets._set_tile_hover("crate_metal", true)
|
||||
var id_lbl: Label = assets._find_id_label(btn)
|
||||
_check(id_lbl != null, "Kachel hat ID-Label")
|
||||
if id_lbl != null:
|
||||
var gross: int = id_lbl.get_theme_font_size("font_size")
|
||||
assets._set_tile_hover("crate_metal", false)
|
||||
var klein: int = id_lbl.get_theme_font_size("font_size")
|
||||
_check(gross > klein, "Hover vergrößert die ID (%d > %d)" % [gross, klein])
|
||||
_check(klein == id_lbl_normal, "ohne Hover wieder Normalgröße (%d)" % klein)
|
||||
|
||||
|
||||
# --------------------------------------------------------- 7 Doppelklick ----
|
||||
|
||||
func _test_doppelklick() -> void:
|
||||
print("Testgruppe: Doppelklick startet Werkzeug wie bisher, nochmal = aus")
|
||||
activated_count = 0
|
||||
assets._activate_tile("crate_wood")
|
||||
_check(main.interaction.placing and main.interaction.place_oid == "crate_wood",
|
||||
"Doppelklick: Platzier-Werkzeug läuft für crate_wood")
|
||||
_check(activated_count == 1, "object_activated genau 1× (ist %d)" % activated_count)
|
||||
assets._activate_tile("crate_wood")
|
||||
_check(not main.interaction.placing, "nochmal dieselbe Kachel: Platzieren abgebrochen")
|
||||
assets._activate_tile("crate_metal")
|
||||
_check(main.interaction.placing and main.interaction.place_oid == "crate_metal",
|
||||
"Wechsel per Doppelklick auf crate_metal")
|
||||
main.interaction.cancel_placement()
|
||||
_check(not main.interaction.placing, "aufgeräumt: kein Platzieren mehr aktiv")
|
||||
|
||||
|
||||
# --------------------------------------------------- 8 Zuletzt benutzt ----
|
||||
|
||||
func _test_zuletzt_persistenz() -> void:
|
||||
print("Testgruppe: „Zuletzt benutzt“ — cfg + Neu-Instanz")
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.load("user://scopedvr_editor.cfg")
|
||||
var lst: Array = cfg.get_value("library", "zuletzt_benutzt", [])
|
||||
_check(lst.size() >= 2, "cfg [library] zuletzt_benutzt gefüllt (%d)" % lst.size())
|
||||
_check(str(lst[0]) == "crate_metal", "neueste Benutzung zuerst (ist %s)" % str(lst[0]))
|
||||
var chip := assets._cat_buttons.get("zuletzt") as Button
|
||||
_check(chip != null and chip.visible, "Zuletzt-Chip jetzt sichtbar")
|
||||
_check(chip.text.contains("2"), "Zuletzt-Chip zählt Einträge (%s)" % chip.text)
|
||||
# Ansicht: Kategorie „zuletzt" zeigt nur die Gemerkten.
|
||||
assets._select_category("zuletzt")
|
||||
_check(assets._recent_box.visible, "Zuletzt-Ansicht sichtbar")
|
||||
var tiles := 0
|
||||
for oid in assets._recent_ids:
|
||||
if assets._recent_tiles.has(str(oid)):
|
||||
tiles += 1
|
||||
_check(tiles == 2, "Zuletzt-Ansicht zeigt 2 Kacheln (ist %d)" % tiles)
|
||||
# Neu-Instanz = „Neustart": Chip + Liste kommen zurück.
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
await process_frame
|
||||
main = (load("res://scenes/main.tscn") as PackedScene).instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
assets = main.assets_mod
|
||||
var chip2 := assets._cat_buttons.get("zuletzt") as Button
|
||||
_check(chip2 != null and chip2.visible, "nach Neu-Instanz: Zuletzt-Chip sichtbar")
|
||||
_check(assets._recent_ids.size() == 2, "nach Neu-Instanz: 2 gemerkte IDs (ist %d)" % assets._recent_ids.size())
|
||||
_check(str(assets._recent_ids[0]) == "crate_metal", "Reihenfolge hält den Neustart")
|
||||
1
tests/test_assets_panel.gd.uid
Normal file
1
tests/test_assets_panel.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://d3454ajunh2tp
|
||||
Loading…
Reference in a new issue