feat: overhaul controller menu accessibility
This commit is contained in:
parent
a7837c3711
commit
4f9c85989b
46 changed files with 4091 additions and 396 deletions
124
ui/file_dialog_controller_navigation.gd
Normal file
124
ui/file_dialog_controller_navigation.gd
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
class_name FileDialogControllerNavigation
|
||||
extends RefCounted
|
||||
|
||||
const ControllerFocusNavigationType = preload(
|
||||
"res://ui/controller_focus_navigation.gd"
|
||||
)
|
||||
|
||||
|
||||
static func configure(dialog: FileDialog) -> void:
|
||||
if dialog == null or not dialog.visible or not dialog.is_inside_tree():
|
||||
return
|
||||
var scope: Window = active_scope(dialog)
|
||||
if scope == null:
|
||||
return
|
||||
configure_scope(scope)
|
||||
|
||||
|
||||
static func configure_scope(
|
||||
scope: Window,
|
||||
preferred_control: Control = null,
|
||||
) -> void:
|
||||
if scope == null or not scope.visible or not scope.is_inside_tree():
|
||||
return
|
||||
var controls: Array[Control] = []
|
||||
_collect_interactive_controls(scope, scope, controls)
|
||||
if controls.is_empty():
|
||||
return
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(controls)
|
||||
var focus_owner: Control = scope.gui_get_focus_owner()
|
||||
if controls.has(preferred_control):
|
||||
if focus_owner != preferred_control:
|
||||
preferred_control.grab_focus()
|
||||
return
|
||||
if focus_owner != null and controls.has(focus_owner):
|
||||
return
|
||||
var preferred: Control = _preferred_initial_control(scope, controls)
|
||||
if preferred != null:
|
||||
preferred.grab_focus()
|
||||
|
||||
|
||||
static func active_scope(dialog: FileDialog) -> Window:
|
||||
if dialog == null or not dialog.visible:
|
||||
return null
|
||||
return _deepest_exclusive_window(dialog)
|
||||
|
||||
|
||||
static func interactive_controls(scope: Window) -> Array[Control]:
|
||||
var controls: Array[Control] = []
|
||||
if scope != null:
|
||||
_collect_interactive_controls(scope, scope, controls)
|
||||
return controls
|
||||
|
||||
|
||||
static func _deepest_exclusive_window(window: Window) -> Window:
|
||||
for child: Node in window.get_children(true):
|
||||
var child_window := child as Window
|
||||
if (
|
||||
child_window == null
|
||||
or not child_window.visible
|
||||
or not child_window.exclusive
|
||||
):
|
||||
continue
|
||||
return _deepest_exclusive_window(child_window)
|
||||
return window
|
||||
|
||||
|
||||
static func _collect_interactive_controls(
|
||||
node: Node,
|
||||
scope: Window,
|
||||
output: Array[Control],
|
||||
) -> void:
|
||||
for child: Node in node.get_children(true):
|
||||
var child_window := child as Window
|
||||
if child_window != null and child_window != scope:
|
||||
continue
|
||||
var control := child as Control
|
||||
if control != null and _is_controller_interactive(control):
|
||||
# Godot marks some visible FileDialog toolbar and menu buttons as
|
||||
# accessibility-only. Promote those genuine controls for controller
|
||||
# focus while leaving labels, scrollbars, and splitters untouched.
|
||||
control.focus_mode = Control.FOCUS_ALL
|
||||
output.append(control)
|
||||
_collect_interactive_controls(child, scope, output)
|
||||
|
||||
|
||||
static func _is_controller_interactive(control: Control) -> bool:
|
||||
if (
|
||||
control == null
|
||||
or not control.is_visible_in_tree()
|
||||
or control.size.x < 8.0
|
||||
or control.size.y < 8.0
|
||||
or control is ScrollBar
|
||||
):
|
||||
return false
|
||||
var button := control as BaseButton
|
||||
if button != null:
|
||||
return not button.disabled
|
||||
return (
|
||||
control is LineEdit
|
||||
or control is TextEdit
|
||||
or control is ItemList
|
||||
or control is Tree
|
||||
)
|
||||
|
||||
|
||||
static func _preferred_initial_control(
|
||||
scope: Window,
|
||||
controls: Array[Control],
|
||||
) -> Control:
|
||||
if scope is FileDialog:
|
||||
for control: Control in controls:
|
||||
if control is ItemList and control.accessibility_name == (
|
||||
"Directories & Files:"
|
||||
):
|
||||
return control
|
||||
for control: Control in controls:
|
||||
if control is LineEdit or control is TextEdit:
|
||||
return control
|
||||
if scope is ConfirmationDialog:
|
||||
var confirmation := scope as ConfirmationDialog
|
||||
var ok_button: Button = confirmation.get_ok_button()
|
||||
if controls.has(ok_button):
|
||||
return ok_button
|
||||
return controls.front()
|
||||
Loading…
Add table
Add a link
Reference in a new issue