# UiManager (P1) —— 窗口栈:打开 / 关闭 / 置顶 / ESC 关顶层 / modal 变暗 / 标题栏拖动。 # # var ui := preload("res://ui/ui_manager.gd").new() # add_child(ui) # ui.locale = func(k): return Locale.t(k) # var w := ui.open_script("res://../assets/uiscript/uiscript/systemdialog.py", assets_root) # w.nodes["close_button"].pressed.connect(func(): ui.close(w.root)) # # open_script 返回 { root: Control, nodes: {name: Control} }(同 UiBuild.build)。 extends CanvasLayer const UiScript = preload("res://ui/uiscript.gd") const UiBuild = preload("res://ui/ui_build.gd") signal window_opened(root: Control) signal window_closed(root: Control) var locale: Callable var screen := Vector2i(1920, 1080) var _root: Control # 全屏容器 var _dim: ColorRect # modal 变暗层 var _stack: Array[Control] = [] var _drag_win: Control = null var _drag_from := Vector2.ZERO func _ready() -> void: layer = 10 _root = Control.new() _root.set_anchors_preset(Control.PRESET_FULL_RECT) _root.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(_root) _dim = ColorRect.new() _dim.color = Color(0, 0, 0, 0.45) _dim.set_anchors_preset(Control.PRESET_FULL_RECT) _dim.visible = false _root.add_child(_dim) set_process_unhandled_input(true) # --- open / close -------------------------------------------------------- func build_script(path: String, assets_root: String) -> Dictionary: var us := UiScript.new() us.screen = screen if locale.is_valid(): us.locale = locale var spec := us.parse_file(path) if spec.is_empty(): push_warning("UiManager: parse failed (%s): %s" % [path, us.last_error]) return {"root": Control.new(), "nodes": {}} return UiBuild.build(spec, assets_root) # 解析 + 构建 + 打开,返回 { root, nodes } func open_script(path: String, assets_root: String, modal := false) -> Dictionary: var r := build_script(path, assets_root) open(r.root, modal) return r func open(win: Control, modal := false) -> void: if win.get_parent() == null: _root.add_child(win) win.set_meta("modal", modal) _wire_drag(win) _stack.erase(win) _stack.append(win) _restack() window_opened.emit(win) func close(win: Control) -> void: if win == null: return _stack.erase(win) if win.get_parent(): win.get_parent().remove_child(win) win.queue_free() _restack() window_closed.emit(win) func close_top() -> bool: if _stack.is_empty(): return false close(_stack[-1]) return true func top() -> Control: return _stack[-1] if not _stack.is_empty() else null func _restack() -> void: var any_modal := false for i in _stack.size(): var w := _stack[i] w.move_to_front() if bool(w.get_meta("modal", false)): any_modal = true _dim.visible = any_modal if any_modal: # 变暗层紧贴在最顶层 modal 之下 _dim.move_to_front() _stack[-1].move_to_front() # --- ESC --------------------------------------------------------------- func _unhandled_input(e: InputEvent) -> void: if e is InputEventKey and e.pressed and e.keycode == KEY_ESCAPE: if close_top(): get_viewport().set_input_as_handled() # --- 标题栏拖动 -------------------------------------------------------- func _wire_drag(win: Control) -> void: var handle := _find_drag_handle(win) if handle == null or handle.has_meta("_drag_wired"): return handle.set_meta("_drag_wired", true) handle.mouse_filter = Control.MOUSE_FILTER_STOP handle.gui_input.connect(func(ev: InputEvent): _on_handle_input(win, ev)) func _find_drag_handle(win: Control) -> Control: # 优先 titlebar;否则 board_with_titlebar 的顶部条;否则整个 window 根 for n in win.find_children("*", "", true, false): if n is Control and (n.get_meta("is_titlebar", false) or n.get_meta("titlebar_h", 0) > 0): return n return win func _on_handle_input(win: Control, ev: InputEvent) -> void: if ev is InputEventMouseButton and ev.button_index == MOUSE_BUTTON_LEFT: if ev.pressed: _drag_win = win _drag_from = win.get_global_mouse_position() - win.position open(win) # 置顶 else: _drag_win = null elif ev is InputEventMouseMotion and _drag_win == win: win.position = win.get_global_mouse_position() - _drag_from