From 0cf1f92eaabd59c9b7421c21d1b1908414086144 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 14 Jun 2026 10:19:31 -0500 Subject: [PATCH] UI improvements --- .claude/docs/ui-navigation.md | 128 ++++++++++++++++++++++++++++ CLAUDE.md | 2 + overworld/camera/OverworldCamera.gd | 6 +- overworld/entity/EntityMovement.gd | 2 +- scene/Pause.gd | 4 +- ui/RootUI.gd | 5 -- ui/UIFocusStack.gd | 31 +++++++ ui/UIFocusStack.gd.uid | 1 + ui/UISingleton.gd | 4 + ui/component/ClosableMenu.gd | 72 ++++++++++++---- ui/component/ConfirmDialog.gd | 6 +- ui/component/ModalBackdrop.gd | 50 ++--------- ui/gamemenu/GameMenu.gd | 31 +++---- ui/mainmenu/MainMenu.gd | 8 +- ui/pause/PauseMenu.gd | 25 ++---- ui/pause/PauseSettings.gd | 2 +- 16 files changed, 263 insertions(+), 114 deletions(-) create mode 100644 .claude/docs/ui-navigation.md create mode 100644 ui/UIFocusStack.gd create mode 100644 ui/UIFocusStack.gd.uid diff --git a/.claude/docs/ui-navigation.md b/.claude/docs/ui-navigation.md new file mode 100644 index 0000000..a7a6b67 --- /dev/null +++ b/.claude/docs/ui-navigation.md @@ -0,0 +1,128 @@ +# UI Navigation Design + +--- + +## The four scenarios + +1. **Two-panel navigation** — left sidebar selects a tab, right panel shows content; left/right crosses panels +2. **Modal focus** — when a modal opens the parent stops receiving input; closing restores it +3. **Nested modals** — multiple stacked modals; each blocks the one below; back unwinds the stack +4. **Mouse handling** — only the topmost active layer accepts clicks; background layers are blocked + +--- + +## Core concept: Focus Stack + +`UIFocusStack` (`ui/UIFocusStack.gd`) is a `RefCounted` that tracks an ordered stack of open `ClosableMenu` layers. Only the topmost layer processes input. When a layer is pushed, the one below is paused via `set_process_unhandled_input(false)`; when popped, it resumes. The topmost layer always renders on top — `z_index` is set automatically on push/pop so visual order always matches input priority. + +Lives at `UI.FOCUS_STACK`. Key methods: `push(layer)`, `pop()`, `top() -> ClosableMenu`, `isTop(layer) -> bool`. Emits `activeLayerChanged(layer)` whenever the top changes; `null` means the stack is empty (world has focus). + +``` +Stack (bottom → top): + [GameMenu] z_index 10 ← paused while QuitDialog is open + [QuitConfirmDialog] z_index 20 ← top, owns input, renders on top + [ModalBackdrop] z_index 15 ← sits between them visually +``` + +--- + +## ClosableMenu + +`ClosableMenu` (`ui/component/ClosableMenu.gd`) is the base for all interactive menus. Key additions: + +- **`canClose:bool = true`** — when true, `open()`/`close()` push/pop the FocusStack. When false, the menu is purely passive (shown/hidden by code, no input ownership). Set in the Inspector or overridden in `_ready()`. +- **`focusGained` / `focusLost` signals** — emitted when the stack pushes/pops this layer. +- **`_savedFocusNode`** — the focused Control captured in `_onFocusLost()`. Restored in `_onFocusGained()` so that pressing back from a dialog returns focus to the button that triggered it. +- **`_grabInitialFocus()`** — override in subclasses to place focus on the right element when first opened (when no saved node exists). +- `open()` sets `visible = true`, pushes to stack (if `canClose`), then emits `opened`. +- `close()` pops from stack (if `canClose`), hides, then emits `closed`. +- `_ready()` calls `set_process_unhandled_input(false)` for `canClose` menus — they start silenced and only enable input when on top of the stack. + +| Menu | canClose | Reason | +|---|---|---| +| `GameMenu` | `true` | Player opens and closes it | +| `ConfirmDialog` | `true` | Player dismisses it | +| `PauseMenu` | `true` | Player opens/closes via pause bind | +| `DialogueTextbox` | `false` | Dialogue system controls its lifetime | +| `PauseSettings` | n/a | Does not extend ClosableMenu — internal sub-panel | + +--- + +## Z-indexing + +`UIFocusStack` is the sole owner of `z_index` for all `ClosableMenu` layers. On push, `z_index = stack_depth * 10`. On pop, `z_index` resets to 0. `ModalBackdrop` always sits at `(top z_index) - 5`. + +Never set `z_index` manually on a ClosableMenu — the stack manages it. + +--- + +## ModalBackdrop + +`ModalBackdrop` (`ui/component/ModalBackdrop.gd`) connects to `UI.FOCUS_STACK.activeLayerChanged` in `_ready()`. When a layer is active it becomes visible, sets `mouse_filter = MOUSE_FILTER_STOP` (blocking all clicks on anything behind it), and sets its own `z_index` to `(top layer z_index) - 5`. When the stack empties it hides and resets to `MOUSE_FILTER_IGNORE`. + +The `register()` method and `_openOverlays` tracking are removed — the FocusStack signal replaces that entirely. `RootUI._ready()` no longer calls `register()`. + +--- + +## Scenario 1: Two-Panel Navigation (SidebarMenu) + +`SidebarMenu` (to be created at `ui/component/SidebarMenu.gd`) extends `ClosableMenu` and manages two internal panels: a left sidebar (tab selector) and a right content panel. An `_activePanel` enum (`SIDEBAR` / `CONTENT`) tracks which panel currently owns controller/keyboard navigation. Both panels live in the same focus layer — no stack push/pop when crossing between them. + +**Controller / keyboard** — routed through `_unhandled_input`, which checks `_activePanel`: +- While `SIDEBAR`: UP/DOWN navigate sidebar items and update the content preview. RIGHT or ACCEPT calls `_enterContent()`. BACK closes the menu. +- While `CONTENT`: UP/DOWN navigate content items (wraps). LEFT or BACK calls `_exitContent()`. ACCEPT activates the item. + +**Mouse** — ignores `_activePanel` entirely. `pressed` signals on items fire regardless of which panel the controller is in. Each item's press handler calls `_enterContent()` or `_exitContent()` as appropriate before processing the selection — these are no-ops if the panel is already active. + +**ContentPanel protocol** — each right-panel tab must implement `grabFirstFocus()`, `releaseFocus()`, and `getSelectedIndex() -> int`. No enforced base class; convention only. + +`GameMenu` will extend `SidebarMenu` once SidebarMenu is built. + +--- + +## Scenario 2 & 3: Modal Focus and Nested Modals + +Both are the same mechanism — one push vs. multiple. Opening a sub-layer calls `layer.open()` which pushes it; the layer below automatically loses input. Closing calls `close()` which pops; the layer below automatically resumes and `_savedFocusNode` is restored to wherever focus was when the sub-layer opened. + +For 3-deep nesting (`MainMenu → LoadGameModal → ConfirmDialog`), each BACK unwinds one level. No special logic — the stack handles it. + +--- + +## Scenario 4: Mouse Handling + +`ModalBackdrop` with `MOUSE_FILTER_STOP` eats all mouse events aimed at anything behind it. The topmost layer (higher `z_index`) renders above the backdrop and receives clicks normally. Works at any nesting depth. + +Within a `SidebarMenu`, both panels are in the same layer so no backdrop is between them — mouse clicks always work on either panel. + +--- + +## World input and movement blocking + +`EntityMovement._canMove()` and `OverworldCamera._canOrbit()` now check `UI.FOCUS_STACK.top() != null` — if any layer is active, movement and camera orbit are blocked. This replaces the previous ad-hoc `UI.GAME_MENU.isOpen()` check. + +`UI.activeConversation` is kept separately for dialogue-mode blocking (dialogue does not use the FocusStack). + +--- + +## What was changed + +| File | Change | +|---|---| +| `ui/UIFocusStack.gd` | New — FocusStack manager | +| `ui/component/ClosableMenu.gd` | Added `canClose`, focus signals, `_onFocusGained/Lost`, `_savedFocusNode`, `_grabInitialFocus` | +| `ui/UISingleton.gd` | Added `FOCUS_STACK` (initialized via preload in `_ready`) | +| `ui/component/ModalBackdrop.gd` | Rewritten — connects to FocusStack signal, sets `MOUSE_FILTER_STOP` and `z_index` | +| `ui/RootUI.gd` | Removed `modalBackdrop.register()` calls | +| `ui/component/ConfirmDialog.gd` | Extends ClosableMenu; `_grabInitialFocus` focuses No button; removed `!isOpen` guard | +| `ui/gamemenu/GameMenu.gd` | Extends ClosableMenu; uses `_grabInitialFocus`; "menu" toggle via `_input` | +| `ui/pause/PauseMenu.gd` | Extends ClosableMenu; removed visibility/dialog guards from `_unhandled_input` | +| `ui/pause/PauseSettings.gd` | Unchanged — stays as Control (internal sub-panel, not in stack) | +| `ui/mainmenu/MainMenu.gd` | `settingsMenu.open()` replaces direct `isOpen` set; removed `_onSettingsOpened` stub | +| `scene/Pause.gd` | `menu.isOpen()` → `menu.isOpen` (property) | +| `overworld/entity/EntityMovement.gd` | `_canMove` checks `FOCUS_STACK.top() != null` | +| `overworld/camera/OverworldCamera.gd` | `_canOrbit` checks `FOCUS_STACK.top() != null` | + +## Still to implement + +- `SidebarMenu` base class +- `GameMenu` refactored to extend `SidebarMenu` diff --git a/CLAUDE.md b/CLAUDE.md index 7dd5b38..3c14064 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,6 +13,7 @@ Detailed reference lives in [.claude/docs/](.claude/docs/): - [Stubs](.claude/docs/stubs.md) — incomplete / placeholder systems to avoid relying on - [Overworld](.claude/docs/overworld.md) — map transitions, Entity exports, interaction types, camera, movement - [UI](.claude/docs/ui.md) — UI singleton, VNTextbox, ClosableMenu, pause/debug/settings menus, AdvancedRichText +- [UI Navigation](.claude/docs/ui-navigation.md) — FocusStack, FocusLayer, SidebarMenu, modal layering, mouse blocking (design doc — not yet implemented) @.claude/docs/code-style.md @.claude/docs/architecture.md @@ -21,3 +22,4 @@ Detailed reference lives in [.claude/docs/](.claude/docs/): @.claude/docs/stubs.md @.claude/docs/overworld.md @.claude/docs/ui.md +@.claude/docs/ui-navigation.md diff --git a/overworld/camera/OverworldCamera.gd b/overworld/camera/OverworldCamera.gd index f0ea3e3..71044c2 100644 --- a/overworld/camera/OverworldCamera.gd +++ b/overworld/camera/OverworldCamera.gd @@ -43,7 +43,11 @@ var _mouseDelta:Vector2 = Vector2.ZERO var _rightMouseHeld:bool = false func _canOrbit() -> bool: - return not UI.activeConversation + if UI.activeConversation: + return false + if UI.FOCUS_STACK.top() != null: + return false + return true func _input(event:InputEvent) -> void: if not _canOrbit(): diff --git a/overworld/entity/EntityMovement.gd b/overworld/entity/EntityMovement.gd index d360bc6..9bdb06e 100644 --- a/overworld/entity/EntityMovement.gd +++ b/overworld/entity/EntityMovement.gd @@ -65,7 +65,7 @@ func _applyFriction(delta:float) -> void: func _canMove() -> bool: if UI.activeConversation: return false - if UI.GAME_MENU && UI.GAME_MENU.isOpen(): + if UI.FOCUS_STACK.top() != null: return false return true diff --git a/scene/Pause.gd b/scene/Pause.gd index 491f3d3..78c36b2 100644 --- a/scene/Pause.gd +++ b/scene/Pause.gd @@ -9,9 +9,9 @@ func _unhandled_input(event:InputEvent) -> void: var menu:PauseMenu = UI.PAUSE_MENU if menu == null: return - if SCENE.currentScene == SceneSingleton.SceneType.INITIAL and !menu.isOpen(): + if SCENE.currentScene == SceneSingleton.SceneType.INITIAL and !menu.isOpen: return - if menu.isOpen(): + if menu.isOpen: menu.close() else: menu.open() diff --git a/ui/RootUI.gd b/ui/RootUI.gd index 813390a..b1265fa 100644 --- a/ui/RootUI.gd +++ b/ui/RootUI.gd @@ -14,8 +14,3 @@ func _enter_tree() -> void: func _exit_tree() -> void: if UI.rootUi == self: UI.rootUi = null - -func _ready() -> void: - modalBackdrop.register(pauseMenu) - modalBackdrop.register(quitConfirmDialog) - modalBackdrop.register(mainMenuConfirmDialog) diff --git a/ui/UIFocusStack.gd b/ui/UIFocusStack.gd new file mode 100644 index 0000000..e7b7ce8 --- /dev/null +++ b/ui/UIFocusStack.gd @@ -0,0 +1,31 @@ +class_name UIFocusStack extends RefCounted + +signal activeLayerChanged(layer:ClosableMenu) + +const Z_STEP:int = 10 + +var _stack:Array[ClosableMenu] = [] + +func push(layer:ClosableMenu) -> void: + if not _stack.is_empty(): + _stack.back()._onFocusLost() + _stack.push_back(layer) + layer.z_index = _stack.size() * Z_STEP + layer._onFocusGained() + activeLayerChanged.emit(layer) + +func pop() -> void: + if _stack.is_empty(): return + var removed:ClosableMenu = _stack.pop_back() + removed.z_index = 0 + removed._onFocusLost() + var next:ClosableMenu = top() + if next != null: + next._onFocusGained() + activeLayerChanged.emit(next) + +func top() -> ClosableMenu: + return _stack.back() if not _stack.is_empty() else null + +func isTop(layer:ClosableMenu) -> bool: + return top() == layer diff --git a/ui/UIFocusStack.gd.uid b/ui/UIFocusStack.gd.uid new file mode 100644 index 0000000..6bc5693 --- /dev/null +++ b/ui/UIFocusStack.gd.uid @@ -0,0 +1 @@ +uid://bnttsy278nvaw diff --git a/ui/UISingleton.gd b/ui/UISingleton.gd index b460006..14631c2 100644 --- a/ui/UISingleton.gd +++ b/ui/UISingleton.gd @@ -1,7 +1,10 @@ extends Node +const _FocusStackScript = preload("res://ui/UIFocusStack.gd") + var rootUi:RootUI = null var interactIndicator:InteractIndicator = null +var FOCUS_STACK:RefCounted = null # True whenever any dialogue resource is being processed by DialogueManager. # Driven by DialogueManager.dialogue_started / dialogue_ended signals. @@ -11,6 +14,7 @@ var dialogueActive:bool = false var activeConversation:bool = false func _ready() -> void: + FOCUS_STACK = _FocusStackScript.new() DialogueManager.dialogue_started.connect(_onDialogueStarted) DialogueManager.dialogue_ended.connect(_onDialogueEnded) SCENE.sceneChanged.connect(_onSceneChanged) diff --git a/ui/component/ClosableMenu.gd b/ui/component/ClosableMenu.gd index c88b421..43e8891 100644 --- a/ui/component/ClosableMenu.gd +++ b/ui/component/ClosableMenu.gd @@ -1,18 +1,19 @@ class_name ClosableMenu extends Control -@export var isOpen: bool: - set(newValue): - isOpen = newValue - visible = newValue - if newValue: - opened.emit() - else: - closed.emit() +signal opened +signal closed +signal focusGained +signal focusLost + +@export var canClose:bool = true +@export var isOpen:bool = false: + set(v): + isOpen = v + visible = v get(): return isOpen -signal closed -signal opened +var _savedFocusNode:Control = null func _enter_tree() -> void: visible = isOpen @@ -22,13 +23,54 @@ func _exit_tree() -> void: func _ready() -> void: visible = isOpen - print("ClosableMenu is ready, isOpen: ", isOpen) - -func close() -> void: - isOpen = false + if canClose: + set_process_unhandled_input(false) func open() -> void: + visible = true + if canClose: + UI.FOCUS_STACK.push(self) isOpen = true + opened.emit() + +func close() -> void: + if canClose: + UI.FOCUS_STACK.pop() + isOpen = false + closed.emit() func toggle() -> void: - isOpen = !isOpen + if isOpen: + close() + else: + open() + +func _onFocusGained() -> void: + set_process_unhandled_input(true) + get_viewport().gui_focus_changed.connect(_onViewportFocusChanged) + if _savedFocusNode != null and is_instance_valid(_savedFocusNode): + _savedFocusNode.grab_focus() + else: + _grabInitialFocus() + var currentFocus:Control = get_viewport().gui_get_focus_owner() + if currentFocus != null and is_ancestor_of(currentFocus): + _savedFocusNode = currentFocus + focusGained.emit() + +func _onFocusLost() -> void: + _savedFocusNode = get_viewport().gui_get_focus_owner() + if get_viewport().gui_focus_changed.is_connected(_onViewportFocusChanged): + get_viewport().gui_focus_changed.disconnect(_onViewportFocusChanged) + set_process_unhandled_input(false) + focusLost.emit() + +func _onViewportFocusChanged(control:Control) -> void: + if control == null or is_ancestor_of(control): + return + if _savedFocusNode != null and is_instance_valid(_savedFocusNode): + _savedFocusNode.grab_focus() + else: + _grabInitialFocus() + +func _grabInitialFocus() -> void: + pass diff --git a/ui/component/ConfirmDialog.gd b/ui/component/ConfirmDialog.gd index 8c9a8fd..617496c 100644 --- a/ui/component/ConfirmDialog.gd +++ b/ui/component/ConfirmDialog.gd @@ -6,6 +6,7 @@ signal confirmed @export var btnNo:Button func _ready() -> void: + super._ready() close() btnYes.pressed.connect(_onYes) btnNo.pressed.connect(close) @@ -18,13 +19,10 @@ func _onYes() -> void: close() confirmed.emit() -func open() -> void: - super.open() +func _grabInitialFocus() -> void: btnNo.grab_focus() func _unhandled_input(event:InputEvent) -> void: - if !isOpen: - return if event.is_action_pressed("ui_cancel"): close() get_viewport().set_input_as_handled() diff --git a/ui/component/ModalBackdrop.gd b/ui/component/ModalBackdrop.gd index 22fcc9e..ba078bd 100644 --- a/ui/component/ModalBackdrop.gd +++ b/ui/component/ModalBackdrop.gd @@ -1,48 +1,16 @@ class_name ModalBackdrop extends ColorRect -# Tracks which overlays are currently open. Each entry must be a direct sibling -# (child of the same parent). The backdrop repositions itself in the scene tree -# to sit immediately below whichever open overlay has the highest tree index, -# so only one backdrop is ever visible regardless of how many overlays are open. -var _openOverlays:Array[Control] = [] - func _ready() -> void: visible = false + mouse_filter = MOUSE_FILTER_IGNORE + UI.FOCUS_STACK.activeLayerChanged.connect(_onActiveLayerChanged) -func register(overlay:Control) -> void: - assert(overlay.get_parent() == get_parent(), "ModalBackdrop: overlay must be a sibling") - assert(overlay.has_signal("opened") and overlay.has_signal("closed"), - "ModalBackdrop: overlay must have opened/closed signals") - overlay.connect("opened", func(): _onOpened(overlay)) - overlay.connect("closed", func(): _onClosed(overlay)) - -func _onOpened(overlay:Control) -> void: - if overlay not in _openOverlays: - _openOverlays.append(overlay) - _reposition() - -func _onClosed(overlay:Control) -> void: - _openOverlays.erase(overlay) - _reposition() - -func _reposition() -> void: - if _openOverlays.is_empty(): +func _onActiveLayerChanged(layer:ClosableMenu) -> void: + if layer == null or layer.get_parent() != get_parent(): visible = false - return - visible = true - var top := _topOverlay() - var topIdx := top.get_index() - var myIdx := get_index() - if myIdx == topIdx - 1: - return - if myIdx < topIdx: - get_parent().move_child(self, topIdx - 1) + mouse_filter = MOUSE_FILTER_IGNORE + z_index = 0 else: - get_parent().move_child(self, topIdx) - -func _topOverlay() -> Control: - var top:Control = _openOverlays[0] - for overlay in _openOverlays: - if overlay.get_index() > top.get_index(): - top = overlay - return top + visible = true + mouse_filter = MOUSE_FILTER_STOP + z_index = layer.z_index - 5 diff --git a/ui/gamemenu/GameMenu.gd b/ui/gamemenu/GameMenu.gd index 62be818..e3d5bd3 100644 --- a/ui/gamemenu/GameMenu.gd +++ b/ui/gamemenu/GameMenu.gd @@ -1,4 +1,4 @@ -class_name GameMenu extends Control +class_name GameMenu extends ClosableMenu enum Tab { PARTY, ITEMS } @@ -9,21 +9,14 @@ enum Tab { PARTY, ITEMS } var _currentTab:Tab = Tab.PARTY func _ready() -> void: - visible = false + super._ready() SIDEBAR.item_selected.connect(_onTabSelected) -func open() -> void: - visible = true +func _grabInitialFocus() -> void: _selectTab(_currentTab) SIDEBAR.select(_currentTab) SIDEBAR.grab_focus() -func close() -> void: - visible = false - -func isOpen() -> bool: - return visible - func _onTabSelected(index:int) -> void: _selectTab(index as Tab) @@ -37,16 +30,16 @@ func _selectTab(tab:Tab) -> void: Tab.ITEMS: ITEMS_TAB.refresh() +func _input(event:InputEvent) -> void: + if not event.is_action_pressed("menu"): + return + if isOpen: + close() + elif UI.FOCUS_STACK.top() == null and not UI.dialogueActive and SCENE.currentScene == SceneSingleton.SceneType.OVERWORLD: + open() + get_viewport().set_input_as_handled() + func _unhandled_input(event:InputEvent) -> void: - if event.is_action_pressed("menu"): - if visible: - close() - elif !UI.dialogueActive and SCENE.currentScene == SceneSingleton.SceneType.OVERWORLD: - open() - get_viewport().set_input_as_handled() - return - if !visible: - return if event.is_action_pressed("ui_cancel"): close() get_viewport().set_input_as_handled() diff --git a/ui/mainmenu/MainMenu.gd b/ui/mainmenu/MainMenu.gd index f1c2dd8..ff82906 100644 --- a/ui/mainmenu/MainMenu.gd +++ b/ui/mainmenu/MainMenu.gd @@ -10,18 +10,12 @@ func _ready() -> void: btnNewGame.pressed.connect(onNewGamePressed) btnSettings.pressed.connect(onSettingsPressed) btnQuit.pressed.connect(_onQuitPressed) - settingsMenu.opened.connect(_onSettingsOpened) settingsMenu.closed.connect(_onSettingsClosed) func _notification(what:int) -> void: if what == NOTIFICATION_ENTER_TREE: btnNewGame.call_deferred("grab_focus") -func _onSettingsOpened() -> void: - # Move focus into the settings panel so the controller can navigate it. - # The SettingsMenu grabs its own internal focus via _notification. - pass - func _onSettingsClosed() -> void: btnSettings.grab_focus() @@ -43,4 +37,4 @@ func onNewGamePressed() -> void: OVERWORLD.mapChange(newGameScene, "PlayerSpawnPoint") func onSettingsPressed() -> void: - settingsMenu.isOpen = true + settingsMenu.open() diff --git a/ui/pause/PauseMenu.gd b/ui/pause/PauseMenu.gd index 8ad7791..45985c1 100644 --- a/ui/pause/PauseMenu.gd +++ b/ui/pause/PauseMenu.gd @@ -1,32 +1,25 @@ -class_name PauseMenu extends Control - -signal opened -signal closed +class_name PauseMenu extends ClosableMenu @export var MAIN:PauseMain @export var settingsPanel:PauseSettings func _ready() -> void: - close() + super._ready() MAIN.resumeRequested.connect(close) MAIN.settingsRequested.connect(_openSettings) UI.MAIN_MENU_DIALOG.confirmed.connect(_goToMainMenu) -func isOpen() -> bool: - return visible - func open() -> void: - visible = true + super.open() get_tree().paused = true + settingsPanel.close() MAIN.open() - opened.emit() func close() -> void: get_tree().paused = false - visible = false - MAIN.close() settingsPanel.close() - closed.emit() + MAIN.close() + super.close() func _openSettings() -> void: MAIN.close() @@ -37,11 +30,7 @@ func _goToMainMenu() -> void: SCENE.setScene(SceneSingleton.SceneType.INITIAL) func _unhandled_input(event:InputEvent) -> void: - if !visible: - return - if !event.is_action_pressed("ui_cancel"): - return - if (UI.QUIT_DIALOG != null and UI.QUIT_DIALOG.isOpen) or (UI.MAIN_MENU_DIALOG != null and UI.MAIN_MENU_DIALOG.isOpen): + if not event.is_action_pressed("ui_cancel"): return if settingsPanel.isOpen(): settingsPanel.close() diff --git a/ui/pause/PauseSettings.gd b/ui/pause/PauseSettings.gd index 04f5dce..5be0cd4 100644 --- a/ui/pause/PauseSettings.gd +++ b/ui/pause/PauseSettings.gd @@ -2,7 +2,7 @@ class_name PauseSettings extends Control func open() -> void: visible = true - + func close() -> void: visible = false