From b18d9392d89f63b3f658f4589438bacdc5e6d93f Mon Sep 17 00:00:00 2001 From: Voyager Date: Thu, 30 Jul 2026 10:46:15 -0400 Subject: [PATCH] Unify menu presentation and fix UI regressions --- export_presets.cfg | 4 +- main/main.gd | 16 +-- main/main.tscn | 20 +++- network/identity_backup_service.gd | 2 +- network/network_protocol.gd | 2 +- network/network_session.gd | 2 +- network/player_data_root.gd | 14 +-- network/portable_data_migration.gd | 12 +-- player/player.gd | 7 +- player/player_visual_presenter.gd | 28 +++++ player/player_visual_presenter.gd.uid | 1 + playtest/README-PLAYTEST.txt | 4 +- ui/chat_ui.gd | 3 + ui/game_ui.gd | 7 -- ui/mail_page.gd | 36 +++++-- ui/network/join_game_page.gd | 36 +++++-- ui/network/join_game_page.tscn | 91 +++------------- ui/pause_menu.gd | 41 +++---- ui/pause_menu.tscn | 18 +--- ui/player_menu.gd | 48 ++++----- ui/player_menu.tscn | 23 +--- ui/players_page.gd | 31 ++++-- ui/profile_page.gd | 75 +++++++------ ui/profile_preview.gd | 15 ++- ui/profile_preview.tscn | 20 +--- ui/settings/settings_bubble_page.gd | 35 +++--- ui/settings_panel.gd | 106 ++++++++++++------ ui/settings_panel.tscn | 22 ++-- ui/title_confirmation_bubble_page.gd | 13 ++- ui/title_screen.gd | 52 ++++++--- ui/title_screen.tscn | 1 + ui/ui_motion.gd | 12 +++ ui/ui_motion.gd.uid | 1 + ui/utility_page_style.gd | 150 ++++++++++++++++++++++++++ ui/utility_page_style.gd.uid | 1 + 35 files changed, 588 insertions(+), 361 deletions(-) create mode 100644 player/player_visual_presenter.gd create mode 100644 player/player_visual_presenter.gd.uid create mode 100644 ui/ui_motion.gd create mode 100644 ui/ui_motion.gd.uid create mode 100644 ui/utility_page_style.gd create mode 100644 ui/utility_page_style.gd.uid diff --git a/export_presets.cfg b/export_presets.cfg index e01b43e..88ebda9 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -38,8 +38,8 @@ application/icon_interpolation=4 application/file_version="0.2.0.2" application/product_version="0.2.0.2" application/company_name="" -application/product_name="NETFISHING" -application/file_description="NETFISHING Pre-Alpha Playtest 0.2" +application/product_name="NETfishing" +application/file_description="NETfishing Pre-Alpha Playtest 0.2" application/copyright="" application/trademarks="" application/export_angle=0 diff --git a/main/main.gd b/main/main.gd index d8e86e8..0b37542 100644 --- a/main/main.gd +++ b/main/main.gd @@ -132,6 +132,7 @@ const TITLE_MUSIC_SILENCE_DB: float = -80.0 %NetworkProfileService ) @onready var _players_root: Node3D = $Players +@onready var _title_background: ColorRect = %TitleBackground var _gameplay_started: bool = false var _shop_interaction: FishingShopInteractionType @@ -154,6 +155,7 @@ var _pending_existing_root_path := "" func _ready() -> void: + DisplayServer.window_set_title("NETfishing") if not _settings_manager.settings_changed.is_connected( _apply_runtime_settings ): @@ -486,7 +488,7 @@ func _initialize_after_data_root() -> void: func _show_data_root_setup() -> void: if _data_setup_dialog == null: _data_setup_dialog = ConfirmationDialog.new() - _data_setup_dialog.title = "NETFISHING player data" + _data_setup_dialog.title = "NETfishing player data" _data_setup_dialog.ok_button_text = "Use This Folder" _data_setup_dialog.cancel_button_text = "Quit" _data_setup_dialog.confirmed.connect(_use_default_data_root) @@ -503,8 +505,8 @@ func _show_data_root_setup() -> void: var default_path: String = _data_root.default_visible_path() var legacy: bool = PortableDataMigration.legacy_files_present() _data_setup_dialog.dialog_text = ( - ("Move your existing NETFISHING data to an easy-to-find folder." - if legacy else "Choose where NETFISHING stores your player data.") + ("Move your existing NETfishing data to an easy-to-find folder." + if legacy else "Choose where NETfishing stores your player data.") + "\n\n%s\n\nYou can choose a Syncthing folder." % (default_path if not default_path.is_empty() else "Choose a folder") ) @@ -513,7 +515,7 @@ func _show_data_root_setup() -> void: ) if not _data_root.error_message.is_empty(): _data_setup_dialog.dialog_text = ( - "Your NETFISHING data folder is unavailable.\n\n%s" + "Your NETfishing data folder is unavailable.\n\n%s" % _data_root.error_message ) _data_setup_dialog.popup_centered(Vector2i(640, 360)) @@ -621,11 +623,11 @@ func _activate_selected_data_path(path: String, app_data: bool = false) -> void: func _show_existing_root_choice(path: String) -> void: _pending_existing_root_path = path var dialog: ConfirmationDialog = ConfirmationDialog.new() - dialog.title = "Existing NETFISHING data" + dialog.title = "Existing NETfishing data" dialog.ok_button_text = "Use Data Already in Selected Folder" dialog.cancel_button_text = "Cancel" dialog.dialog_text = ( - "This folder already contains NETFISHING player data.\n\n" + "This folder already contains NETfishing player data.\n\n" + "Choose which complete data set to use. Data is never merged automatically." ) dialog.add_button( @@ -869,12 +871,14 @@ func _reset_pixelation() -> void: func _set_gameplay_active(active: bool) -> void: _gameplay_started = active + _title_background.visible = not active _world_pixelation.set_gameplay_active(active) _ui_pixelation.set_gameplay_active(active) if not active: _player.item_effects.reset_all() _player.set_movement_enabled(active) _player.set_camera_input_enabled(active) + _player.set_camera_active(active) _fishing_spot.set_gameplay_input_enabled(active) _water_recovery.set_recovery_enabled(active) _game_ui.set_gameplay_ui_enabled(active) diff --git a/main/main.tscn b/main/main.tscn index 89c988f..6131033 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=40 format=3] +[gd_scene load_steps=42 format=3] [ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"] [ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"] @@ -39,6 +39,10 @@ [ext_resource type="Script" path="res://network/player_data_root.gd" id="37_data_root"] [ext_resource type="Script" path="res://network/identity_backup_service.gd" id="38_identity_backup"] [ext_resource type="Script" path="res://ui/interface_font_controller.gd" id="39_fonts"] +[ext_resource type="Shader" path="res://ui/title_water_background.gdshader" id="40_title_water"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"] +shader = ExtResource("40_title_water") [node name="Main" type="Node3D"] script = ExtResource("3_main") @@ -47,6 +51,20 @@ pelican_buyer_profile = ExtResource("7_pelicans") main_shop_buyer_profile = ExtResource("12_main_shop") item_catalog = ExtResource("11_items") +[node name="TitleBackgroundLayer" type="CanvasLayer" parent="."] +unique_name_in_owner = true +layer = -1 + +[node name="TitleBackground" type="ColorRect" parent="TitleBackgroundLayer"] +unique_name_in_owner = true +material = SubResource("ShaderMaterial_title_water_native") +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 + [node name="InterfaceFontController" type="Node" parent="."] unique_name_in_owner = true script = ExtResource("39_fonts") diff --git a/network/identity_backup_service.gd b/network/identity_backup_service.gd index bceb4bb..ff7396c 100644 --- a/network/identity_backup_service.gd +++ b/network/identity_backup_service.gd @@ -189,7 +189,7 @@ func import_backup( ) _finish( bool(result.get("ok", false)), - "Identity imported. Restart NETFISHING before multiplayer." + "Identity imported. Restart NETfishing before multiplayer." if bool(result.get("ok", false)) else "Could not install this identity backup.", ) diff --git a/network/network_protocol.gd b/network/network_protocol.gd index a8672f5..e52705f 100644 --- a/network/network_protocol.gd +++ b/network/network_protocol.gd @@ -177,7 +177,7 @@ static func make_server_hello( "protocol_version": PROTOCOL_VERSION, "session_id": session_id, "assigned_peer_id": assigned_peer_id, - "server_display_name": "NETFISHING", + "server_display_name": "NETfishing", "player_count": player_count, "max_players": max_players, "capability_flags": PackedStringArray([ diff --git a/network/network_session.gd b/network/network_session.gd index 1538550..71cb003 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -787,7 +787,7 @@ func resolve_server_trust(accepted: bool) -> void: if not _server_trust.trust( get_current_endpoint(), str(_pending_server_proof["server_fingerprint"]), - "NETFISHING", + "NETfishing", ): _fail_identity("The server identity could not be pinned.") return diff --git a/network/player_data_root.gd b/network/player_data_root.gd index 9e379d2..37983fd 100644 --- a/network/player_data_root.gd +++ b/network/player_data_root.gd @@ -86,16 +86,16 @@ func select_new_root(path: String, app_data: bool = false) -> bool: if FileAccess.file_exists(manifest_path): var manifest: Dictionary = _read_json(manifest_path) if not _valid_manifest(manifest): - return _fail("The selected folder has a malformed NETFISHING manifest.") + return _fail("The selected folder has a malformed NETfishing manifest.") root_id = str(manifest["root_id"]) else: if not _directory_is_empty(normalized) and not app_data: return _fail( - "Choose an empty folder or an existing NETFISHING data folder." + "Choose an empty folder or an existing NETfishing data folder." ) root_id = Crypto.new().generate_random_bytes(16).hex_encode() if not _create_layout(normalized, root_id): - return _fail("The NETFISHING data folder could not be created.") + return _fail("The NETfishing data folder could not be created.") if not _write_bootstrap(normalized, root_id): return _fail("The data-folder pointer could not be saved.") root_path = normalized @@ -207,15 +207,15 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool return false var manifest_path: String = normalized.path_join(MANIFEST_FILENAME) if not FileAccess.file_exists(manifest_path): - return _fail("The selected folder is not a NETFISHING data folder.") + return _fail("The selected folder is not a NETfishing data folder.") var manifest: Dictionary = _read_json(manifest_path) if not _valid_manifest(manifest): - return _fail("The NETFISHING data-folder manifest is malformed.") + return _fail("The NETfishing data-folder manifest is malformed.") var found_id: String = str(manifest["root_id"]) if not expected_id.is_empty() and expected_id != found_id: return _fail("The selected data folder does not match this device pointer.") if not _test_writable(normalized): - return _fail("The NETFISHING data folder is unavailable or unwritable.") + return _fail("The NETfishing data folder is unavailable or unwritable.") root_path = normalized root_id = found_id requires_selection = false @@ -270,7 +270,7 @@ func _create_layout(path: String, id: String) -> bool: ): return false var readme: String = ( - "NETFISHING player data\n\n" + "NETfishing player data\n\n" + "This folder is safe to synchronize with tools such as Syncthing.\n" + "Active private identity keys remain device-local.\n" + "Encrypted identity backups require their passphrase.\n" diff --git a/network/portable_data_migration.gd b/network/portable_data_migration.gd index 45ce7fb..ab8f21d 100644 --- a/network/portable_data_migration.gd +++ b/network/portable_data_migration.gd @@ -33,12 +33,12 @@ static func migrate_legacy_to( return { "ok": false, "requires_existing_root_decision": true, - "message": "The selected folder already contains NETFISHING data.", + "message": "The selected folder already contains NETfishing data.", } if not _directory_empty(normalized): return { "ok": false, - "message": "Choose an empty folder or create a NETFISHING subfolder.", + "message": "Choose an empty folder or create a NETfishing subfolder.", } var staging: String = "%s.migration-%s" % [ normalized, Crypto.new().generate_random_bytes(8).hex_encode() @@ -107,11 +107,11 @@ static func migrate_active_to( "ok": false, "requires_existing_root_decision": true, "message": ( - "The selected folder already contains NETFISHING data. " + "The selected folder already contains NETfishing data. " + "Choose it through the existing-data recovery flow." ), } - return {"ok": false, "message": "Choose an empty folder or a NETFISHING subfolder."} + return {"ok": false, "message": "Choose an empty folder or a NETfishing subfolder."} var staging: String = "%s.migration-%s" % [ normalized, Crypto.new().generate_random_bytes(8).hex_encode() ] @@ -188,7 +188,7 @@ static func replace_existing_with_legacy( var normalized: String = destination.simplify_path().trim_suffix("/") var manifest: String = normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME) if not FileAccess.file_exists(manifest): - return {"ok": false, "message": "The selected folder is not a NETFISHING data root."} + return {"ok": false, "message": "The selected folder is not a NETfishing data root."} var recovery: String = ProjectSettings.globalize_path( "user://migration-recovery" ).path_join( @@ -209,7 +209,7 @@ static func replace_existing_with_active( ) -> Dictionary: var normalized: String = destination.simplify_path().trim_suffix("/") if not FileAccess.file_exists(normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)): - return {"ok": false, "message": "The selected folder is not a NETFISHING data root."} + return {"ok": false, "message": "The selected folder is not a NETfishing data root."} var recovery: String = ProjectSettings.globalize_path( "user://migration-recovery" ).path_join( diff --git a/player/player.gd b/player/player.gd index e735557..d6662b2 100644 --- a/player/player.gd +++ b/player/player.gd @@ -26,8 +26,7 @@ var appearance_snapshot: Dictionary = ( func apply_appearance_snapshot(snapshot: Dictionary) -> void: if CharacterCustomizationCatalog.validate_snapshot(snapshot): appearance_snapshot = snapshot.duplicate(true) - # Current gameplay art is monolithic. This seam intentionally stores the - # validated state until modular visual parts are available. + PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot) class ShowcaseCameraSnapshot: extends RefCounted @@ -495,6 +494,10 @@ func set_camera_input_enabled(enabled: bool) -> void: _camera_dragging = false +func set_camera_active(active: bool) -> void: + _camera.current = active + + func apply_camera_settings( new_mouse_sensitivity: float, new_controller_sensitivity: float, diff --git a/player/player_visual_presenter.gd b/player/player_visual_presenter.gd new file mode 100644 index 0000000..caddfe8 --- /dev/null +++ b/player/player_visual_presenter.gd @@ -0,0 +1,28 @@ +class_name PlayerVisualPresenter +extends RefCounted + +const PlayerScene: PackedScene = preload("res://player/player.tscn") + + +static func instantiate_visuals() -> Node3D: + var source: Player = PlayerScene.instantiate() + var visuals := source.get_node("Visuals") as Node3D + var presentation := visuals.duplicate( + Node.DUPLICATE_SIGNALS + | Node.DUPLICATE_GROUPS + | Node.DUPLICATE_SCRIPTS + | Node.DUPLICATE_USE_INSTANTIATION + ) as Node3D + source.free() + presentation.name = "PlayerVisuals" + return presentation + + +static func apply_appearance( + _visuals: Node3D, + snapshot: Dictionary, +) -> void: + # Gameplay and preview deliberately share this presentation seam. Modular + # visual parts can be applied here without coupling Profile to Player. + if not CharacterCustomizationCatalog.validate_snapshot(snapshot): + return diff --git a/player/player_visual_presenter.gd.uid b/player/player_visual_presenter.gd.uid new file mode 100644 index 0000000..f67f8da --- /dev/null +++ b/player/player_visual_presenter.gd.uid @@ -0,0 +1 @@ +uid://b4w23asm85rtu diff --git a/playtest/README-PLAYTEST.txt b/playtest/README-PLAYTEST.txt index f781b31..58d94c6 100644 --- a/playtest/README-PLAYTEST.txt +++ b/playtest/README-PLAYTEST.txt @@ -1,10 +1,10 @@ -NETFISHING +NETfishing Pre-Alpha Playtest 0.2 Version 0.2.0-prealpha.2 Thank you for trying this early private playtest. -NETFISHING is currently a local gameplay prototype for a future +NETfishing is currently a local gameplay prototype for a future multiplayer-first game. Networking and multiplayer are NOT implemented in this build. diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index d9bed6b..d35c8a7 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -96,6 +96,9 @@ func _unhandled_input(event: InputEvent) -> void: ): open_chat() get_viewport().set_input_as_handled() + elif event.keycode == KEY_T and not _opened and _available: + open_chat() + get_viewport().set_input_as_handled() elif event.keycode == KEY_ESCAPE and _opened: close_chat() get_viewport().set_input_as_handled() diff --git a/ui/game_ui.gd b/ui/game_ui.gd index acd54c4..8bd2589 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -72,9 +72,6 @@ var _item_effects: PlayerItemEffectsType func _ready() -> void: - _pause_menu.chat_requested.connect( - func() -> void: call_deferred("_open_chat_from_pause") - ) _title_settings_panel.panel_visibility_changed.connect( _on_settings_visibility_changed ) @@ -89,10 +86,6 @@ func _ready() -> void: ) -func _open_chat_from_pause() -> void: - _chat_ui.open_chat() - - func setup( player: PlayerType, inventory: FishInventoryType, diff --git a/ui/mail_page.gd b/ui/mail_page.gd index 55d1583..08cedd4 100644 --- a/ui/mail_page.gd +++ b/ui/mail_page.gd @@ -49,6 +49,7 @@ var _current_mail_id := "" func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + UtilityPageStyle.apply_page(self) _build_ui() @@ -75,6 +76,7 @@ func setup( func activate() -> void: _show_inbox() + UtilityPageStyle.animate_in(self) call_deferred("_focus_first") @@ -103,14 +105,7 @@ func _build_ui() -> void: var paper := PanelContainer.new() paper.position = Vector2(116, 126) paper.size = Vector2(1048, 470) - var style := StyleBoxFlat.new() - style.bg_color = Color("f3ecd7") - style.border_color = Color("665a46") - style.set_border_width_all(4) - style.set_corner_radius_all(18) - style.shadow_color = Color(0.05, 0.04, 0.03, 0.42) - style.shadow_size = 7 - paper.add_theme_stylebox_override("panel", style) + paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style()) add_child(paper) var margin := MarginContainer.new() margin.add_theme_constant_override("margin_left", 34) @@ -132,13 +127,14 @@ func _build_ui() -> void: _status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _status.add_theme_color_override("font_color", Color("4a3f31")) root.add_child(_status) + _style_controls(root) _show_inbox() func _build_inbox() -> Control: var page := Control.new() var title := Label.new() - title.text = "session mail" + title.text = "Mail" title.position = Vector2(12, 0) title.size = Vector2(500, 42) title.add_theme_font_size_override("font_size", 30) @@ -367,6 +363,7 @@ func _refresh_inbox() -> void: button.custom_minimum_size = Vector2(930, 54) button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.pressed.connect(_open_letter.bind(str(letter["mail_id"]))) + UtilityPageStyle.apply_button(button) _inbox_list.add_child(button) @@ -601,3 +598,24 @@ func _focus_first() -> void: var buttons := _inbox_list.find_children("*", "Button", true, false) if not buttons.is_empty(): (buttons.front() as Button).grab_focus() + + +func _style_controls(root: Node) -> void: + for node: Node in root.find_children("*", "", true, false): + if node is BaseButton: + UtilityPageStyle.apply_button(node) + elif node is LineEdit: + UtilityPageStyle.apply_line_edit(node) + elif node is Label: + node.add_theme_color_override( + "font_color", UtilityPageStyle.INK + ) + elif node is TextEdit: + node.add_theme_font_override("font", UtilityPageStyle.TuffyFont) + node.add_theme_color_override( + "font_color", UtilityPageStyle.LIGHT_TEXT + ) + node.add_theme_color_override( + "font_placeholder_color", + Color(0.82, 0.80, 0.71, 0.84) + ) diff --git a/ui/network/join_game_page.gd b/ui/network/join_game_page.gd index b05e6f6..94ba695 100644 --- a/ui/network/join_game_page.gd +++ b/ui/network/join_game_page.gd @@ -26,17 +26,17 @@ const DIRECT_WORKFLOW_HELP: String = ( @onready var _name_helper: Label = %NameHelper @onready var _server_list: ItemList = %ServerList @onready var _details: Label = %Details -@onready var _direct_button: BubbleButton = %DirectButton -@onready var _saved_button: BubbleButton = %SavedButton -@onready var _recent_button: BubbleButton = %RecentButton -@onready var _join_button: BubbleButton = %JoinButton -@onready var _save_button: BubbleButton = %SaveButton -@onready var _edit_button: BubbleButton = %EditButton -@onready var _favorite_button: BubbleButton = %FavoriteButton -@onready var _delete_button: BubbleButton = %DeleteButton -@onready var _cancel_button: BubbleButton = %CancelButton -@onready var _back_button: BubbleButton = %BackButton -@onready var _open_close_button: BubbleButton = %OpenCloseButton +@onready var _direct_button: Button = %DirectButton +@onready var _saved_button: Button = %SavedButton +@onready var _recent_button: Button = %RecentButton +@onready var _join_button: Button = %JoinButton +@onready var _save_button: Button = %SaveButton +@onready var _edit_button: Button = %EditButton +@onready var _favorite_button: Button = %FavoriteButton +@onready var _delete_button: Button = %DeleteButton +@onready var _cancel_button: Button = %CancelButton +@onready var _back_button: Button = %BackButton +@onready var _open_close_button: Button = %OpenCloseButton @onready var _status: Label = %Status @onready var _session_summary: Label = %SessionSummary @onready var _delete_confirmation: BubbleConfirmationPage = ( @@ -56,6 +56,19 @@ var _delete_armed: bool = false func _ready() -> void: + UtilityPageStyle.apply_page(self) + var paper := get_node("Paper") as PanelContainer + paper.add_theme_stylebox_override( + "panel", UtilityPageStyle.panel_style() + ) + for button: BaseButton in [ + _direct_button, _saved_button, _recent_button, _join_button, + _save_button, _edit_button, _favorite_button, _delete_button, + _cancel_button, _back_button, _open_close_button, + ]: + UtilityPageStyle.apply_button(button) + UtilityPageStyle.apply_line_edit(_address) + UtilityPageStyle.apply_line_edit(_name_edit) _direct_button.pressed.connect(_set_mode.bind(Mode.DIRECT)) _saved_button.pressed.connect(_set_mode.bind(Mode.SAVED)) _recent_button.pressed.connect(_set_mode.bind(Mode.RECENT)) @@ -126,6 +139,7 @@ func open_page(preserved_endpoint: String = "") -> void: elif _address.text.is_empty(): _address.text = "127.0.0.1:7777" show() + UtilityPageStyle.animate_in(self) _set_mode(_mode) if _mode == Mode.DIRECT: _address.grab_focus() diff --git a/ui/network/join_game_page.tscn b/ui/network/join_game_page.tscn index 5c09840..268d420 100644 --- a/ui/network/join_game_page.tscn +++ b/ui/network/join_game_page.tscn @@ -1,8 +1,7 @@ -[gd_scene load_steps=13 format=3] +[gd_scene load_steps=12 format=3] [ext_resource type="Script" path="res://ui/network/join_game_page.gd" id="1_script"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] -[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_button.tscn" id="3_bubble"] [ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="4_confirmation"] [sub_resource type="StyleBoxFlat" id="StyleBox_paper"] @@ -128,41 +127,23 @@ layout_mode = 2 theme_override_constants/separation = 14 alignment = 1 -[node name="DirectButton" parent="Paper/Margin/Layout/Modes" instance=ExtResource("3_bubble")] +[node name="DirectButton" type="Button" parent="Paper/Margin/Layout/Modes"] unique_name_in_owner = true custom_minimum_size = Vector2(100, 72) layout_mode = 2 text = "direct" -neutral_size = Vector2(100, 72) -minimum_font_size = 16 -maximum_font_size = 19 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="SavedButton" parent="Paper/Margin/Layout/Modes" instance=ExtResource("3_bubble")] +[node name="SavedButton" type="Button" parent="Paper/Margin/Layout/Modes"] unique_name_in_owner = true custom_minimum_size = Vector2(100, 72) layout_mode = 2 text = "saved" -neutral_size = Vector2(100, 72) -minimum_font_size = 16 -maximum_font_size = 19 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="RecentButton" parent="Paper/Margin/Layout/Modes" instance=ExtResource("3_bubble")] +[node name="RecentButton" type="Button" parent="Paper/Margin/Layout/Modes"] unique_name_in_owner = true custom_minimum_size = Vector2(100, 72) layout_mode = 2 text = "recent" -neutral_size = Vector2(100, 72) -minimum_font_size = 16 -maximum_font_size = 19 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 [node name="AddressLabel" type="Label" parent="Paper/Margin/Layout"] unique_name_in_owner = true @@ -290,106 +271,58 @@ layout_mode = 2 theme_override_constants/separation = 7 alignment = 1 -[node name="JoinButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="JoinButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true custom_minimum_size = Vector2(82, 72) layout_mode = 2 text = "join" -neutral_size = Vector2(82, 72) -minimum_font_size = 15 -maximum_font_size = 18 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="SaveButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="SaveButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true custom_minimum_size = Vector2(82, 72) layout_mode = 2 text = "save\nserver" -neutral_size = Vector2(82, 72) -minimum_font_size = 14 -maximum_font_size = 17 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="EditButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="EditButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true visible = false custom_minimum_size = Vector2(76, 68) layout_mode = 2 text = "edit" -neutral_size = Vector2(76, 68) -minimum_font_size = 14 -maximum_font_size = 17 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="FavoriteButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="FavoriteButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true visible = false custom_minimum_size = Vector2(88, 68) layout_mode = 2 text = "favorite" -neutral_size = Vector2(88, 68) -minimum_font_size = 13 -maximum_font_size = 16 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="DeleteButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="DeleteButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true visible = false custom_minimum_size = Vector2(82, 68) layout_mode = 2 text = "delete" -neutral_size = Vector2(82, 68) -minimum_font_size = 13 -maximum_font_size = 16 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="OpenCloseButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="OpenCloseButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true visible = false custom_minimum_size = Vector2(88, 72) layout_mode = 2 text = "open\ngame" -neutral_size = Vector2(88, 72) -minimum_font_size = 14 -maximum_font_size = 17 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="CancelButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="CancelButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true visible = false custom_minimum_size = Vector2(76, 68) layout_mode = 2 text = "cancel" -neutral_size = Vector2(76, 68) -minimum_font_size = 14 -maximum_font_size = 17 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 -[node name="BackButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")] +[node name="BackButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true custom_minimum_size = Vector2(76, 68) layout_mode = 2 text = "back" -neutral_size = Vector2(76, 68) -minimum_font_size = 14 -maximum_font_size = 17 -horizontal_amplitude = 0.0 -vertical_amplitude = 0.0 -deformation_amplitude = 0.0 [node name="DeleteConfirmation" parent="." instance=ExtResource("4_confirmation")] unique_name_in_owner = true diff --git a/ui/pause_menu.gd b/ui/pause_menu.gd index 6f34d7e..15621e9 100644 --- a/ui/pause_menu.gd +++ b/ui/pause_menu.gd @@ -5,8 +5,6 @@ const INPUT_OWNER: StringName = &"game_menu" const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0) const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0) const COMPACT_HEIGHT_THRESHOLD: float = 560.0 -const PAGE_OUTGOING_DURATION: float = 1.70 -const PAGE_INCOMING_DURATION: float = 1.85 const VISIBILITY_FADE_DURATION: float = 0.55 const BACKDROP_TARGET_ALPHA: float = 0.68 const PlayerType = preload("res://player/player.gd") @@ -28,7 +26,6 @@ signal reset_progress_requested signal quit_requested signal menu_visibility_changed(is_open: bool) signal join_game_requested(endpoint: String) -signal chat_requested enum ConfirmationAction { NONE, @@ -88,7 +85,6 @@ func _ready() -> void: _save_button.pressed.connect(_save_now) %SettingsButton.pressed.connect(_open_settings) %JoinGameButton.pressed.connect(_open_join_game) - %ChatButton.pressed.connect(_request_chat) %ReturnToTitleButton.pressed.connect(_confirm_return_to_title) %ResetProgressButton.pressed.connect(_confirm_reset_progress) %QuitButton.pressed.connect(_request_quit) @@ -96,6 +92,7 @@ func _ready() -> void: _confirmation_page.cancelled.connect(_close_confirmation) _settings_panel.applied.connect(_on_settings_applied) _settings_panel.closed.connect(_on_settings_closed) + _settings_panel.closing.connect(_on_settings_closing) _settings_panel.navigation_transition_started.connect( _emit_transition_flurry ) @@ -107,13 +104,6 @@ func _ready() -> void: call_deferred("_update_responsive_pause_stage") -func _request_chat() -> void: - if not visible or _action_in_progress or _root_transition_active: - return - close_menu(CloseReason.USER_RETURN, true) - chat_requested.emit() - - func setup( player: PlayerType, save_manager: SaveManagerType, @@ -287,13 +277,20 @@ func _on_settings_applied() -> void: if _closing_menu: return _feedback.text = "settings saved." - _begin_root_entry(true) func _on_settings_closed() -> void: + pass + + +func _on_settings_closing() -> void: if _closing_menu: return - _begin_root_entry(true) + await get_tree().create_timer( + UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY + ).timeout + if _settings_panel.visible: + _begin_root_entry(true) func _confirm_return_to_title() -> void: @@ -364,7 +361,7 @@ func _open_confirmation( func _show_confirmation() -> void: _confirmation_page.transition_in( - PAGE_INCOMING_DURATION, + 0.0, _finish_confirmation_open ) @@ -379,7 +376,7 @@ func _close_confirmation() -> void: _confirmation_action = ConfirmationAction.NONE _emit_transition_flurry() _confirmation_page.transition_out( - PAGE_OUTGOING_DURATION, + 0.0, _finish_confirmation_cancel ) @@ -448,7 +445,7 @@ func _begin_root_entry(flurry_already_emitted: bool) -> void: _root_page.transition_in( true, _finish_root_entry.bind(generation), - PAGE_INCOMING_DURATION + 0.0 ) @@ -466,19 +463,23 @@ func _begin_root_exit(completed: Callable) -> void: _emit_transition_flurry() var generation: int = _root_transition_generation _root_page.transition_out( - _finish_root_exit.bind(generation, completed), - PAGE_OUTGOING_DURATION + _finish_root_exit.bind(generation), + 0.0 ) + await get_tree().create_timer( + UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY + ).timeout + if generation != _root_transition_generation or not visible: + return + completed.call() func _finish_root_exit( generation: int, - completed: Callable, ) -> void: if generation != _root_transition_generation or not visible: return _root_transition_active = false - completed.call() func _finish_user_close() -> void: diff --git a/ui/pause_menu.tscn b/ui/pause_menu.tscn index c896d4c..4bdaa9c 100644 --- a/ui/pause_menu.tscn +++ b/ui/pause_menu.tscn @@ -77,16 +77,14 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("4_page") page_id = &"pause" -bubble_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/ChatButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")]) -focus_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/ChatButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")]) +bubble_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")]) +focus_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")]) initial_focus_path = NodePath("BubbleCluster/ResumeButton") back_focus_path = NodePath("BubbleCluster/ResumeButton") maximum_layout_size = Vector2(720, 520) compact_maximum_layout_size = Vector2(544, 400) compact_width_threshold = 680.0 compact_height_threshold = 500.0 -outgoing_rise_duration = 1.7 -incoming_rise_duration = 1.85 [node name="BubbleCluster" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage"] layout_mode = 0 @@ -135,18 +133,6 @@ minimum_font_size = 16 maximum_font_size = 23 motion_phase = 2.05 -[node name="ChatButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 0) -text = "chat" -neutral_size = Vector2(112, 106) -desktop_anchor = Vector2(620, 255) -compact_anchor = Vector2(595, 270) -compact_minimum_size = Vector2(92, 88) -minimum_font_size = 15 -maximum_font_size = 20 -motion_phase = 2.3 - [node name="JoinGameButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] unique_name_in_owner = true custom_minimum_size = Vector2(0, 0) diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 243a858..ba64f0c 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -62,8 +62,6 @@ const LogbookEntryScene = preload( "res://ui/components/bubble_menu/logbook_entry.tscn" ) -const MENU_ENTER_DURATION: float = 0.58 -const MENU_EXIT_DURATION: float = 0.52 const PAGE_OUT_DURATION: float = 0.34 const PAGE_IN_DURATION: float = 0.42 const LOGBOOK_PAGE_DURATION: float = 0.18 @@ -150,7 +148,6 @@ enum CloseReason { @onready var _bag_host: Control = %BagHost @onready var _bag_item_field: Control = %BagItemField @onready var _bag_empty_state: Label = %BagEmptyState -@onready var _bag_instruction_bubble: PanelContainer = %BagInstructionBubble @onready var _bag_detail_constellation: Control = %BagDetailConstellation @onready var _bag_sprite_detail_bubble: Control = %BagDetailBubble @onready var _bag_sprite_detail_texture: TextureRect = %BagSpriteDetailTexture @@ -913,7 +910,6 @@ func _apply_bag_styles() -> void: bubble.content_margin_top = 9.0 bubble.content_margin_right = 12.0 bubble.content_margin_bottom = 9.0 - _bag_instruction_bubble.add_theme_stylebox_override("panel", bubble) _bag_sprite_detail_bubble.add_theme_stylebox_override( "panel", bubble.duplicate(), @@ -1133,7 +1129,7 @@ func _update_shell_layout() -> void: _bag_page.position = Vector2.ZERO _bag_rest_position = Vector2.ZERO _bag_outer_wall.position = ( - Vector2(14.0, 170.0) if compact else Vector2(72.0, 132.0) + Vector2(14.0, 170.0) if compact else Vector2(122.0, 132.0) ) _bag_outer_wall.size = ( Vector2(612.0, 200.0) if compact else Vector2(896.0, 442.0) @@ -1142,12 +1138,6 @@ func _update_shell_layout() -> void: Vector2(520.0, 140.0) if compact else Vector2(820.0, 350.0) ) _bag_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED - _bag_instruction_bubble.position = ( - Vector2(218.0, 145.0) if compact else Vector2(992.0, 140.0) - ) - _bag_instruction_bubble.size = ( - Vector2(204.0, 48.0) if compact else Vector2(240.0, 98.0) - ) _bag_detail_constellation.position = ( Vector2.ZERO if compact else Vector2(984.0, 266.0) ) @@ -1335,48 +1325,49 @@ func _begin_menu_entry() -> void: _profile_page.position = _profile_rest_position + Vector2.DOWN * start_offset var navigation_rest: Vector2 = _navigation_cluster.position _navigation_cluster.position = navigation_rest + Vector2.DOWN * start_offset + var transition_duration := UIMotion.bubble_duration(start_offset) _presentation_tween = create_tween().set_parallel(true) _presentation_tween.tween_property( _content_shell, "position", _presentation_rest_position, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _cooler_page, "position", _cooler_rest_position, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _bag_page, "position", _bag_rest_position, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _logbook_page, "position", _logbook_rest_position, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _mail_page, "position", _mail_rest_position, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _profile_page, "position", _profile_rest_position, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _navigation_cluster, "position", navigation_rest, - MENU_ENTER_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.chain().tween_callback( _finish_menu_entry.bind(generation) @@ -1407,48 +1398,51 @@ func _begin_menu_exit(reason: CloseReason, restore_controls: bool) -> void: _content_shell.size.y, _navigation_cluster.size.y ) - TRANSITION_SAFE_MARGIN + var transition_duration := UIMotion.bubble_duration( + end_y - _navigation_cluster.position.y + ) _presentation_tween = create_tween().set_parallel(true) _presentation_tween.tween_property( _content_shell, "position:y", end_y, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _cooler_page, "position:y", -_cooler_page.size.y - TRANSITION_SAFE_MARGIN, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _bag_page, "position:y", -_bag_page.size.y - TRANSITION_SAFE_MARGIN, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _logbook_page, "position:y", -_logbook_page.size.y - TRANSITION_SAFE_MARGIN, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _mail_page, "position:y", -_mail_page.size.y - TRANSITION_SAFE_MARGIN, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _profile_page, "position:y", -_profile_page.size.y - TRANSITION_SAFE_MARGIN, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.tween_property( _navigation_cluster, "position:y", -_navigation_cluster.size.y - TRANSITION_SAFE_MARGIN, - MENU_EXIT_DURATION + transition_duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _presentation_tween.chain().tween_callback( _finish_menu_exit.bind( @@ -2009,14 +2003,10 @@ func _update_bag_detail() -> void: _bag_detail_constellation.visible = ( item != null and _current_section == Section.BAG ) - _bag_instruction_bubble.visible = ( - item == null and _current_section == Section.BAG - ) func _close_bag_detail() -> void: _bag_detail_constellation.visible = false - _bag_instruction_bubble.visible = _current_section == Section.BAG func _on_bag_field_gui_input(event: InputEvent) -> void: diff --git a/ui/player_menu.tscn b/ui/player_menu.tscn index 4ea9b6a..7affc70 100644 --- a/ui/player_menu.tscn +++ b/ui/player_menu.tscn @@ -448,9 +448,9 @@ mouse_filter = 1 [node name="BagOuterWall" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"] unique_name_in_owner = true layout_mode = 0 -offset_left = 72.0 +offset_left = 122.0 offset_top = 132.0 -offset_right = 968.0 +offset_right = 1018.0 offset_bottom = 574.0 [node name="BagOuterMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall"] @@ -507,25 +507,6 @@ text = "your bag is empty" horizontal_alignment = 1 vertical_alignment = 1 -[node name="BagInstructionBubble" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage" instance=ExtResource("9_info_scene")] -unique_name_in_owner = true -layout_mode = 0 -offset_left = 992.0 -offset_top = 140.0 -offset_right = 1232.0 -offset_bottom = 238.0 -mouse_filter = 2 - -[node name="BagInstructionText" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagInstructionBubble"] -unique_name_in_owner = true -layout_mode = 2 -mouse_filter = 2 -theme_override_colors/font_color = Color(0.22, 0.15, 0.08, 1) -theme_override_font_sizes/font_size = 18 -text = "drag items\nto the hotbar" -horizontal_alignment = 1 -vertical_alignment = 1 - [node name="BagDetailConstellation" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"] unique_name_in_owner = true visible = false diff --git a/ui/players_page.gd b/ui/players_page.gd index e32bf5a..a142e55 100644 --- a/ui/players_page.gd +++ b/ui/players_page.gd @@ -11,6 +11,7 @@ var _current_tab := 0 func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + UtilityPageStyle.apply_page(self) _build() @@ -26,6 +27,7 @@ func setup(service: NetworkPlayerListService) -> void: func activate() -> void: _refresh() + UtilityPageStyle.animate_in(self) _focus_first() @@ -38,16 +40,7 @@ func _build() -> void: paper.position = Vector2(58, 128) paper.size = Vector2(1164, 476) add_child(paper) - var style := StyleBoxFlat.new() - style.bg_color = Color("eee2bd") - style.border_color = Color("473d2e") - style.set_border_width_all(4) - style.set_corner_radius_all(16) - style.content_margin_left = 26 - style.content_margin_right = 26 - style.content_margin_top = 20 - style.content_margin_bottom = 20 - paper.add_theme_stylebox_override("panel", style) + paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style()) var root := VBoxContainer.new() root.add_theme_constant_override("separation", 10) paper.add_child(root) @@ -63,6 +56,7 @@ func _build() -> void: button.text = ["players", "relationships", "banned"][index] button.toggle_mode = true button.pressed.connect(_select_tab.bind(index)) + UtilityPageStyle.apply_button(button) _tabs.add_child(button) var scroll := ScrollContainer.new() scroll.custom_minimum_size = Vector2(0, 330) @@ -88,7 +82,7 @@ func _select_tab(index: int) -> void: func _refresh() -> void: if _service == null or _list == null: return - _header.text = "players %d / %d connected" % [ + _header.text = "Players\n%d / %d connected" % [ _service.get_connected_count(), _service.get_max_players(), ] for index: int in _tabs.get_child_count(): @@ -129,6 +123,9 @@ func _build_active_rows() -> void: identity.tooltip_text = NetworkIdentityCrypto.format_fingerprint( entry.full_fingerprint ) + identity.add_theme_color_override( + "font_color", UtilityPageStyle.INK + ) row.add_child(identity) var ping := Label.new() ping.custom_minimum_size.x = 80 @@ -137,26 +134,31 @@ func _build_active_rows() -> void: else "%d ms" % entry.ping_to_host_ms if entry.ping_to_host_ms >= 0 else "—" ) + ping.add_theme_color_override("font_color", UtilityPageStyle.INK) row.add_child(ping) var mute := Button.new() mute.text = "unmute" if entry.muted else "mute" mute.disabled = entry.is_local_player mute.pressed.connect(_toggle_mute.bind(entry)) + UtilityPageStyle.apply_button(mute) row.add_child(mute) var block := Button.new() block.text = "block" block.disabled = entry.is_local_player block.pressed.connect(_confirm_block.bind(entry)) + UtilityPageStyle.apply_button(block) row.add_child(block) var kick := Button.new() kick.text = "kick" kick.disabled = not entry.can_kick kick.pressed.connect(_confirm_kick.bind(entry)) + UtilityPageStyle.apply_button(kick) row.add_child(kick) var ban := Button.new() ban.text = "ban" ban.disabled = not entry.can_ban ban.pressed.connect(_confirm_ban.bind(entry)) + UtilityPageStyle.apply_button(ban) row.add_child(ban) _list.add_child(row) @@ -177,6 +179,7 @@ func _build_relationship_rows() -> void: "Blocked" if bool(record.get("blocked", false)) else "Muted", ] label.tooltip_text = NetworkIdentityCrypto.format_fingerprint(fingerprint) + label.add_theme_color_override("font_color", UtilityPageStyle.INK) row.add_child(label) if bool(record.get("blocked", false)): var unblock := Button.new() @@ -184,6 +187,7 @@ func _build_relationship_rows() -> void: unblock.pressed.connect(func() -> void: _service.set_blocked(fingerprint, str(record["last_known_display_name"]), false) ) + UtilityPageStyle.apply_button(unblock) row.add_child(unblock) var unmute := Button.new() unmute.text = "unmute" @@ -191,6 +195,7 @@ func _build_relationship_rows() -> void: unmute.pressed.connect(func() -> void: _service.set_muted(fingerprint, str(record["last_known_display_name"]), false) ) + UtilityPageStyle.apply_button(unmute) row.add_child(unmute) _list.add_child(row) @@ -211,10 +216,12 @@ func _build_ban_rows() -> void: Time.get_date_string_from_unix_time(int(record.get("banned_unix", 0))), ] label.tooltip_text = NetworkIdentityCrypto.format_fingerprint(fingerprint) + label.add_theme_color_override("font_color", UtilityPageStyle.INK) row.add_child(label) var unban := Button.new() unban.text = "unban" unban.pressed.connect(_confirm_unban.bind(fingerprint)) + UtilityPageStyle.apply_button(unban) row.add_child(unban) _list.add_child(row) @@ -223,6 +230,7 @@ func _make_row() -> HBoxContainer: var row := HBoxContainer.new() row.custom_minimum_size.y = 58 row.add_theme_constant_override("separation", 8) + row.add_theme_constant_override("outline_size", 1) return row @@ -231,6 +239,7 @@ func _add_empty(text: String) -> void: label.text = text label.custom_minimum_size.y = 100 label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + label.add_theme_color_override("font_color", UtilityPageStyle.MUTED_INK) _list.add_child(label) diff --git a/ui/profile_page.gd b/ui/profile_page.gd index f7ac069..ccfaafe 100644 --- a/ui/profile_page.gd +++ b/ui/profile_page.gd @@ -45,15 +45,18 @@ func setup(service: NetworkProfileService) -> void: _load_persisted() var identity_value := find_child("IdentityFingerprint", true, false) as Label if identity_value != null: - identity_value.text = "%s • Stored on this device" % ( - NetworkIdentityCrypto.format_fingerprint( - _service.get_identity_fingerprint() - ) + var fingerprint := _service.get_identity_fingerprint() + identity_value.text = "Identity • %s • Stored on this device" % ( + NetworkIdentityCrypto.compact_suffix(fingerprint) + ) + identity_value.tooltip_text = ( + NetworkIdentityCrypto.format_fingerprint(fingerprint) ) func activate() -> void: visible = true + UtilityPageStyle.animate_in(self) if _service != null: _load_persisted() _name_edit.grab_focus() @@ -100,29 +103,27 @@ func request_close_confirmation() -> bool: func _build_ui() -> void: var paper := PanelContainer.new() paper.set_anchors_preset(Control.PRESET_FULL_RECT) - paper.offset_left = 42.0 - paper.offset_top = 14.0 - paper.offset_right = -42.0 - paper.offset_bottom = -14.0 - var style := StyleBoxFlat.new() - style.bg_color = Color("f2ead3") - style.border_color = Color("4a4238") - style.set_border_width_all(4) - style.set_corner_radius_all(18) - paper.add_theme_stylebox_override("panel", style) + paper.offset_left = 64.0 + paper.offset_top = 28.0 + paper.offset_right = -64.0 + paper.offset_bottom = -28.0 + paper.add_theme_stylebox_override( + "panel", UtilityPageStyle.panel_style() + ) add_child(paper) + UtilityPageStyle.apply_page(self) var margin := MarginContainer.new() for side: String in ["left", "right", "top", "bottom"]: - margin.add_theme_constant_override("margin_%s" % side, 28) + margin.add_theme_constant_override("margin_%s" % side, 18) paper.add_child(margin) var layout := VBoxContainer.new() - layout.add_theme_constant_override("separation", 8) + layout.add_theme_constant_override("separation", 5) margin.add_child(layout) var heading := Label.new() - heading.text = "player profile" - heading.add_theme_font_size_override("font_size", 28) + heading.text = "Player Profile" + heading.add_theme_font_size_override("font_size", 25) heading.add_theme_color_override("font_color", Color("302b27")) layout.add_child(heading) @@ -139,7 +140,8 @@ func _build_ui() -> void: _name_edit = LineEdit.new() _name_edit.max_length = NetworkProtocol.MAX_DISPLAY_NAME_LENGTH _name_edit.placeholder_text = "Player" - _name_edit.custom_minimum_size = Vector2(360, 42) + _name_edit.custom_minimum_size = Vector2(300, 40) + UtilityPageStyle.apply_line_edit(_name_edit) _name_edit.text_changed.connect(_on_name_changed) name_stack.add_child(_name_edit) var helper := Label.new() @@ -148,17 +150,20 @@ func _build_ui() -> void: name_stack.add_child(helper) _apply_button = Button.new() _apply_button.text = "apply" - _apply_button.custom_minimum_size = Vector2(118, 50) + _apply_button.custom_minimum_size = Vector2(92, 40) + UtilityPageStyle.apply_button(_apply_button) _apply_button.pressed.connect(_apply) name_row.add_child(_apply_button) _revert_button = Button.new() _revert_button.text = "revert" - _revert_button.custom_minimum_size = Vector2(108, 50) + _revert_button.custom_minimum_size = Vector2(88, 40) + UtilityPageStyle.apply_button(_revert_button) _revert_button.pressed.connect(_revert) name_row.add_child(_revert_button) var defaults_button := Button.new() defaults_button.text = "defaults" - defaults_button.custom_minimum_size = Vector2(108, 50) + defaults_button.custom_minimum_size = Vector2(88, 40) + UtilityPageStyle.apply_button(defaults_button) defaults_button.pressed.connect(_show_confirmation.bind("defaults")) name_row.add_child(defaults_button) @@ -169,13 +174,9 @@ func _build_ui() -> void: _suggestions.add_theme_constant_override("separation", 8) layout.add_child(_suggestions) - var identity_label := Label.new() - identity_label.text = "identity" - identity_label.add_theme_color_override("font_color", Color("302b27")) - layout.add_child(identity_label) var identity_value := Label.new() identity_value.name = "IdentityFingerprint" - identity_value.text = "Stored on this device" + identity_value.text = "Identity • Stored on this device" identity_value.tooltip_text = ( "This identity helps other players recognize you between sessions." ) @@ -184,13 +185,13 @@ func _build_ui() -> void: var body := HBoxContainer.new() body.size_flags_vertical = Control.SIZE_EXPAND_FILL - body.add_theme_constant_override("separation", 18) + body.add_theme_constant_override("separation", 12) layout.add_child(body) _category_list = VBoxContainer.new() - _category_list.custom_minimum_size = Vector2(170, 0) + _category_list.custom_minimum_size = Vector2(140, 0) body.add_child(_category_list) _option_list = VBoxContainer.new() - _option_list.custom_minimum_size = Vector2(230, 0) + _option_list.custom_minimum_size = Vector2(190, 0) body.add_child(_option_list) var preview_stack := VBoxContainer.new() preview_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL @@ -198,15 +199,19 @@ func _build_ui() -> void: body.add_child(preview_stack) _preview = preload("res://ui/profile_preview.tscn").instantiate() preview_stack.add_child(_preview) + var preview_actions := HBoxContainer.new() + preview_actions.alignment = BoxContainer.ALIGNMENT_CENTER + preview_actions.add_theme_constant_override("separation", 10) + preview_stack.add_child(preview_actions) var preview_note := Label.new() - preview_note.text = "capsule preview • drag or use left / right" - preview_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + preview_note.text = "Current player • drag or use left / right" preview_note.add_theme_color_override("font_color", Color("514a42")) - preview_stack.add_child(preview_note) + preview_actions.add_child(preview_note) var reset_view := Button.new() reset_view.text = "reset view" reset_view.pressed.connect(_preview.reset_view) - preview_stack.add_child(reset_view) + UtilityPageStyle.apply_button(reset_view) + preview_actions.add_child(reset_view) _discard_confirmation = PanelContainer.new() _discard_confirmation.visible = false @@ -249,6 +254,7 @@ func _build_categories() -> void: button.custom_minimum_size = Vector2(0, 34) button.button_pressed = category_id == _category_id button.pressed.connect(_select_category.bind(category_id)) + UtilityPageStyle.apply_button(button) _category_list.add_child(button) _refresh_options() @@ -287,6 +293,7 @@ func _refresh_options() -> void: button.custom_minimum_size = Vector2(0, 34) button.button_pressed = _draft_appearance.get(_category_id) == option_id button.pressed.connect(_select_option.bind(_category_id, option_id)) + UtilityPageStyle.apply_button(button) _option_list.add_child(button) diff --git a/ui/profile_preview.gd b/ui/profile_preview.gd index b2e0cd9..c0bef1c 100644 --- a/ui/profile_preview.gd +++ b/ui/profile_preview.gd @@ -7,16 +7,25 @@ extends SubViewportContainer @onready var _preview_root: Node3D = %PreviewRoot var _dragging: bool = false +var _visuals: Node3D func _ready() -> void: focus_mode = Control.FOCUS_ALL gui_input.connect(_on_gui_input) + _visuals = PlayerVisualPresenter.instantiate_visuals() + _preview_root.add_child(_visuals) + var rod := _visuals.find_child("FishingRod", true, false) as Node3D + if rod != null: + rod.visible = false + var catch_display := _visuals.find_child("CatchDisplay", true, false) as Node3D + if catch_display != null: + catch_display.visible = false -func apply_appearance_profile(_profile: Dictionary) -> void: - # The capsule is deliberately neutral until modular character assets exist. - pass +func apply_appearance_profile(profile: Dictionary) -> void: + if _visuals != null: + PlayerVisualPresenter.apply_appearance(_visuals, profile) func reset_view() -> void: diff --git a/ui/profile_preview.tscn b/ui/profile_preview.tscn index 9c84a77..37be6fa 100644 --- a/ui/profile_preview.tscn +++ b/ui/profile_preview.tscn @@ -1,15 +1,7 @@ -[gd_scene load_steps=6 format=3] +[gd_scene load_steps=4 format=3] [ext_resource type="Script" path="res://ui/profile_preview.gd" id="1"] -[sub_resource type="CapsuleMesh" id="Capsule"] -radius = 0.62 -height = 2.25 - -[sub_resource type="StandardMaterial3D" id="Material"] -albedo_color = Color(0.38, 0.67, 0.74, 1) -roughness = 0.72 - [sub_resource type="Environment" id="Environment"] background_mode = 1 background_color = Color(0.075, 0.13, 0.16, 1) @@ -21,13 +13,13 @@ ambient_light_energy = 0.65 environment = SubResource("Environment") [node name="ProfilePreview" type="SubViewportContainer"] -custom_minimum_size = Vector2(360, 250) +custom_minimum_size = Vector2(300, 220) stretch = true script = ExtResource("1") [node name="Viewport" type="SubViewport" parent="."] transparent_bg = false -size = Vector2i(360, 390) +size = Vector2i(320, 330) render_target_update_mode = 4 world_3d = SubResource("PreviewWorld") @@ -48,10 +40,6 @@ light_energy = 1.3 [node name="PreviewRoot" type="Node3D" parent="Viewport"] unique_name_in_owner = true -[node name="Capsule" type="MeshInstance3D" parent="Viewport/PreviewRoot"] -mesh = SubResource("Capsule") -material_override = SubResource("Material") - [node name="Camera3D" type="Camera3D" parent="Viewport"] -position = Vector3(0, 0.15, 4.25) +position = Vector3(0, 0.95, 1.9) current = true diff --git a/ui/settings/settings_bubble_page.gd b/ui/settings/settings_bubble_page.gd index 96b4bcb..1ccff30 100644 --- a/ui/settings/settings_bubble_page.gd +++ b/ui/settings/settings_bubble_page.gd @@ -11,8 +11,6 @@ extends Control @export var compact_maximum_layout_size: Vector2 = Vector2.ZERO @export var compact_width_threshold: float = 680.0 @export var compact_height_threshold: float = 500.0 -@export_range(0.1, 2.0, 0.01) var outgoing_rise_duration: float = 1.70 -@export_range(0.1, 2.0, 0.01) var incoming_rise_duration: float = 1.85 @export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0 var _cluster: BubbleCluster @@ -49,30 +47,35 @@ func show_page(should_focus: bool = true) -> void: _cluster.position = _resting_cluster_position set_process(true) _set_interactive(true) + var paper := get_node_or_null("Paper") as Control + if paper != null: + UtilityPageStyle.animate_in(paper) if should_focus: focus_initial() func transition_out( completed: Callable, - duration_override: float = -1.0, + _duration_override: float = -1.0, ) -> void: _transition_generation += 1 _cancel_transition() _is_transitioning = true _set_interactive(false) + var paper := get_node_or_null("Paper") as Control + if paper != null: + UtilityPageStyle.animate_out(paper, Callable()) set_process(true) - var duration: float = ( - duration_override - if duration_override > 0.0 - else outgoing_rise_duration + var target_y: float = -_cluster.size.y - transition_safe_margin + var duration := UIMotion.bubble_duration( + target_y - _cluster.position.y ) var generation: int = _transition_generation _transition = create_tween() _transition.tween_property( _cluster, "position:y", - -_cluster.size.y - transition_safe_margin, + target_y, duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _transition.finished.connect( @@ -84,7 +87,7 @@ func transition_out( func transition_in( should_focus: bool, completed: Callable, - duration_override: float = -1.0, + _duration_override: float = -1.0, ) -> void: _transition_generation += 1 _cancel_transition() @@ -92,15 +95,16 @@ func transition_in( _is_transitioning = true _set_interactive(false) set_process(true) - var duration: float = ( - duration_override - if duration_override > 0.0 - else incoming_rise_duration - ) _cluster.position = Vector2( _resting_cluster_position.x, size.y + transition_safe_margin ) + var paper := get_node_or_null("Paper") as Control + if paper != null: + UtilityPageStyle.animate_in(paper) + var duration := UIMotion.bubble_duration( + _cluster.position.y - _resting_cluster_position.y + ) var generation: int = _transition_generation _transition = create_tween() _transition.tween_property( @@ -208,7 +212,8 @@ func _finish_transition_out(generation: int, completed: Callable) -> void: _is_transitioning = false hide() _cluster.position = _resting_cluster_position - completed.call() + if completed.is_valid(): + completed.call() func _finish_transition_in( diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index 830f2b4..a8faa93 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -8,8 +8,6 @@ const PAGE_DISPLAY: StringName = &"display" const PAGE_CONTROLS: StringName = &"controls" const PAGE_ACCESSIBILITY: StringName = &"accessibility" const PAGE_DATA: StringName = &"data" -const TITLE_HOST_OUTGOING_DURATION: float = 1.70 -const TITLE_HOST_INCOMING_DURATION: float = 1.80 const PIXELATION_NAMES: PackedStringArray = [ "legible", "cute", @@ -23,6 +21,7 @@ const SettingsManagerType = preload( signal applied signal closed +signal closing signal opened signal crisp_reset_focus_requested signal panel_visibility_changed(is_visible: bool) @@ -151,6 +150,19 @@ func _ready() -> void: _invert_y_toggle.pressed.connect(_toggle_invert_y) _auto_click_toggle.pressed.connect(_toggle_auto_click) _readable_font_toggle.pressed.connect(_toggle_readable_font) + for control: Control in [_auto_click_toggle, _auto_click_interval]: + control.mouse_entered.connect( + _show_accessibility_helper.bind(&"auto_click") + ) + control.focus_entered.connect( + _show_accessibility_helper.bind(&"auto_click") + ) + _readable_font_toggle.mouse_entered.connect( + _show_accessibility_helper.bind(&"alternate_font") + ) + _readable_font_toggle.focus_entered.connect( + _show_accessibility_helper.bind(&"alternate_font") + ) %IntervalDecrease.pressed.connect(_adjust_auto_click_interval.bind(-1)) %IntervalIncrease.pressed.connect(_adjust_auto_click_interval.bind(1)) _auto_click_interval.pressed.connect( @@ -171,6 +183,7 @@ func _ready() -> void: parent_control.resized.connect(_refresh_panel_size) for page: SettingsBubblePage in _pages.values(): page.hide_page() + _style_data_page() call_deferred("_refresh_panel_size") @@ -234,7 +247,7 @@ func open_panel( _root_page.transition_in( true, _finish_animated_open.bind(generation), - TITLE_HOST_INCOMING_DURATION + 0.0 ) else: _show_active_page(true) @@ -304,34 +317,29 @@ func _start_page_transition(page_id: StringName, push_page: bool) -> void: _page_transition_active = true navigation_transition_started.emit() var generation: int = _page_transition_generation - outgoing_page.transition_out( - _finish_outgoing_page.bind( - generation, - page_id, - push_page, - incoming_page - ) - ) - - -func _finish_outgoing_page( - generation: int, - page_id: StringName, - push_page: bool, - incoming_page: SettingsBubblePage, -) -> void: - if generation != _page_transition_generation or not _page_transition_active: - return if push_page: _page_stack.append(page_id) else: _page_stack.pop_back() for page: SettingsBubblePage in _pages.values(): - if page != incoming_page: + if page != outgoing_page and page != incoming_page: page.hide_page() - incoming_page.transition_in( - true, - _finish_incoming_page.bind(generation) + outgoing_page.transition_out( + Callable() + ) + get_tree().create_timer( + UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY + ).timeout.connect( + func() -> void: + if ( + generation == _page_transition_generation + and _page_transition_active + ): + incoming_page.transition_in( + true, + _finish_incoming_page.bind(generation) + ), + CONNECT_ONE_SHOT ) @@ -355,10 +363,11 @@ func _begin_animated_close(applied_result: bool) -> void: _page_transition_generation += 1 _page_transition_active = true navigation_transition_started.emit() + closing.emit() var generation: int = _page_transition_generation active_page.transition_out( _finish_animated_close.bind(generation, applied_result), - TITLE_HOST_OUTGOING_DURATION + 0.0 ) @@ -393,7 +402,7 @@ func _refresh_data_page() -> void: if not is_node_ready() or _data_root == null: return %DataFolderPath.text = _data_root.root_path - %DataStorageMode.text = "storage mode: " + _data_root.storage_mode_text() + %DataStorageMode.text = "Storage Mode: " + _data_root.storage_mode_text() %ChangeDataFolder.disabled = ( _data_root.override_active or (_network_session != null and _network_session.is_session_active()) @@ -432,7 +441,7 @@ func _change_data_folder(path: String) -> void: _show_existing_data_folder_choice(path) return if bool(result.get("ok", false)): - _feedback.text = "Data folder changed. NETFISHING will close safely." + _feedback.text = "Data folder changed. NETfishing will close safely." _refresh_data_page() get_tree().call_deferred("quit") else: @@ -441,16 +450,16 @@ func _change_data_folder(path: String) -> void: func _show_existing_data_folder_choice(path: String) -> void: var dialog: ConfirmationDialog = ConfirmationDialog.new() - dialog.title = "Existing NETFISHING data" + dialog.title = "Existing NETfishing data" dialog.ok_button_text = "Use Selected Data" dialog.dialog_text = ( - "The selected folder contains different NETFISHING data.\n\n" + "The selected folder contains different NETfishing data.\n\n" + "Choose one complete data set. Data will not be merged." ) dialog.add_button("Replace Selected Data", false, "replace") dialog.confirmed.connect(func() -> void: if _data_root.use_existing_root(path): - _feedback.text = "Data folder changed. NETFISHING will close safely." + _feedback.text = "Data folder changed. NETfishing will close safely." get_tree().call_deferred("quit") else: _feedback.text = _data_root.error_message @@ -483,7 +492,7 @@ func _choose_identity_export(identity_type: String) -> void: _export_file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE _export_file_dialog.access = FileDialog.ACCESS_FILESYSTEM _export_file_dialog.use_native_dialog = false - _export_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETFISHING identity backup"]) + _export_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETfishing identity backup"]) _export_file_dialog.file_selected.connect(_identity_export_file_selected) _interface_fonts.apply_utility_theme(_export_file_dialog) add_child(_export_file_dialog) @@ -509,7 +518,7 @@ func _choose_identity_import(identity_type: String) -> void: _backup_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE _backup_file_dialog.access = FileDialog.ACCESS_FILESYSTEM _backup_file_dialog.use_native_dialog = false - _backup_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETFISHING identity backup"]) + _backup_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETfishing identity backup"]) _backup_file_dialog.file_selected.connect(_identity_import_file_selected) _interface_fonts.apply_utility_theme(_backup_file_dialog) add_child(_backup_file_dialog) @@ -707,7 +716,7 @@ func _refresh_value_labels() -> void: + ("on" if _auto_click_enabled else "off") ) _readable_font_toggle.text = ( - "readable\ninterface font\n" + "alternate\nfont\n" + ( "on" if settings.use_readable_interface_font @@ -725,6 +734,35 @@ func _refresh_value_labels() -> void: _ui_options[index].button_pressed = index + 1 == settings.ui_pixel_size +func _show_accessibility_helper(kind: StringName) -> void: + var helper := %IntervalHelp as BubbleButton + if kind == &"alternate_font": + helper.text = ( + "The quick brown fox\njumps over the lazy dog.\n0123456789" + ) + else: + helper.text = ( + "Lower intervals click\nbarriers faster\nwhile held." + ) + + +func _style_data_page() -> void: + UtilityPageStyle.apply_page(_data_page) + var paper := _data_page.get_node("Paper") as PanelContainer + paper.add_theme_stylebox_override( + "panel", UtilityPageStyle.panel_style() + ) + var content := _data_page.get_node("Paper/Content") as VBoxContainer + content.add_theme_constant_override("separation", 12) + for node: Node in content.find_children("*", "", true, false): + if node is BaseButton: + UtilityPageStyle.apply_button(node) + elif node is Label: + node.add_theme_color_override( + "font_color", UtilityPageStyle.INK + ) + + func _set_world_pixelation(pixel_size: int) -> void: if _settings_manager == null: return diff --git a/ui/settings_panel.tscn b/ui/settings_panel.tscn index f41cba8..6bbd8a9 100644 --- a/ui/settings_panel.tscn +++ b/ui/settings_panel.tscn @@ -553,8 +553,8 @@ unique_name_in_owner = true custom_minimum_size = Vector2(0, 0) mouse_filter = 2 focus_mode = 0 -text = "lower intervals\nclick barriers\nfaster\nwhile held" -neutral_size = Vector2(170, 160) +text = "Focus a control\nfor a helpful\npreview." +neutral_size = Vector2(210, 176) desktop_anchor = Vector2(315, 405) compact_anchor = Vector2(280, 370) compact_minimum_size = Vector2(160, 152) @@ -566,8 +566,8 @@ motion_phase = 4.1 unique_name_in_owner = true custom_minimum_size = Vector2(0, 0) toggle_mode = true -text = "readable\ninterface font\noff" -neutral_size = Vector2(180, 132) +text = "alternate\nfont\noff" +neutral_size = Vector2(150, 150) desktop_anchor = Vector2(92, 405) compact_anchor = Vector2(102, 374) compact_minimum_size = Vector2(156, 118) @@ -576,6 +576,7 @@ maximum_font_size = 21 motion_phase = 4.6 [node name="ReadableFontSample" type="Label" parent="AccessibilityPage/BubbleCluster"] +visible = false layout_mode = 0 offset_left = 250.0 offset_top = 20.0 @@ -588,6 +589,7 @@ vertical_alignment = 1 autowrap_mode = 2 [node name="ReadableFontHelper" type="Label" parent="AccessibilityPage/BubbleCluster"] +visible = false layout_mode = 0 offset_left = 250.0 offset_top = 82.0 @@ -647,6 +649,7 @@ offset_right = 330.0 offset_bottom = 210.0 grow_horizontal = 2 grow_vertical = 2 +z_index = 1 [node name="Content" type="VBoxContainer" parent="DataPage/Paper"] layout_mode = 2 @@ -655,12 +658,12 @@ theme_override_constants/separation = 10 [node name="Heading" type="Label" parent="DataPage/Paper/Content"] layout_mode = 2 theme_override_font_sizes/font_size = 28 -text = "data & identity" +text = "Data & Identity" horizontal_alignment = 1 [node name="DataFolderLabel" type="Label" parent="DataPage/Paper/Content"] layout_mode = 2 -text = "Data Folder" +text = "Data Folder:" [node name="DataFolderPath" type="Label" parent="DataPage/Paper/Content"] unique_name_in_owner = true @@ -671,7 +674,7 @@ autowrap_mode = 2 [node name="DataStorageMode" type="Label" parent="DataPage/Paper/Content"] unique_name_in_owner = true layout_mode = 2 -text = "storage mode: unavailable" +text = "Storage Mode: unavailable" [node name="OpenDataFolder" type="Button" parent="DataPage/Paper/Content"] unique_name_in_owner = true @@ -714,11 +717,12 @@ text = "Import Host Identity" [node name="DataBackButton" parent="DataPage/BubbleCluster" instance=ExtResource("4_bubble")] unique_name_in_owner = true +z_index = 4 layout_mode = 0 text = "back" neutral_size = Vector2(96, 90) -desktop_anchor = Vector2(135, 470) -compact_anchor = Vector2(115, 420) +desktop_anchor = Vector2(-25, 470) +compact_anchor = Vector2(-20, 420) compact_minimum_size = Vector2(82, 78) minimum_font_size = 13 maximum_font_size = 16 diff --git a/ui/title_confirmation_bubble_page.gd b/ui/title_confirmation_bubble_page.gd index 6a0d2e4..a1a75da 100644 --- a/ui/title_confirmation_bubble_page.gd +++ b/ui/title_confirmation_bubble_page.gd @@ -102,7 +102,7 @@ func set_stage_rect(stage_rect: Rect2) -> void: func transition_in( - duration: float, + _duration: float, completed: Callable, ) -> void: _transition_generation += 1 @@ -115,6 +115,9 @@ func transition_in( _resting_cluster_position.x, size.y + transition_safe_margin ) + var duration := UIMotion.bubble_duration( + _cluster.position.y - _resting_cluster_position.y + ) var generation: int = _transition_generation _transition = create_tween() _transition.tween_property( @@ -130,7 +133,7 @@ func transition_in( func transition_out( - duration: float, + _duration: float, completed: Callable, ) -> void: if not visible or _is_transitioning: @@ -139,12 +142,16 @@ func transition_out( _cancel_transition() _is_transitioning = true _set_interactive(false) + var target_y: float = -_cluster.size.y - transition_safe_margin + var duration := UIMotion.bubble_duration( + target_y - _cluster.position.y + ) var generation: int = _transition_generation _transition = create_tween() _transition.tween_property( _cluster, "position:y", - -_cluster.size.y - transition_safe_margin, + target_y, duration ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _transition.finished.connect( diff --git a/ui/title_screen.gd b/ui/title_screen.gd index e18f7dc..adf58e3 100644 --- a/ui/title_screen.gd +++ b/ui/title_screen.gd @@ -83,8 +83,6 @@ const INTRO_PROMPT_FADE_DURATION: float = 0.55 const INTRO_BUBBLE_TRAVEL_DURATION: float = 2.40 const INTRO_BRANDING_TRAVEL_DURATION: float = 2.0 const INTRO_BRANDING_START_DELAY: float = 0.35 -const HOST_PRESENTATION_OUT_DURATION: float = 1.70 -const HOST_PRESENTATION_IN_DURATION: float = 1.85 const HOST_CLUSTER_SAFE_MARGIN: float = 24.0 signal new_game_requested @@ -203,6 +201,7 @@ func _ready() -> void: _confirmation_page.cancelled.connect(_request_close_confirmation) _settings_panel.applied.connect(_on_settings_applied) _settings_panel.closed.connect(_on_settings_closed) + _settings_panel.closing.connect(_on_settings_closing) _settings_panel.opened.connect(_on_settings_opened) _settings_panel.navigation_transition_started.connect( _emit_navigation_bubble_flurry @@ -1032,7 +1031,7 @@ func _begin_confirmation_open() -> void: _presentation_center, "position:y", _confirmation_title_content_rest_position.y - exit_distance, - HOST_PRESENTATION_OUT_DURATION + UIMotion.bubble_duration(exit_distance) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _confirmation_transition.finished.connect( _finish_confirmation_title_exit.bind(generation), @@ -1050,7 +1049,7 @@ func _finish_confirmation_title_exit(generation: int) -> void: _presentation_center.hide() _presentation_center.position = _confirmation_title_content_rest_position _confirmation_page.transition_in( - HOST_PRESENTATION_IN_DURATION, + 0.0, _finish_confirmation_open.bind(generation) ) @@ -1074,7 +1073,7 @@ func _begin_confirmation_return() -> void: _confirmation_page.lock_interaction() var generation: int = _confirmation_transition_generation _confirmation_page.transition_out( - HOST_PRESENTATION_OUT_DURATION, + 0.0, _finish_confirmation_page_exit.bind(generation) ) @@ -1092,12 +1091,16 @@ func _finish_confirmation_page_exit(generation: int) -> void: + HOST_CLUSTER_SAFE_MARGIN ) _presentation_center.show() + var entry_distance: float = absf( + _presentation_center.position.y + - _confirmation_title_content_rest_position.y + ) _confirmation_transition = create_tween() _confirmation_transition.tween_property( _presentation_center, "position", _confirmation_title_content_rest_position, - HOST_PRESENTATION_IN_DURATION + UIMotion.bubble_duration(entry_distance) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _confirmation_transition.finished.connect( _finish_confirmation_return.bind(generation), @@ -1177,15 +1180,23 @@ func _close_settings() -> void: func _on_settings_applied() -> void: - _begin_title_cluster_return("settings saved.") + _feedback_text_before_settings = "settings saved." func _on_settings_closed() -> void: - _begin_title_cluster_return(_feedback_text_before_settings) + pass + + +func _on_settings_closing() -> void: + await get_tree().create_timer( + UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY + ).timeout + if _settings_panel.visible: + _begin_title_cluster_return(_feedback_text_before_settings) func _on_settings_opened() -> void: - _title_settings_transition_active = false + pass func _begin_title_cluster_exit() -> void: @@ -1207,12 +1218,23 @@ func _begin_title_cluster_exit() -> void: _presentation_center, "position:y", _title_content_rest_position.y - exit_distance, - HOST_PRESENTATION_OUT_DURATION + UIMotion.bubble_duration(exit_distance) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _title_settings_transition.finished.connect( _finish_title_cluster_exit.bind(generation), CONNECT_ONE_SHOT ) + await get_tree().create_timer( + UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY + ).timeout + if ( + generation == _title_settings_transition_generation + and _title_settings_transition_active + ): + _settings_panel.open_panel( + _settings_manager, + SettingsPanelType.PresentationMode.TITLE_EMBEDDED + ) func _finish_title_cluster_exit(generation: int) -> void: @@ -1222,12 +1244,9 @@ func _finish_title_cluster_exit(generation: int) -> void: ): return _title_settings_transition = null + _title_settings_transition_active = false _presentation_center.hide() _presentation_center.position = _title_content_rest_position - _settings_panel.open_panel( - _settings_manager, - SettingsPanelType.PresentationMode.TITLE_EMBEDDED - ) func _begin_title_cluster_return(feedback_text: String) -> void: @@ -1245,12 +1264,15 @@ func _begin_title_cluster_return(feedback_text: String) -> void: _title_presentation_scale_root.size.y + HOST_CLUSTER_SAFE_MARGIN ) _presentation_center.show() + var entry_distance: float = absf( + _presentation_center.position.y - _title_content_rest_position.y + ) _title_settings_transition = create_tween() _title_settings_transition.tween_property( _presentation_center, "position", _title_content_rest_position, - HOST_PRESENTATION_IN_DURATION + UIMotion.bubble_duration(entry_distance) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) _title_settings_transition.finished.connect( _finish_title_cluster_return.bind(generation), diff --git a/ui/title_screen.tscn b/ui/title_screen.tscn index e591a0f..ae2da84 100644 --- a/ui/title_screen.tscn +++ b/ui/title_screen.tscn @@ -33,6 +33,7 @@ script = ExtResource("1_script") [node name="Background" type="ColorRect" parent="."] unique_name_in_owner = true +visible = false material = SubResource("ShaderMaterial_title_water") layout_mode = 1 anchors_preset = 15 diff --git a/ui/ui_motion.gd b/ui/ui_motion.gd new file mode 100644 index 0000000..55956ca --- /dev/null +++ b/ui/ui_motion.gd @@ -0,0 +1,12 @@ +class_name UIMotion +extends RefCounted + +const BUBBLE_VERTICAL_SPEED: float = 420.0 +const BUBBLE_TRANSITION_OVERLAP_DELAY: float = 0.05 +const UTILITY_ENTER_DURATION: float = 0.19 +const UTILITY_EXIT_DURATION: float = 0.15 +const UTILITY_ENTER_SCALE: float = 0.97 + + +static func bubble_duration(distance: float) -> float: + return maxf(absf(distance) / BUBBLE_VERTICAL_SPEED, 0.08) diff --git a/ui/ui_motion.gd.uid b/ui/ui_motion.gd.uid new file mode 100644 index 0000000..70fd633 --- /dev/null +++ b/ui/ui_motion.gd.uid @@ -0,0 +1 @@ +uid://biwyjd2usqsm6 diff --git a/ui/utility_page_style.gd b/ui/utility_page_style.gd new file mode 100644 index 0000000..cdf7235 --- /dev/null +++ b/ui/utility_page_style.gd @@ -0,0 +1,150 @@ +class_name UtilityPageStyle +extends RefCounted + +const TuffyFont: Font = preload("res://ui/fonts/Tuffy_Bold.otf") +const PAPER: Color = Color("eee2bd") +const PAPER_ALT: Color = Color("f3ecd7") +const INK: Color = Color("28251f") +const MUTED_INK: Color = Color("5f5547") +const NAVY: Color = Color("092b3d") +const NAVY_HOVER: Color = Color("12465b") +const GREEN: Color = Color("31594d") +const BORDER: Color = Color("473d2e") +const LIGHT_TEXT: Color = Color("f5eed9") +const DISABLED_TEXT: Color = Color(0.33, 0.36, 0.35, 0.72) +const MOTION_TWEEN_META: StringName = &"utility_page_motion_tween" + + +static func apply_page(root: Control) -> void: + root.add_theme_font_override("font", TuffyFont) + + +static func panel_style() -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = PAPER + style.border_color = BORDER + style.set_border_width_all(4) + style.set_corner_radius_all(16) + style.content_margin_left = 26 + style.content_margin_right = 26 + style.content_margin_top = 20 + style.content_margin_bottom = 20 + style.shadow_color = Color(0.02, 0.075, 0.11, 0.34) + style.shadow_size = 7 + style.shadow_offset = Vector2(5, 6) + return style + + +static func row_style(selected: bool = false) -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = NAVY if selected else PAPER_ALT + style.border_color = Color(BORDER, 0.55) + style.set_border_width_all(2) + style.set_corner_radius_all(8) + style.content_margin_left = 12 + style.content_margin_right = 12 + style.content_margin_top = 8 + style.content_margin_bottom = 8 + return style + + +static func button_style(color: Color) -> StyleBoxFlat: + var style := StyleBoxFlat.new() + style.bg_color = color + style.border_color = Color(0.76, 0.86, 0.78, 0.72) + style.set_border_width_all(2) + style.set_corner_radius_all(7) + style.content_margin_left = 14 + style.content_margin_right = 14 + style.content_margin_top = 8 + style.content_margin_bottom = 8 + return style + + +static func apply_button(button: BaseButton) -> void: + button.add_theme_font_override("font", TuffyFont) + button.add_theme_color_override("font_color", LIGHT_TEXT) + button.add_theme_color_override("font_hover_color", Color.WHITE) + button.add_theme_color_override("font_pressed_color", Color.WHITE) + button.add_theme_color_override("font_focus_color", Color.WHITE) + button.add_theme_color_override("font_disabled_color", DISABLED_TEXT) + button.add_theme_stylebox_override("normal", button_style(NAVY)) + button.add_theme_stylebox_override("hover", button_style(NAVY_HOVER)) + button.add_theme_stylebox_override("pressed", button_style(GREEN)) + button.add_theme_stylebox_override("focus", button_style(NAVY_HOVER)) + button.add_theme_stylebox_override( + "disabled", button_style(Color(0.37, 0.42, 0.40, 0.55)) + ) + button.custom_minimum_size.y = maxf(button.custom_minimum_size.y, 40.0) + + +static func apply_line_edit(edit: LineEdit) -> void: + edit.add_theme_font_override("font", TuffyFont) + edit.add_theme_color_override("font_color", LIGHT_TEXT) + edit.add_theme_color_override( + "font_placeholder_color", Color(0.82, 0.80, 0.71, 0.84) + ) + edit.add_theme_color_override("font_selected_color", Color.WHITE) + edit.add_theme_color_override("caret_color", Color("fff0b6")) + edit.add_theme_color_override("selection_color", Color("2f7186")) + edit.add_theme_color_override("font_uneditable_color", Color("c9c2ae")) + edit.add_theme_stylebox_override("normal", button_style(NAVY)) + edit.add_theme_stylebox_override("focus", button_style(NAVY_HOVER)) + edit.add_theme_stylebox_override( + "read_only", button_style(Color(NAVY, 0.82)) + ) + + +static func animate_in(control: Control) -> void: + _cancel_motion(control) + control.pivot_offset = control.size * 0.5 + control.scale = Vector2.ONE * UIMotion.UTILITY_ENTER_SCALE + control.modulate.a = 0.0 + control.mouse_filter = Control.MOUSE_FILTER_IGNORE + var tween := control.create_tween() + control.set_meta(MOTION_TWEEN_META, tween) + tween.set_parallel(true) + tween.tween_property( + control, "scale", Vector2.ONE, UIMotion.UTILITY_ENTER_DURATION + ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) + tween.tween_property( + control, "modulate:a", 1.0, UIMotion.UTILITY_ENTER_DURATION + ) + tween.finished.connect(func() -> void: + if is_instance_valid(control): + control.mouse_filter = Control.MOUSE_FILTER_PASS + control.remove_meta(MOTION_TWEEN_META) + ) + + +static func animate_out(control: Control, completed: Callable) -> void: + _cancel_motion(control) + control.mouse_filter = Control.MOUSE_FILTER_IGNORE + control.pivot_offset = control.size * 0.5 + var tween := control.create_tween() + control.set_meta(MOTION_TWEEN_META, tween) + tween.set_parallel(true) + tween.tween_property( + control, + "scale", + Vector2.ONE * UIMotion.UTILITY_ENTER_SCALE, + UIMotion.UTILITY_EXIT_DURATION + ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) + tween.tween_property( + control, "modulate:a", 0.0, UIMotion.UTILITY_EXIT_DURATION + ) + if completed.is_valid(): + tween.finished.connect(completed, CONNECT_ONE_SHOT) + tween.finished.connect(func() -> void: + if is_instance_valid(control): + control.remove_meta(MOTION_TWEEN_META) + ) + + +static func _cancel_motion(control: Control) -> void: + if not control.has_meta(MOTION_TWEEN_META): + return + var existing := control.get_meta(MOTION_TWEEN_META) as Tween + if existing != null and existing.is_valid(): + existing.kill() + control.remove_meta(MOTION_TWEEN_META) diff --git a/ui/utility_page_style.gd.uid b/ui/utility_page_style.gd.uid new file mode 100644 index 0000000..a255b79 --- /dev/null +++ b/ui/utility_page_style.gd.uid @@ -0,0 +1 @@ +uid://bmfqwan211x0j