extends Node class_name LayoutManager ## Manages saved dashboard layouts. ## ## Layouts map tile_id -> {col, row, w, h}. The current layout is loaded ## on startup and auto-saved when modules are placed or resized. ## Supports multiple named layouts with a simple file-per-layout scheme. signal layout_changed(layout_name: String) signal layout_saved(layout_name: String) signal layout_loaded(layout_name: String) const LAYOUT_DIR: String = "res://config/layouts/" ## Current layout name (e.g. "default"). var current_layout_name: String = "default" ## The active layout data: tile_id -> {col, row, w, h} var _layout: Dictionary = {} func _ready() -> void: if not DirAccess.dir_exists_absolute(LAYOUT_DIR): DirAccess.make_dir_recursive_absolute(LAYOUT_DIR) var configured_name: String = _get_configured_layout() if not configured_name.is_empty(): current_layout_name = configured_name if not load_layout(current_layout_name): current_layout_name = "default" _layout.clear() ## Return the layout name from config, or empty. func _get_configured_layout() -> String: if ConfigManager.has_method("get_setting"): var val: Variant = ConfigManager.get_setting("layout_current", "") if val is String and not val.is_empty(): return val return "" ## Set the current layout name (does not load or save). func set_current_layout(name: String) -> void: current_layout_name = name if ConfigManager.has_method("set_setting"): ConfigManager.set_setting("layout_current", name) layout_changed.emit(name) ## Get tile_id -> {col, row, w, h} for the current layout. func get_layout() -> Dictionary: return _layout.duplicate(true) ## Set a single tile's position in the current layout. func set_tile_position(tile_id: String, col: int, row: int, w: int, h: int) -> void: _layout[tile_id] = {col = col, row = row, w = w, h = h} ## Remove a tile from the current layout. func remove_tile(tile_id: String) -> void: _layout.erase(tile_id) ## Save the current layout to config/layouts/{name}.cfg func save_layout(name: String = "") -> void: if name.is_empty(): name = current_layout_name var cfg := ConfigFile.new() cfg.set_value("layout", "name", name) cfg.set_value("layout", "saved_at", Time.get_datetime_string_from_system()) var tile_ids: Array = _layout.keys() cfg.set_value("tiles", "count", tile_ids.size()) for i in tile_ids.size(): var tid: String = tile_ids[i] as String var entry: Dictionary = _layout[tid] var section := "tile_%d" % i cfg.set_value(section, "id", tid) cfg.set_value(section, "col", entry.get("col", 0)) cfg.set_value(section, "row", entry.get("row", 0)) cfg.set_value(section, "w", entry.get("w", 1)) cfg.set_value(section, "h", entry.get("h", 1)) var path := LAYOUT_DIR.path_join(name + ".cfg") var result := cfg.save(path) if result == OK: current_layout_name = name layout_saved.emit(name) ## Load a saved layout, replacing the current in-memory layout. ## Returns true if the file existed and was parsed. func load_layout(name: String) -> bool: var path := LAYOUT_DIR.path_join(name + ".cfg") if not FileAccess.file_exists(path): return false var cfg := ConfigFile.new() if cfg.load(path) != OK: return false _layout.clear() current_layout_name = name var tile_count: int = cfg.get_value("tiles", "count", 0) for i in range(tile_count): var section := "tile_%d" % i var tid: String = cfg.get_value(section, "id", "") if tid.is_empty(): continue _layout[tid] = { col = cfg.get_value(section, "col", 0), row = cfg.get_value(section, "row", 0), w = cfg.get_value(section, "w", 1), h = cfg.get_value(section, "h", 1), } layout_loaded.emit(name) return true ## Get a list of all saved layout names. func get_layout_names() -> PackedStringArray: var dir := DirAccess.open(LAYOUT_DIR) if dir == null: return PackedStringArray() var names: PackedStringArray = [] dir.list_dir_begin() var entry: String = dir.get_next() while entry != "": if entry.ends_with(".cfg") and not entry.begins_with("."): names.append(entry.trim_suffix(".cfg")) entry = dir.get_next() dir.list_dir_end() return names ## Switch to a different layout: saves current, loads new, emits signal. func switch_layout(name: String) -> void: if name == current_layout_name: return save_layout(current_layout_name) load_layout(name) set_current_layout(name)