Improve handheld data folder setup
This commit is contained in:
parent
3f48106e00
commit
8ca2c10f71
4 changed files with 190 additions and 11 deletions
107
main/main.gd
107
main/main.gd
|
|
@ -200,6 +200,8 @@ var _server_trust_dialog: ConfirmationDialog
|
|||
var _pending_trust_changed: bool = false
|
||||
var _identity_notice_dialog: AcceptDialog
|
||||
var _data_setup_dialog: ConfirmationDialog
|
||||
var _data_setup_choose_button: Button
|
||||
var _data_setup_current_button: Button
|
||||
var _data_folder_dialog: FileDialog
|
||||
var _data_folder_picker_generation: int = 0
|
||||
var _restore_data_setup_after_picker: bool = false
|
||||
|
|
@ -657,10 +659,10 @@ func _show_data_root_setup() -> void:
|
|||
_data_setup_dialog.cancel_button_text = "Quit"
|
||||
_data_setup_dialog.confirmed.connect(_use_default_data_root)
|
||||
_data_setup_dialog.canceled.connect(get_tree().quit)
|
||||
_data_setup_dialog.add_button(
|
||||
_data_setup_choose_button = _data_setup_dialog.add_button(
|
||||
"Choose Another Folder", false, "choose"
|
||||
)
|
||||
_data_setup_dialog.add_button(
|
||||
_data_setup_current_button = _data_setup_dialog.add_button(
|
||||
"Keep Current Location", false, "current"
|
||||
)
|
||||
_data_setup_dialog.custom_action.connect(_on_data_setup_action)
|
||||
|
|
@ -675,20 +677,54 @@ func _show_data_root_setup() -> void:
|
|||
% (default_path if not default_path.is_empty() else "Choose a folder")
|
||||
)
|
||||
_data_setup_dialog.ok_button_text = (
|
||||
"Move to Documents/NETFISHING" if legacy else "Use This Folder"
|
||||
(
|
||||
"Move to Documents/NETFISHING"
|
||||
if legacy else "Use This Folder"
|
||||
)
|
||||
if not default_path.is_empty()
|
||||
else "Keep Current Location"
|
||||
)
|
||||
_data_setup_current_button.visible = not default_path.is_empty()
|
||||
if not _data_root.error_message.is_empty():
|
||||
_data_setup_dialog.dialog_text = (
|
||||
"Your NETfishing data folder is unavailable.\n\n%s"
|
||||
% _data_root.error_message
|
||||
)
|
||||
_data_setup_dialog.popup_centered(Vector2i(640, 360))
|
||||
_focus_data_setup_actions.call_deferred()
|
||||
|
||||
|
||||
func _focus_data_setup_actions() -> void:
|
||||
if _data_setup_dialog == null or not _data_setup_dialog.visible:
|
||||
return
|
||||
var actions: Array[Button] = [
|
||||
_data_setup_dialog.get_ok_button(),
|
||||
_data_setup_choose_button,
|
||||
]
|
||||
if _data_setup_current_button.visible:
|
||||
actions.append(_data_setup_current_button)
|
||||
actions.append(_data_setup_dialog.get_cancel_button())
|
||||
for index: int in actions.size():
|
||||
var action: Button = actions[index]
|
||||
var previous: Button = actions[posmod(index - 1, actions.size())]
|
||||
var next: Button = actions[(index + 1) % actions.size()]
|
||||
action.focus_mode = Control.FOCUS_ALL
|
||||
action.focus_neighbor_left = action.get_path_to(previous)
|
||||
action.focus_neighbor_top = action.focus_neighbor_left
|
||||
action.focus_neighbor_right = action.get_path_to(next)
|
||||
action.focus_neighbor_bottom = action.focus_neighbor_right
|
||||
_data_setup_dialog.get_ok_button().grab_focus()
|
||||
|
||||
|
||||
func _use_default_data_root() -> void:
|
||||
var path: String = _data_root.default_visible_path()
|
||||
if path.is_empty():
|
||||
_show_folder_picker()
|
||||
_activate_selected_data_path(
|
||||
ProjectSettings.globalize_path(
|
||||
PlayerDataRoot.APP_DATA_PORTABLE_PATH
|
||||
),
|
||||
true,
|
||||
)
|
||||
return
|
||||
_activate_selected_data_path(path)
|
||||
|
||||
|
|
@ -737,7 +773,7 @@ func _open_folder_picker_after_modal(generation: int) -> void:
|
|||
if not _data_root.default_visible_path().is_empty()
|
||||
else OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
||||
)
|
||||
_data_folder_dialog.popup_centered_ratio(0.75)
|
||||
_interface_fonts.popup_file_dialog(_data_folder_dialog)
|
||||
|
||||
|
||||
func _on_data_folder_picker_canceled() -> void:
|
||||
|
|
@ -934,6 +970,9 @@ func _on_peer_identity_observed(_peer_id: int, status: String) -> void:
|
|||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if _handle_data_root_controller_input(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if _game_ui.is_controller_mapping_capturing():
|
||||
return
|
||||
if (
|
||||
|
|
@ -972,6 +1011,64 @@ func _input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _handle_data_root_controller_input(event: InputEvent) -> bool:
|
||||
var button_event := event as InputEventJoypadButton
|
||||
if button_event == null or not button_event.pressed:
|
||||
return false
|
||||
var setup_visible: bool = (
|
||||
_data_setup_dialog != null and _data_setup_dialog.visible
|
||||
)
|
||||
var picker_visible: bool = (
|
||||
_data_folder_dialog != null and _data_folder_dialog.visible
|
||||
)
|
||||
if not setup_visible and not picker_visible:
|
||||
return false
|
||||
var use_mapping: bool = _controller_mapping_manager.has_custom_mapping()
|
||||
var accept_pressed: bool = (
|
||||
_controller_mapping_manager.event_matches_role(
|
||||
event,
|
||||
ControllerMappingManagerType.ROLE_A,
|
||||
)
|
||||
if use_mapping
|
||||
else (
|
||||
button_event.button_index == JOY_BUTTON_A
|
||||
or event.is_action_pressed("ui_accept")
|
||||
)
|
||||
)
|
||||
var cancel_pressed: bool = (
|
||||
_controller_mapping_manager.event_matches_role(
|
||||
event,
|
||||
ControllerMappingManagerType.ROLE_B,
|
||||
)
|
||||
if use_mapping
|
||||
else (
|
||||
button_event.button_index == JOY_BUTTON_B
|
||||
or event.is_action_pressed("ui_cancel")
|
||||
)
|
||||
)
|
||||
if cancel_pressed:
|
||||
if picker_visible:
|
||||
_on_data_folder_picker_canceled()
|
||||
else:
|
||||
_data_setup_dialog.get_cancel_button().pressed.emit()
|
||||
return true
|
||||
if not accept_pressed:
|
||||
return false
|
||||
var focused: Control = (
|
||||
_data_folder_dialog.gui_get_focus_owner()
|
||||
if picker_visible
|
||||
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()
|
||||
return true
|
||||
if setup_visible:
|
||||
_data_setup_dialog.get_ok_button().pressed.emit()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _is_pause_open_request(event: InputEvent) -> bool:
|
||||
return (
|
||||
event.is_action_pressed("open_system_menu")
|
||||
|
|
|
|||
|
|
@ -72,7 +72,17 @@ func resolve() -> bool:
|
|||
|
||||
func default_visible_path() -> String:
|
||||
var documents: String = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
||||
return documents.path_join("NETFISHING") if not documents.is_empty() else ""
|
||||
if documents.is_empty():
|
||||
return ""
|
||||
var candidate: String = _normalize(documents.path_join("NETFISHING"))
|
||||
if not candidate.is_absolute_path():
|
||||
return ""
|
||||
var classification: StringName = classify_candidate_path(
|
||||
candidate,
|
||||
ProjectSettings.globalize_path("res://"),
|
||||
OS.get_executable_path().get_base_dir(),
|
||||
)
|
||||
return candidate if classification == PATH_ALLOWED else ""
|
||||
|
||||
|
||||
func select_new_root(path: String, app_data: bool = false) -> bool:
|
||||
|
|
@ -232,14 +242,21 @@ func _validate_candidate(path: String, create: bool) -> bool:
|
|||
var normalized: String = _normalize(path)
|
||||
if normalized.is_empty() or not normalized.is_absolute_path():
|
||||
return _fail("Choose an absolute filesystem folder.")
|
||||
var app_data_path: String = _normalize(
|
||||
ProjectSettings.globalize_path(APP_DATA_PORTABLE_PATH)
|
||||
)
|
||||
var exported_app_data: bool = (
|
||||
normalized == app_data_path
|
||||
and not OS.has_feature("editor")
|
||||
)
|
||||
var classification: StringName = classify_candidate_path(
|
||||
normalized,
|
||||
ProjectSettings.globalize_path("res://"),
|
||||
OS.get_executable_path().get_base_dir(),
|
||||
)
|
||||
if classification == PATH_SOURCE_PROJECT:
|
||||
if classification == PATH_SOURCE_PROJECT and not exported_app_data:
|
||||
return _fail("The project folder cannot be used as the player data folder.")
|
||||
if classification == PATH_INSTALLATION:
|
||||
if classification == PATH_INSTALLATION and not exported_app_data:
|
||||
return _fail(
|
||||
"The application installation folder cannot be used as the player data folder."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ class_name InterfaceFontController
|
|||
extends Node
|
||||
|
||||
const STANDARD_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf")
|
||||
const COMPACT_FILE_DIALOG_LIMIT := Vector2i(800, 600)
|
||||
const COMPACT_FILE_DIALOG_MARGIN := Vector2i(12, 12)
|
||||
const COMPACT_FILE_DIALOG_FONT_SIZE: int = 17
|
||||
|
||||
var _game_theme: Theme = preload("res://ui/game_theme.tres")
|
||||
var _utility_theme: Theme
|
||||
var _compact_file_dialog_theme: Theme
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -40,5 +44,66 @@ func apply_utility_theme(themed_node: Node) -> void:
|
|||
(themed_node as Window).theme = _utility_theme
|
||||
|
||||
|
||||
func popup_file_dialog(dialog: FileDialog) -> void:
|
||||
if dialog == null or not dialog.is_inside_tree():
|
||||
return
|
||||
var host_window: Window = dialog.get_parent().get_window()
|
||||
var host_size: Vector2i = host_window.size
|
||||
var compact: bool = (
|
||||
host_size.x <= COMPACT_FILE_DIALOG_LIMIT.x
|
||||
or host_size.y <= COMPACT_FILE_DIALOG_LIMIT.y
|
||||
)
|
||||
if not compact:
|
||||
apply_utility_theme(dialog)
|
||||
dialog.min_size = Vector2i(200, 70)
|
||||
dialog.max_size = Vector2i.ZERO
|
||||
dialog.popup_centered_ratio(0.75)
|
||||
return
|
||||
if _compact_file_dialog_theme == null:
|
||||
_compact_file_dialog_theme = _utility_theme.duplicate(true)
|
||||
_compact_file_dialog_theme.default_font_size = (
|
||||
COMPACT_FILE_DIALOG_FONT_SIZE
|
||||
)
|
||||
dialog.theme = _compact_file_dialog_theme
|
||||
var available_size := Vector2i(
|
||||
maxi(1, host_size.x - COMPACT_FILE_DIALOG_MARGIN.x * 2),
|
||||
maxi(1, host_size.y - COMPACT_FILE_DIALOG_MARGIN.y * 2),
|
||||
)
|
||||
dialog.min_size = Vector2i.ZERO
|
||||
dialog.max_size = available_size
|
||||
dialog.popup_centered(available_size)
|
||||
# FileDialog's desktop-oriented intrinsic minimum can exceed a compact
|
||||
# 4:3 display. The compact font keeps its controls usable while this final
|
||||
# clamp guarantees the embedded window cannot escape the physical screen.
|
||||
dialog.size = available_size
|
||||
if dialog.get_viewport().gui_embed_subwindows:
|
||||
dialog.position = (host_size - available_size) / 2
|
||||
_finalize_compact_file_dialog.call_deferred(
|
||||
dialog, host_size, available_size
|
||||
)
|
||||
|
||||
|
||||
func _finalize_compact_file_dialog(
|
||||
dialog: FileDialog,
|
||||
host_size: Vector2i,
|
||||
requested_size: Vector2i,
|
||||
) -> void:
|
||||
await get_tree().process_frame
|
||||
if dialog == null or not dialog.visible:
|
||||
return
|
||||
# Embedded windows report their content origin below the title bar. Account
|
||||
# for that inset after Godot has laid the window out so its bottom and right
|
||||
# edges retain the same safe margin as its decorated top and left edges.
|
||||
var safe_bottom_right: Vector2i = host_size - COMPACT_FILE_DIALOG_MARGIN
|
||||
var fitted_size: Vector2i = requested_size
|
||||
fitted_size.x -= maxi(
|
||||
0, dialog.position.x + fitted_size.x - safe_bottom_right.x
|
||||
)
|
||||
fitted_size.y -= maxi(
|
||||
0, dialog.position.y + fitted_size.y - safe_bottom_right.y
|
||||
)
|
||||
dialog.size = Vector2i(maxi(1, fitted_size.x), maxi(1, fitted_size.y))
|
||||
|
||||
|
||||
func readable_font() -> Font:
|
||||
return STANDARD_FONT
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ func _choose_data_folder() -> void:
|
|||
_interface_fonts.apply_utility_theme(_data_folder_dialog)
|
||||
add_child(_data_folder_dialog)
|
||||
_data_folder_dialog.current_dir = _data_root.root_path.get_base_dir()
|
||||
_data_folder_dialog.popup_centered_ratio(0.75)
|
||||
_interface_fonts.popup_file_dialog(_data_folder_dialog)
|
||||
|
||||
|
||||
func _change_data_folder(path: String) -> void:
|
||||
|
|
@ -540,7 +540,7 @@ func _choose_identity_export(identity_type: String) -> void:
|
|||
add_child(_export_file_dialog)
|
||||
_export_file_dialog.current_dir = suggested.get_base_dir()
|
||||
_export_file_dialog.current_file = suggested.get_file()
|
||||
_export_file_dialog.popup_centered_ratio(0.75)
|
||||
_interface_fonts.popup_file_dialog(_export_file_dialog)
|
||||
|
||||
|
||||
func _identity_export_file_selected(path: String) -> void:
|
||||
|
|
@ -565,7 +565,7 @@ func _choose_identity_import(identity_type: String) -> void:
|
|||
_interface_fonts.apply_utility_theme(_backup_file_dialog)
|
||||
add_child(_backup_file_dialog)
|
||||
_backup_file_dialog.current_dir = _data_root.identity_backup_directory()
|
||||
_backup_file_dialog.popup_centered_ratio(0.75)
|
||||
_interface_fonts.popup_file_dialog(_backup_file_dialog)
|
||||
|
||||
|
||||
func _identity_import_file_selected(path: String) -> void:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue