UI improvements
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
uid://bnttsy278nvaw
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
-19
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
+7
-18
@@ -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()
|
||||
|
||||
@@ -2,7 +2,7 @@ class_name PauseSettings extends Control
|
||||
|
||||
func open() -> void:
|
||||
visible = true
|
||||
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user