fix controller file dialog navigation
This commit is contained in:
parent
2208977c8d
commit
e12ed54ab8
6 changed files with 469 additions and 26 deletions
|
|
@ -1324,8 +1324,10 @@ func _handle_data_root_controller_input(event: InputEvent) -> bool:
|
|||
else _data_setup_dialog.gui_get_focus_owner()
|
||||
)
|
||||
var focused_button := focused as BaseButton
|
||||
if focused_button != null and not focused_button.disabled:
|
||||
focused_button.pressed.emit()
|
||||
if (
|
||||
focused_button != null
|
||||
and FileDialogControllerNavigation.activate_control(focused_button)
|
||||
):
|
||||
return true
|
||||
if (
|
||||
(focused is LineEdit or focused is TextEdit)
|
||||
|
|
|
|||
|
|
@ -35,11 +35,19 @@ func _run() -> void:
|
|||
FileDialogControllerNavigationType.interactive_controls(root_scope)
|
||||
)
|
||||
var directory_list: ItemList
|
||||
var parent_button: Button
|
||||
var create_folder_button: Button
|
||||
var path_edit: LineEdit
|
||||
var has_menu_button: bool = false
|
||||
var has_cancel: bool = false
|
||||
var has_select: bool = false
|
||||
var drive_button: MenuButton
|
||||
var refresh_button: Button
|
||||
var favorite_button: Button
|
||||
var hidden_button: Button
|
||||
var grid_button: Button
|
||||
var list_button: Button
|
||||
var filter_button: Button
|
||||
var sort_button: MenuButton
|
||||
var cancel_button: Button
|
||||
var select_button: Button
|
||||
for control: Control in root_controls:
|
||||
if (
|
||||
control is ItemList
|
||||
|
|
@ -50,22 +58,125 @@ func _run() -> void:
|
|||
path_edit = control as LineEdit
|
||||
var button := control as Button
|
||||
if button != null:
|
||||
parent_button = (
|
||||
button
|
||||
if button.tooltip_text == "Go to parent folder."
|
||||
else parent_button
|
||||
)
|
||||
create_folder_button = (
|
||||
button
|
||||
if button.tooltip_text == "Create a new folder."
|
||||
else create_folder_button
|
||||
)
|
||||
has_cancel = has_cancel or button.text == "Cancel"
|
||||
has_select = has_select or button.text == "Select Current Folder"
|
||||
has_menu_button = has_menu_button or control is MenuButton
|
||||
refresh_button = (
|
||||
button
|
||||
if button.tooltip_text == "Refresh files."
|
||||
else refresh_button
|
||||
)
|
||||
favorite_button = (
|
||||
button
|
||||
if button.tooltip_text == "(Un)favorite current folder."
|
||||
else favorite_button
|
||||
)
|
||||
hidden_button = (
|
||||
button
|
||||
if button.tooltip_text == (
|
||||
"Toggle the visibility of hidden files."
|
||||
)
|
||||
else hidden_button
|
||||
)
|
||||
grid_button = (
|
||||
button
|
||||
if button.tooltip_text == (
|
||||
"View items as a grid of thumbnails."
|
||||
)
|
||||
else grid_button
|
||||
)
|
||||
list_button = (
|
||||
button
|
||||
if button.tooltip_text == "View items as a list."
|
||||
else list_button
|
||||
)
|
||||
filter_button = (
|
||||
button
|
||||
if button.tooltip_text == (
|
||||
"Toggle the visibility of the filter for file names."
|
||||
)
|
||||
else filter_button
|
||||
)
|
||||
cancel_button = button if button.text == "Cancel" else cancel_button
|
||||
select_button = (
|
||||
button
|
||||
if button.text == "Select Current Folder"
|
||||
else select_button
|
||||
)
|
||||
var menu := control as MenuButton
|
||||
if menu != null:
|
||||
drive_button = menu if menu.accessibility_name == "Drive" else drive_button
|
||||
sort_button = (
|
||||
menu if menu.tooltip_text == "Sort files" else sort_button
|
||||
)
|
||||
assert(directory_list != null)
|
||||
assert(parent_button != null)
|
||||
assert(path_edit != null)
|
||||
assert(create_folder_button != null)
|
||||
assert(has_menu_button)
|
||||
assert(has_cancel)
|
||||
assert(has_select)
|
||||
assert(drive_button != null)
|
||||
assert(refresh_button != null)
|
||||
assert(favorite_button != null)
|
||||
assert(hidden_button != null)
|
||||
assert(grid_button != null)
|
||||
assert(list_button != null)
|
||||
assert(filter_button != null)
|
||||
assert(sort_button != null)
|
||||
assert(cancel_button != null)
|
||||
assert(select_button != null)
|
||||
assert(dialog.gui_get_focus_owner() == directory_list)
|
||||
_assert_neighbor(path_edit, &"focus_neighbor_left", parent_button)
|
||||
_assert_neighbor(path_edit, &"focus_neighbor_right", drive_button)
|
||||
_assert_neighbor(drive_button, &"focus_neighbor_left", path_edit)
|
||||
_assert_neighbor(refresh_button, &"focus_neighbor_left", drive_button)
|
||||
_assert_neighbor(favorite_button, &"focus_neighbor_left", refresh_button)
|
||||
_assert_neighbor(
|
||||
create_folder_button, &"focus_neighbor_left", favorite_button
|
||||
)
|
||||
_assert_neighbor(hidden_button, &"focus_neighbor_right", grid_button)
|
||||
_assert_neighbor(grid_button, &"focus_neighbor_right", list_button)
|
||||
_assert_neighbor(list_button, &"focus_neighbor_right", filter_button)
|
||||
_assert_neighbor(filter_button, &"focus_neighbor_right", sort_button)
|
||||
_assert_neighbor(sort_button, &"focus_neighbor_top", create_folder_button)
|
||||
_assert_neighbor(
|
||||
create_folder_button, &"focus_neighbor_bottom", sort_button
|
||||
)
|
||||
_assert_neighbor(directory_list, &"focus_neighbor_top", hidden_button)
|
||||
_assert_neighbor(directory_list, &"focus_neighbor_bottom", select_button)
|
||||
_assert_neighbor(cancel_button, &"focus_neighbor_right", select_button)
|
||||
_assert_neighbor(select_button, &"focus_neighbor_left", cancel_button)
|
||||
assert(favorite_button.accessibility_name == "favorite current folder")
|
||||
assert(create_folder_button.accessibility_name == "new folder")
|
||||
assert(filter_button.accessibility_name == "filter file names")
|
||||
_assert_directionally_reachable(directory_list, root_controls)
|
||||
directory_list.clear()
|
||||
directory_list.add_item("test folder")
|
||||
directory_list.select(0)
|
||||
directory_list.grab_focus()
|
||||
assert(
|
||||
FileDialogControllerNavigationType.move_from_item_list(
|
||||
directory_list, Vector2.UP
|
||||
)
|
||||
)
|
||||
assert(dialog.gui_get_focus_owner() == hidden_button)
|
||||
directory_list.grab_focus()
|
||||
assert(
|
||||
FileDialogControllerNavigationType.move_from_item_list(
|
||||
directory_list, Vector2.DOWN
|
||||
)
|
||||
)
|
||||
assert(dialog.gui_get_focus_owner() == select_button)
|
||||
assert(FileDialogControllerNavigationType.activate_control(sort_button))
|
||||
await process_frame
|
||||
assert(sort_button.get_popup().visible)
|
||||
sort_button.get_popup().hide()
|
||||
await process_frame
|
||||
|
||||
create_folder_button.pressed.emit()
|
||||
for _frame: int in 3:
|
||||
|
|
@ -124,7 +235,14 @@ func _validate_compact_dialog() -> void:
|
|||
root.size = Vector2i(640, 480)
|
||||
var font_controller := InterfaceFontControllerType.new()
|
||||
root.add_child(font_controller)
|
||||
var keyboard := OnScreenKeyboardType.new()
|
||||
root.add_child(keyboard)
|
||||
await process_frame
|
||||
keyboard.set_enabled(false)
|
||||
font_controller.set_controller_text_entry_request(
|
||||
Callable(keyboard, "request_for_control"),
|
||||
Callable(keyboard, "is_open"),
|
||||
)
|
||||
var dialog := FileDialog.new()
|
||||
dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
|
|
@ -140,17 +258,43 @@ func _validate_compact_dialog() -> void:
|
|||
FileDialogControllerNavigationType.interactive_controls(scope)
|
||||
)
|
||||
var directory_list: ItemList
|
||||
var path_edit: LineEdit
|
||||
var select_button: Button
|
||||
for control: Control in controls:
|
||||
if (
|
||||
control is ItemList
|
||||
and control.accessibility_name == "Directories & Files:"
|
||||
):
|
||||
directory_list = control as ItemList
|
||||
break
|
||||
if control is LineEdit and control.accessibility_name == "Path:":
|
||||
path_edit = control as LineEdit
|
||||
if control is Button and (control as Button).text == (
|
||||
"Select Current Folder"
|
||||
):
|
||||
select_button = control as Button
|
||||
assert(directory_list != null)
|
||||
assert(path_edit != null)
|
||||
assert(select_button != null)
|
||||
_assert_directionally_reachable(directory_list, controls)
|
||||
directory_list.clear()
|
||||
directory_list.add_item("test folder")
|
||||
directory_list.select(0)
|
||||
directory_list.grab_focus()
|
||||
var down_event := InputEventJoypadButton.new()
|
||||
down_event.button_index = JOY_BUTTON_DPAD_DOWN
|
||||
down_event.pressed = true
|
||||
font_controller.call("_input", down_event)
|
||||
assert(scope.gui_get_focus_owner() == select_button)
|
||||
path_edit.grab_focus()
|
||||
var accept_event := InputEventJoypadButton.new()
|
||||
accept_event.button_index = JOY_BUTTON_A
|
||||
accept_event.pressed = true
|
||||
font_controller.call("_input", accept_event)
|
||||
assert(keyboard.is_open())
|
||||
keyboard.call("_close_keyboard", true)
|
||||
dialog.queue_free()
|
||||
font_controller.queue_free()
|
||||
keyboard.queue_free()
|
||||
await process_frame
|
||||
|
||||
|
||||
|
|
@ -181,3 +325,13 @@ func _assert_directionally_reachable(
|
|||
visited[neighbor.get_instance_id()] = true
|
||||
pending.append(neighbor)
|
||||
assert(visited.size() == expected.size())
|
||||
|
||||
|
||||
func _assert_neighbor(
|
||||
origin: Control,
|
||||
property_name: StringName,
|
||||
expected: Control,
|
||||
) -> void:
|
||||
var neighbor_path: NodePath = origin.get(property_name)
|
||||
var neighbor := origin.get_node_or_null(neighbor_path) as Control
|
||||
assert(neighbor == expected)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,18 @@ func _run() -> void:
|
|||
func _validate_default_and_persistence() -> void:
|
||||
var defaults := PlayerSettings.new()
|
||||
assert(not defaults.on_screen_keyboard_enabled)
|
||||
assert(KeyboardType.should_enable_for_controller(false, false))
|
||||
assert(not KeyboardType.should_enable_for_controller(false, true))
|
||||
assert(KeyboardType.should_enable_for_controller(true, true))
|
||||
assert(
|
||||
KeyboardType.should_enable_for_controller(false, false, "Linux")
|
||||
)
|
||||
assert(
|
||||
KeyboardType.should_enable_for_controller(false, true, "Linux")
|
||||
)
|
||||
assert(
|
||||
not KeyboardType.should_enable_for_controller(false, true, "Android")
|
||||
)
|
||||
assert(
|
||||
KeyboardType.should_enable_for_controller(true, true, "Android")
|
||||
)
|
||||
var manager := SettingsManagerType.new()
|
||||
root.add_child(manager)
|
||||
assert(manager.load_settings())
|
||||
|
|
@ -47,12 +56,7 @@ func _validate_keyboard_entry() -> void:
|
|||
host.add_child(keyboard)
|
||||
await process_frame
|
||||
keyboard.set_enabled(false)
|
||||
assert(
|
||||
keyboard.is_enabled()
|
||||
== not DisplayServer.has_feature(
|
||||
DisplayServer.FEATURE_VIRTUAL_KEYBOARD
|
||||
)
|
||||
)
|
||||
assert(keyboard.is_enabled())
|
||||
var activate_event := InputEventJoypadButton.new()
|
||||
activate_event.button_index = JOY_BUTTON_A
|
||||
activate_event.pressed = true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,24 @@ extends RefCounted
|
|||
const ControllerFocusNavigationType = preload(
|
||||
"res://ui/controller_focus_navigation.gd"
|
||||
)
|
||||
const ROW_TOLERANCE: float = 12.0
|
||||
const EDGE_TOLERANCE: float = 1.0
|
||||
|
||||
const ACCESSIBILITY_NAMES_BY_TOOLTIP: Dictionary[String, String] = {
|
||||
"Go to previous folder.": "previous folder",
|
||||
"Go to next folder.": "next folder",
|
||||
"Go to parent folder.": "parent folder",
|
||||
"Refresh files.": "refresh files",
|
||||
"(Un)favorite current folder.": "favorite current folder",
|
||||
"Create a new folder.": "new folder",
|
||||
"Toggle the visibility of hidden files.": "show hidden files",
|
||||
"View items as a grid of thumbnails.": "thumbnail view",
|
||||
"View items as a list.": "list view",
|
||||
"Toggle the visibility of the filter for file names.": (
|
||||
"filter file names"
|
||||
),
|
||||
"Sort files": "sort files",
|
||||
}
|
||||
|
||||
|
||||
static func configure(dialog: FileDialog) -> void:
|
||||
|
|
@ -25,7 +43,10 @@ static func configure_scope(
|
|||
_collect_interactive_controls(scope, scope, controls)
|
||||
if controls.is_empty():
|
||||
return
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(controls)
|
||||
if scope is FileDialog:
|
||||
_configure_file_dialog_neighbors(controls)
|
||||
else:
|
||||
ControllerFocusNavigationType.configure_spatial_neighbors(controls)
|
||||
var focus_owner: Control = scope.gui_get_focus_owner()
|
||||
if controls.has(preferred_control):
|
||||
if focus_owner != preferred_control:
|
||||
|
|
@ -51,6 +72,44 @@ static func interactive_controls(scope: Window) -> Array[Control]:
|
|||
return controls
|
||||
|
||||
|
||||
static func activate_control(control: Control) -> bool:
|
||||
if control == null or not control.is_visible_in_tree():
|
||||
return false
|
||||
var menu_button := control as MenuButton
|
||||
if menu_button != null and not menu_button.disabled:
|
||||
menu_button.show_popup()
|
||||
return true
|
||||
var button := control as BaseButton
|
||||
if button != null and not button.disabled:
|
||||
button.pressed.emit()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func move_from_item_list(
|
||||
item_list: ItemList,
|
||||
direction: Vector2,
|
||||
) -> bool:
|
||||
if (
|
||||
item_list == null
|
||||
or not item_list.has_focus()
|
||||
or not _item_list_at_edge(item_list, direction)
|
||||
):
|
||||
return false
|
||||
var neighbor_path: NodePath = _neighbor_path_for_direction(
|
||||
item_list, direction
|
||||
)
|
||||
var neighbor := item_list.get_node_or_null(neighbor_path) as Control
|
||||
if (
|
||||
neighbor == null
|
||||
or neighbor == item_list
|
||||
or not ControllerFocusNavigationType.is_focusable(neighbor)
|
||||
):
|
||||
return false
|
||||
neighbor.grab_focus()
|
||||
return true
|
||||
|
||||
|
||||
static func _deepest_exclusive_window(window: Window) -> Window:
|
||||
for child: Node in window.get_children(true):
|
||||
var child_window := child as Window
|
||||
|
|
@ -79,6 +138,7 @@ static func _collect_interactive_controls(
|
|||
# accessibility-only. Promote those genuine controls for controller
|
||||
# focus while leaving labels, scrollbars, and splitters untouched.
|
||||
control.focus_mode = Control.FOCUS_ALL
|
||||
_apply_accessibility_name(control)
|
||||
output.append(control)
|
||||
_collect_interactive_controls(child, scope, output)
|
||||
|
||||
|
|
@ -122,3 +182,159 @@ static func _preferred_initial_control(
|
|||
if controls.has(ok_button):
|
||||
return ok_button
|
||||
return controls.front()
|
||||
|
||||
|
||||
static func _configure_file_dialog_neighbors(
|
||||
controls: Array[Control],
|
||||
) -> void:
|
||||
var rows: Array[Array] = []
|
||||
var ordered: Array[Control] = controls.duplicate()
|
||||
ordered.sort_custom(_sort_by_top_then_left)
|
||||
for control: Control in ordered:
|
||||
var control_top: float = control.get_global_rect().position.y
|
||||
var destination_row: Array = []
|
||||
for row: Array in rows:
|
||||
var row_control := row.front() as Control
|
||||
var row_top: float = (
|
||||
row_control.get_global_rect().position.y
|
||||
)
|
||||
if absf(control_top - row_top) <= ROW_TOLERANCE:
|
||||
destination_row = row
|
||||
break
|
||||
if destination_row.is_empty():
|
||||
destination_row = []
|
||||
rows.append(destination_row)
|
||||
destination_row.append(control)
|
||||
for row: Array in rows:
|
||||
row.sort_custom(_sort_by_left)
|
||||
for row_index: int in rows.size():
|
||||
var row: Array = rows[row_index]
|
||||
for column_index: int in row.size():
|
||||
var control := row[column_index] as Control
|
||||
var left := row[maxi(column_index - 1, 0)] as Control
|
||||
var right := row[mini(column_index + 1, row.size() - 1)] as Control
|
||||
var top: Control = _vertical_neighbor(
|
||||
control, rows, row_index, -1
|
||||
)
|
||||
var bottom: Control = _vertical_neighbor(
|
||||
control, rows, row_index, 1
|
||||
)
|
||||
control.focus_neighbor_left = control.get_path_to(left)
|
||||
control.focus_neighbor_right = control.get_path_to(right)
|
||||
control.focus_neighbor_top = control.get_path_to(top)
|
||||
control.focus_neighbor_bottom = control.get_path_to(bottom)
|
||||
control.focus_previous = control.get_path_to(
|
||||
ordered[wrapi(ordered.find(control) - 1, 0, ordered.size())]
|
||||
)
|
||||
control.focus_next = control.get_path_to(
|
||||
ordered[wrapi(ordered.find(control) + 1, 0, ordered.size())]
|
||||
)
|
||||
|
||||
|
||||
static func _vertical_neighbor(
|
||||
origin: Control,
|
||||
rows: Array[Array],
|
||||
origin_row: int,
|
||||
direction: int,
|
||||
) -> Control:
|
||||
var origin_rect: Rect2 = origin.get_global_rect()
|
||||
var row_index: int = origin_row + direction
|
||||
while row_index >= 0 and row_index < rows.size():
|
||||
var best: Control
|
||||
var best_distance: float = INF
|
||||
for candidate_value: Variant in rows[row_index]:
|
||||
var candidate := candidate_value as Control
|
||||
var candidate_rect: Rect2 = candidate.get_global_rect()
|
||||
if not _horizontally_aligned(origin_rect, candidate_rect):
|
||||
continue
|
||||
var distance: float = absf(
|
||||
origin_rect.get_center().x - candidate_rect.get_center().x
|
||||
)
|
||||
if distance < best_distance:
|
||||
best = candidate
|
||||
best_distance = distance
|
||||
if best != null:
|
||||
return best
|
||||
row_index += direction
|
||||
return origin
|
||||
|
||||
|
||||
static func _horizontally_aligned(first: Rect2, second: Rect2) -> bool:
|
||||
return (
|
||||
first.position.x <= second.end.x + EDGE_TOLERANCE
|
||||
and second.position.x <= first.end.x + EDGE_TOLERANCE
|
||||
)
|
||||
|
||||
|
||||
static func _sort_by_top_then_left(first: Control, second: Control) -> bool:
|
||||
var first_position: Vector2 = first.get_global_rect().position
|
||||
var second_position: Vector2 = second.get_global_rect().position
|
||||
if absf(first_position.y - second_position.y) > ROW_TOLERANCE:
|
||||
return first_position.y < second_position.y
|
||||
return first_position.x < second_position.x
|
||||
|
||||
|
||||
static func _sort_by_left(first: Variant, second: Variant) -> bool:
|
||||
return (
|
||||
(first as Control).get_global_rect().position.x
|
||||
< (second as Control).get_global_rect().position.x
|
||||
)
|
||||
|
||||
|
||||
static func _apply_accessibility_name(control: Control) -> void:
|
||||
if not control.accessibility_name.is_empty():
|
||||
return
|
||||
var authored_name: String = ACCESSIBILITY_NAMES_BY_TOOLTIP.get(
|
||||
control.tooltip_text, ""
|
||||
)
|
||||
if not authored_name.is_empty():
|
||||
control.accessibility_name = authored_name
|
||||
|
||||
|
||||
static func _item_list_at_edge(
|
||||
item_list: ItemList,
|
||||
direction: Vector2,
|
||||
) -> bool:
|
||||
if item_list.item_count <= 0:
|
||||
return true
|
||||
var selected: PackedInt32Array = item_list.get_selected_items()
|
||||
if selected.is_empty():
|
||||
return false
|
||||
var selected_rect: Rect2 = item_list.get_item_rect(selected[0])
|
||||
var content_bounds: Rect2 = item_list.get_item_rect(0)
|
||||
for item_index: int in range(1, item_list.item_count):
|
||||
content_bounds = content_bounds.merge(
|
||||
item_list.get_item_rect(item_index)
|
||||
)
|
||||
if direction == Vector2.UP:
|
||||
return selected_rect.position.y <= (
|
||||
content_bounds.position.y + EDGE_TOLERANCE
|
||||
)
|
||||
if direction == Vector2.DOWN:
|
||||
return selected_rect.end.y >= (
|
||||
content_bounds.end.y - EDGE_TOLERANCE
|
||||
)
|
||||
if direction == Vector2.LEFT:
|
||||
return selected_rect.position.x <= (
|
||||
content_bounds.position.x + EDGE_TOLERANCE
|
||||
)
|
||||
if direction == Vector2.RIGHT:
|
||||
return selected_rect.end.x >= (
|
||||
content_bounds.end.x - EDGE_TOLERANCE
|
||||
)
|
||||
return false
|
||||
|
||||
|
||||
static func _neighbor_path_for_direction(
|
||||
control: Control,
|
||||
direction: Vector2,
|
||||
) -> NodePath:
|
||||
if direction == Vector2.UP:
|
||||
return control.focus_neighbor_top
|
||||
if direction == Vector2.DOWN:
|
||||
return control.focus_neighbor_bottom
|
||||
if direction == Vector2.LEFT:
|
||||
return control.focus_neighbor_left
|
||||
if direction == Vector2.RIGHT:
|
||||
return control.focus_neighbor_right
|
||||
return NodePath()
|
||||
|
|
|
|||
|
|
@ -116,6 +116,64 @@ func _process(_delta: float) -> void:
|
|||
_connect_file_dialog_text_controls(dialog)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if (
|
||||
_controller_text_entry_is_open.is_valid()
|
||||
and bool(_controller_text_entry_is_open.call())
|
||||
):
|
||||
return
|
||||
if not (event is InputEventJoypadButton or event is InputEventJoypadMotion):
|
||||
return
|
||||
var scope: Window = _active_file_dialog_scope()
|
||||
if scope == null:
|
||||
return
|
||||
var focused: Control = scope.gui_get_focus_owner()
|
||||
if focused == null:
|
||||
return
|
||||
var direction: Vector2 = _controller_direction(event)
|
||||
var item_list := focused as ItemList
|
||||
if (
|
||||
item_list != null
|
||||
and direction != Vector2.ZERO
|
||||
and FileDialogControllerNavigationType.move_from_item_list(
|
||||
item_list, direction
|
||||
)
|
||||
):
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
var button_event := event as InputEventJoypadButton
|
||||
if (
|
||||
button_event != null
|
||||
and button_event.pressed
|
||||
and event.is_action_pressed(&"ui_accept")
|
||||
and (focused is LineEdit or focused is TextEdit)
|
||||
and _controller_text_entry_request.is_valid()
|
||||
and bool(_controller_text_entry_request.call(focused))
|
||||
):
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _active_file_dialog_scope() -> Window:
|
||||
for reference_value: WeakRef in _tracked_file_dialogs.values():
|
||||
var dialog := reference_value.get_ref() as FileDialog
|
||||
if dialog == null or not is_instance_valid(dialog) or not dialog.visible:
|
||||
continue
|
||||
return FileDialogControllerNavigationType.active_scope(dialog)
|
||||
return null
|
||||
|
||||
|
||||
func _controller_direction(event: InputEvent) -> Vector2:
|
||||
if event.is_action_pressed(&"ui_up"):
|
||||
return Vector2.UP
|
||||
if event.is_action_pressed(&"ui_down"):
|
||||
return Vector2.DOWN
|
||||
if event.is_action_pressed(&"ui_left"):
|
||||
return Vector2.LEFT
|
||||
if event.is_action_pressed(&"ui_right"):
|
||||
return Vector2.RIGHT
|
||||
return Vector2.ZERO
|
||||
|
||||
|
||||
func _connect_file_dialog_text_controls(dialog: FileDialog) -> void:
|
||||
var scope: Window = FileDialogControllerNavigationType.active_scope(dialog)
|
||||
for control: Control in (
|
||||
|
|
|
|||
|
|
@ -127,17 +127,26 @@ func _is_available_for_controller() -> bool:
|
|||
return should_enable_for_controller(
|
||||
_enabled,
|
||||
DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD),
|
||||
OS.get_name(),
|
||||
)
|
||||
|
||||
|
||||
static func should_enable_for_controller(
|
||||
preference_enabled: bool,
|
||||
native_virtual_keyboard_available: bool,
|
||||
platform_name: String = "",
|
||||
) -> bool:
|
||||
# A controller user must always have one viable text-entry path. Android
|
||||
# and any future display backend with native support can keep using the
|
||||
# platform keyboard unless the in-game keyboard is explicitly requested.
|
||||
return preference_enabled or not native_virtual_keyboard_available
|
||||
# Desktop display backends can advertise native virtual-keyboard support
|
||||
# without presenting one for controller focus. Only Android delegates to
|
||||
# that platform keyboard by default.
|
||||
var resolved_platform: String = (
|
||||
OS.get_name() if platform_name.is_empty() else platform_name
|
||||
)
|
||||
return (
|
||||
preference_enabled
|
||||
or resolved_platform != "Android"
|
||||
or not native_virtual_keyboard_available
|
||||
)
|
||||
|
||||
|
||||
func _can_edit(control: Control) -> bool:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue