Unify menu presentation and fix UI regressions

This commit is contained in:
Alexander Sellite 2026-07-30 10:46:15 -04:00
parent 3734d4a168
commit b18d9392d8
35 changed files with 588 additions and 361 deletions

View file

@ -38,8 +38,8 @@ application/icon_interpolation=4
application/file_version="0.2.0.2" application/file_version="0.2.0.2"
application/product_version="0.2.0.2" application/product_version="0.2.0.2"
application/company_name="" application/company_name=""
application/product_name="NETFISHING" application/product_name="NETfishing"
application/file_description="NETFISHING Pre-Alpha Playtest 0.2" application/file_description="NETfishing Pre-Alpha Playtest 0.2"
application/copyright="" application/copyright=""
application/trademarks="" application/trademarks=""
application/export_angle=0 application/export_angle=0

View file

@ -132,6 +132,7 @@ const TITLE_MUSIC_SILENCE_DB: float = -80.0
%NetworkProfileService %NetworkProfileService
) )
@onready var _players_root: Node3D = $Players @onready var _players_root: Node3D = $Players
@onready var _title_background: ColorRect = %TitleBackground
var _gameplay_started: bool = false var _gameplay_started: bool = false
var _shop_interaction: FishingShopInteractionType var _shop_interaction: FishingShopInteractionType
@ -154,6 +155,7 @@ var _pending_existing_root_path := ""
func _ready() -> void: func _ready() -> void:
DisplayServer.window_set_title("NETfishing")
if not _settings_manager.settings_changed.is_connected( if not _settings_manager.settings_changed.is_connected(
_apply_runtime_settings _apply_runtime_settings
): ):
@ -486,7 +488,7 @@ func _initialize_after_data_root() -> void:
func _show_data_root_setup() -> void: func _show_data_root_setup() -> void:
if _data_setup_dialog == null: if _data_setup_dialog == null:
_data_setup_dialog = ConfirmationDialog.new() _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.ok_button_text = "Use This Folder"
_data_setup_dialog.cancel_button_text = "Quit" _data_setup_dialog.cancel_button_text = "Quit"
_data_setup_dialog.confirmed.connect(_use_default_data_root) _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 default_path: String = _data_root.default_visible_path()
var legacy: bool = PortableDataMigration.legacy_files_present() var legacy: bool = PortableDataMigration.legacy_files_present()
_data_setup_dialog.dialog_text = ( _data_setup_dialog.dialog_text = (
("Move your existing NETFISHING data to an easy-to-find folder." ("Move your existing NETfishing data to an easy-to-find folder."
if legacy else "Choose where NETFISHING stores your player data.") if legacy else "Choose where NETfishing stores your player data.")
+ "\n\n%s\n\nYou can choose a Syncthing folder." + "\n\n%s\n\nYou can choose a Syncthing folder."
% (default_path if not default_path.is_empty() else "Choose a 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(): if not _data_root.error_message.is_empty():
_data_setup_dialog.dialog_text = ( _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_root.error_message
) )
_data_setup_dialog.popup_centered(Vector2i(640, 360)) _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: func _show_existing_root_choice(path: String) -> void:
_pending_existing_root_path = path _pending_existing_root_path = path
var dialog: ConfirmationDialog = ConfirmationDialog.new() 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.ok_button_text = "Use Data Already in Selected Folder"
dialog.cancel_button_text = "Cancel" dialog.cancel_button_text = "Cancel"
dialog.dialog_text = ( 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." + "Choose which complete data set to use. Data is never merged automatically."
) )
dialog.add_button( dialog.add_button(
@ -869,12 +871,14 @@ func _reset_pixelation() -> void:
func _set_gameplay_active(active: bool) -> void: func _set_gameplay_active(active: bool) -> void:
_gameplay_started = active _gameplay_started = active
_title_background.visible = not active
_world_pixelation.set_gameplay_active(active) _world_pixelation.set_gameplay_active(active)
_ui_pixelation.set_gameplay_active(active) _ui_pixelation.set_gameplay_active(active)
if not active: if not active:
_player.item_effects.reset_all() _player.item_effects.reset_all()
_player.set_movement_enabled(active) _player.set_movement_enabled(active)
_player.set_camera_input_enabled(active) _player.set_camera_input_enabled(active)
_player.set_camera_active(active)
_fishing_spot.set_gameplay_input_enabled(active) _fishing_spot.set_gameplay_input_enabled(active)
_water_recovery.set_recovery_enabled(active) _water_recovery.set_recovery_enabled(active)
_game_ui.set_gameplay_ui_enabled(active) _game_ui.set_gameplay_ui_enabled(active)

View file

@ -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://world/test_world.tscn" id="1_world"]
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"] [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/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://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="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"] [node name="Main" type="Node3D"]
script = ExtResource("3_main") script = ExtResource("3_main")
@ -47,6 +51,20 @@ pelican_buyer_profile = ExtResource("7_pelicans")
main_shop_buyer_profile = ExtResource("12_main_shop") main_shop_buyer_profile = ExtResource("12_main_shop")
item_catalog = ExtResource("11_items") 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="."] [node name="InterfaceFontController" type="Node" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
script = ExtResource("39_fonts") script = ExtResource("39_fonts")

View file

@ -189,7 +189,7 @@ func import_backup(
) )
_finish( _finish(
bool(result.get("ok", false)), bool(result.get("ok", false)),
"Identity imported. Restart NETFISHING before multiplayer." "Identity imported. Restart NETfishing before multiplayer."
if bool(result.get("ok", false)) if bool(result.get("ok", false))
else "Could not install this identity backup.", else "Could not install this identity backup.",
) )

View file

@ -177,7 +177,7 @@ static func make_server_hello(
"protocol_version": PROTOCOL_VERSION, "protocol_version": PROTOCOL_VERSION,
"session_id": session_id, "session_id": session_id,
"assigned_peer_id": assigned_peer_id, "assigned_peer_id": assigned_peer_id,
"server_display_name": "NETFISHING", "server_display_name": "NETfishing",
"player_count": player_count, "player_count": player_count,
"max_players": max_players, "max_players": max_players,
"capability_flags": PackedStringArray([ "capability_flags": PackedStringArray([

View file

@ -787,7 +787,7 @@ func resolve_server_trust(accepted: bool) -> void:
if not _server_trust.trust( if not _server_trust.trust(
get_current_endpoint(), get_current_endpoint(),
str(_pending_server_proof["server_fingerprint"]), str(_pending_server_proof["server_fingerprint"]),
"NETFISHING", "NETfishing",
): ):
_fail_identity("The server identity could not be pinned.") _fail_identity("The server identity could not be pinned.")
return return

View file

@ -86,16 +86,16 @@ func select_new_root(path: String, app_data: bool = false) -> bool:
if FileAccess.file_exists(manifest_path): if FileAccess.file_exists(manifest_path):
var manifest: Dictionary = _read_json(manifest_path) var manifest: Dictionary = _read_json(manifest_path)
if not _valid_manifest(manifest): 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"]) root_id = str(manifest["root_id"])
else: else:
if not _directory_is_empty(normalized) and not app_data: if not _directory_is_empty(normalized) and not app_data:
return _fail( 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() root_id = Crypto.new().generate_random_bytes(16).hex_encode()
if not _create_layout(normalized, root_id): 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): if not _write_bootstrap(normalized, root_id):
return _fail("The data-folder pointer could not be saved.") return _fail("The data-folder pointer could not be saved.")
root_path = normalized root_path = normalized
@ -207,15 +207,15 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool
return false return false
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME) var manifest_path: String = normalized.path_join(MANIFEST_FILENAME)
if not FileAccess.file_exists(manifest_path): 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) var manifest: Dictionary = _read_json(manifest_path)
if not _valid_manifest(manifest): 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"]) var found_id: String = str(manifest["root_id"])
if not expected_id.is_empty() and expected_id != found_id: if not expected_id.is_empty() and expected_id != found_id:
return _fail("The selected data folder does not match this device pointer.") return _fail("The selected data folder does not match this device pointer.")
if not _test_writable(normalized): 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_path = normalized
root_id = found_id root_id = found_id
requires_selection = false requires_selection = false
@ -270,7 +270,7 @@ func _create_layout(path: String, id: String) -> bool:
): ):
return false return false
var readme: String = ( 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" + "This folder is safe to synchronize with tools such as Syncthing.\n"
+ "Active private identity keys remain device-local.\n" + "Active private identity keys remain device-local.\n"
+ "Encrypted identity backups require their passphrase.\n" + "Encrypted identity backups require their passphrase.\n"

View file

@ -33,12 +33,12 @@ static func migrate_legacy_to(
return { return {
"ok": false, "ok": false,
"requires_existing_root_decision": true, "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): if not _directory_empty(normalized):
return { return {
"ok": false, "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" % [ var staging: String = "%s.migration-%s" % [
normalized, Crypto.new().generate_random_bytes(8).hex_encode() normalized, Crypto.new().generate_random_bytes(8).hex_encode()
@ -107,11 +107,11 @@ static func migrate_active_to(
"ok": false, "ok": false,
"requires_existing_root_decision": true, "requires_existing_root_decision": true,
"message": ( "message": (
"The selected folder already contains NETFISHING data. " "The selected folder already contains NETfishing data. "
+ "Choose it through the existing-data recovery flow." + "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" % [ var staging: String = "%s.migration-%s" % [
normalized, Crypto.new().generate_random_bytes(8).hex_encode() 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 normalized: String = destination.simplify_path().trim_suffix("/")
var manifest: String = normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME) var manifest: String = normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)
if not FileAccess.file_exists(manifest): 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( var recovery: String = ProjectSettings.globalize_path(
"user://migration-recovery" "user://migration-recovery"
).path_join( ).path_join(
@ -209,7 +209,7 @@ static func replace_existing_with_active(
) -> Dictionary: ) -> Dictionary:
var normalized: String = destination.simplify_path().trim_suffix("/") var normalized: String = destination.simplify_path().trim_suffix("/")
if not FileAccess.file_exists(normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)): 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( var recovery: String = ProjectSettings.globalize_path(
"user://migration-recovery" "user://migration-recovery"
).path_join( ).path_join(

View file

@ -26,8 +26,7 @@ var appearance_snapshot: Dictionary = (
func apply_appearance_snapshot(snapshot: Dictionary) -> void: func apply_appearance_snapshot(snapshot: Dictionary) -> void:
if CharacterCustomizationCatalog.validate_snapshot(snapshot): if CharacterCustomizationCatalog.validate_snapshot(snapshot):
appearance_snapshot = snapshot.duplicate(true) appearance_snapshot = snapshot.duplicate(true)
# Current gameplay art is monolithic. This seam intentionally stores the PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot)
# validated state until modular visual parts are available.
class ShowcaseCameraSnapshot: class ShowcaseCameraSnapshot:
extends RefCounted extends RefCounted
@ -495,6 +494,10 @@ func set_camera_input_enabled(enabled: bool) -> void:
_camera_dragging = false _camera_dragging = false
func set_camera_active(active: bool) -> void:
_camera.current = active
func apply_camera_settings( func apply_camera_settings(
new_mouse_sensitivity: float, new_mouse_sensitivity: float,
new_controller_sensitivity: float, new_controller_sensitivity: float,

View file

@ -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

View file

@ -0,0 +1 @@
uid://b4w23asm85rtu

View file

@ -1,10 +1,10 @@
NETFISHING NETfishing
Pre-Alpha Playtest 0.2 Pre-Alpha Playtest 0.2
Version 0.2.0-prealpha.2 Version 0.2.0-prealpha.2
Thank you for trying this early private playtest. 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 multiplayer-first game. Networking and multiplayer are NOT implemented in
this build. this build.

View file

@ -96,6 +96,9 @@ func _unhandled_input(event: InputEvent) -> void:
): ):
open_chat() open_chat()
get_viewport().set_input_as_handled() 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: elif event.keycode == KEY_ESCAPE and _opened:
close_chat() close_chat()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()

View file

@ -72,9 +72,6 @@ var _item_effects: PlayerItemEffectsType
func _ready() -> void: func _ready() -> void:
_pause_menu.chat_requested.connect(
func() -> void: call_deferred("_open_chat_from_pause")
)
_title_settings_panel.panel_visibility_changed.connect( _title_settings_panel.panel_visibility_changed.connect(
_on_settings_visibility_changed _on_settings_visibility_changed
) )
@ -89,10 +86,6 @@ func _ready() -> void:
) )
func _open_chat_from_pause() -> void:
_chat_ui.open_chat()
func setup( func setup(
player: PlayerType, player: PlayerType,
inventory: FishInventoryType, inventory: FishInventoryType,

View file

@ -49,6 +49,7 @@ var _current_mail_id := ""
func _ready() -> void: func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
UtilityPageStyle.apply_page(self)
_build_ui() _build_ui()
@ -75,6 +76,7 @@ func setup(
func activate() -> void: func activate() -> void:
_show_inbox() _show_inbox()
UtilityPageStyle.animate_in(self)
call_deferred("_focus_first") call_deferred("_focus_first")
@ -103,14 +105,7 @@ func _build_ui() -> void:
var paper := PanelContainer.new() var paper := PanelContainer.new()
paper.position = Vector2(116, 126) paper.position = Vector2(116, 126)
paper.size = Vector2(1048, 470) paper.size = Vector2(1048, 470)
var style := StyleBoxFlat.new() paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style())
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)
add_child(paper) add_child(paper)
var margin := MarginContainer.new() var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 34) margin.add_theme_constant_override("margin_left", 34)
@ -132,13 +127,14 @@ func _build_ui() -> void:
_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_status.add_theme_color_override("font_color", Color("4a3f31")) _status.add_theme_color_override("font_color", Color("4a3f31"))
root.add_child(_status) root.add_child(_status)
_style_controls(root)
_show_inbox() _show_inbox()
func _build_inbox() -> Control: func _build_inbox() -> Control:
var page := Control.new() var page := Control.new()
var title := Label.new() var title := Label.new()
title.text = "session mail" title.text = "Mail"
title.position = Vector2(12, 0) title.position = Vector2(12, 0)
title.size = Vector2(500, 42) title.size = Vector2(500, 42)
title.add_theme_font_size_override("font_size", 30) 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.custom_minimum_size = Vector2(930, 54)
button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.pressed.connect(_open_letter.bind(str(letter["mail_id"]))) button.pressed.connect(_open_letter.bind(str(letter["mail_id"])))
UtilityPageStyle.apply_button(button)
_inbox_list.add_child(button) _inbox_list.add_child(button)
@ -601,3 +598,24 @@ func _focus_first() -> void:
var buttons := _inbox_list.find_children("*", "Button", true, false) var buttons := _inbox_list.find_children("*", "Button", true, false)
if not buttons.is_empty(): if not buttons.is_empty():
(buttons.front() as Button).grab_focus() (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)
)

View file

@ -26,17 +26,17 @@ const DIRECT_WORKFLOW_HELP: String = (
@onready var _name_helper: Label = %NameHelper @onready var _name_helper: Label = %NameHelper
@onready var _server_list: ItemList = %ServerList @onready var _server_list: ItemList = %ServerList
@onready var _details: Label = %Details @onready var _details: Label = %Details
@onready var _direct_button: BubbleButton = %DirectButton @onready var _direct_button: Button = %DirectButton
@onready var _saved_button: BubbleButton = %SavedButton @onready var _saved_button: Button = %SavedButton
@onready var _recent_button: BubbleButton = %RecentButton @onready var _recent_button: Button = %RecentButton
@onready var _join_button: BubbleButton = %JoinButton @onready var _join_button: Button = %JoinButton
@onready var _save_button: BubbleButton = %SaveButton @onready var _save_button: Button = %SaveButton
@onready var _edit_button: BubbleButton = %EditButton @onready var _edit_button: Button = %EditButton
@onready var _favorite_button: BubbleButton = %FavoriteButton @onready var _favorite_button: Button = %FavoriteButton
@onready var _delete_button: BubbleButton = %DeleteButton @onready var _delete_button: Button = %DeleteButton
@onready var _cancel_button: BubbleButton = %CancelButton @onready var _cancel_button: Button = %CancelButton
@onready var _back_button: BubbleButton = %BackButton @onready var _back_button: Button = %BackButton
@onready var _open_close_button: BubbleButton = %OpenCloseButton @onready var _open_close_button: Button = %OpenCloseButton
@onready var _status: Label = %Status @onready var _status: Label = %Status
@onready var _session_summary: Label = %SessionSummary @onready var _session_summary: Label = %SessionSummary
@onready var _delete_confirmation: BubbleConfirmationPage = ( @onready var _delete_confirmation: BubbleConfirmationPage = (
@ -56,6 +56,19 @@ var _delete_armed: bool = false
func _ready() -> void: 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)) _direct_button.pressed.connect(_set_mode.bind(Mode.DIRECT))
_saved_button.pressed.connect(_set_mode.bind(Mode.SAVED)) _saved_button.pressed.connect(_set_mode.bind(Mode.SAVED))
_recent_button.pressed.connect(_set_mode.bind(Mode.RECENT)) _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(): elif _address.text.is_empty():
_address.text = "127.0.0.1:7777" _address.text = "127.0.0.1:7777"
show() show()
UtilityPageStyle.animate_in(self)
_set_mode(_mode) _set_mode(_mode)
if _mode == Mode.DIRECT: if _mode == Mode.DIRECT:
_address.grab_focus() _address.grab_focus()

View file

@ -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="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="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"] [ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="4_confirmation"]
[sub_resource type="StyleBoxFlat" id="StyleBox_paper"] [sub_resource type="StyleBoxFlat" id="StyleBox_paper"]
@ -128,41 +127,23 @@ layout_mode = 2
theme_override_constants/separation = 14 theme_override_constants/separation = 14
alignment = 1 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 unique_name_in_owner = true
custom_minimum_size = Vector2(100, 72) custom_minimum_size = Vector2(100, 72)
layout_mode = 2 layout_mode = 2
text = "direct" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(100, 72) custom_minimum_size = Vector2(100, 72)
layout_mode = 2 layout_mode = 2
text = "saved" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(100, 72) custom_minimum_size = Vector2(100, 72)
layout_mode = 2 layout_mode = 2
text = "recent" 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"] [node name="AddressLabel" type="Label" parent="Paper/Margin/Layout"]
unique_name_in_owner = true unique_name_in_owner = true
@ -290,106 +271,58 @@ layout_mode = 2
theme_override_constants/separation = 7 theme_override_constants/separation = 7
alignment = 1 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 unique_name_in_owner = true
custom_minimum_size = Vector2(82, 72) custom_minimum_size = Vector2(82, 72)
layout_mode = 2 layout_mode = 2
text = "join" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(82, 72) custom_minimum_size = Vector2(82, 72)
layout_mode = 2 layout_mode = 2
text = "save\nserver" 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 unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(76, 68) custom_minimum_size = Vector2(76, 68)
layout_mode = 2 layout_mode = 2
text = "edit" 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 unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(88, 68) custom_minimum_size = Vector2(88, 68)
layout_mode = 2 layout_mode = 2
text = "favorite" 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 unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(82, 68) custom_minimum_size = Vector2(82, 68)
layout_mode = 2 layout_mode = 2
text = "delete" 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 unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(88, 72) custom_minimum_size = Vector2(88, 72)
layout_mode = 2 layout_mode = 2
text = "open\ngame" 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 unique_name_in_owner = true
visible = false visible = false
custom_minimum_size = Vector2(76, 68) custom_minimum_size = Vector2(76, 68)
layout_mode = 2 layout_mode = 2
text = "cancel" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(76, 68) custom_minimum_size = Vector2(76, 68)
layout_mode = 2 layout_mode = 2
text = "back" 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")] [node name="DeleteConfirmation" parent="." instance=ExtResource("4_confirmation")]
unique_name_in_owner = true unique_name_in_owner = true

View file

@ -5,8 +5,6 @@ const INPUT_OWNER: StringName = &"game_menu"
const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0) const PAUSE_DESKTOP_REFERENCE_SIZE: Vector2 = Vector2(1280.0, 720.0)
const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0) const PAUSE_COMPACT_REFERENCE_SIZE: Vector2 = Vector2(640.0, 480.0)
const COMPACT_HEIGHT_THRESHOLD: float = 560.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 VISIBILITY_FADE_DURATION: float = 0.55
const BACKDROP_TARGET_ALPHA: float = 0.68 const BACKDROP_TARGET_ALPHA: float = 0.68
const PlayerType = preload("res://player/player.gd") const PlayerType = preload("res://player/player.gd")
@ -28,7 +26,6 @@ signal reset_progress_requested
signal quit_requested signal quit_requested
signal menu_visibility_changed(is_open: bool) signal menu_visibility_changed(is_open: bool)
signal join_game_requested(endpoint: String) signal join_game_requested(endpoint: String)
signal chat_requested
enum ConfirmationAction { enum ConfirmationAction {
NONE, NONE,
@ -88,7 +85,6 @@ func _ready() -> void:
_save_button.pressed.connect(_save_now) _save_button.pressed.connect(_save_now)
%SettingsButton.pressed.connect(_open_settings) %SettingsButton.pressed.connect(_open_settings)
%JoinGameButton.pressed.connect(_open_join_game) %JoinGameButton.pressed.connect(_open_join_game)
%ChatButton.pressed.connect(_request_chat)
%ReturnToTitleButton.pressed.connect(_confirm_return_to_title) %ReturnToTitleButton.pressed.connect(_confirm_return_to_title)
%ResetProgressButton.pressed.connect(_confirm_reset_progress) %ResetProgressButton.pressed.connect(_confirm_reset_progress)
%QuitButton.pressed.connect(_request_quit) %QuitButton.pressed.connect(_request_quit)
@ -96,6 +92,7 @@ func _ready() -> void:
_confirmation_page.cancelled.connect(_close_confirmation) _confirmation_page.cancelled.connect(_close_confirmation)
_settings_panel.applied.connect(_on_settings_applied) _settings_panel.applied.connect(_on_settings_applied)
_settings_panel.closed.connect(_on_settings_closed) _settings_panel.closed.connect(_on_settings_closed)
_settings_panel.closing.connect(_on_settings_closing)
_settings_panel.navigation_transition_started.connect( _settings_panel.navigation_transition_started.connect(
_emit_transition_flurry _emit_transition_flurry
) )
@ -107,13 +104,6 @@ func _ready() -> void:
call_deferred("_update_responsive_pause_stage") 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( func setup(
player: PlayerType, player: PlayerType,
save_manager: SaveManagerType, save_manager: SaveManagerType,
@ -287,13 +277,20 @@ func _on_settings_applied() -> void:
if _closing_menu: if _closing_menu:
return return
_feedback.text = "settings saved." _feedback.text = "settings saved."
_begin_root_entry(true)
func _on_settings_closed() -> void: func _on_settings_closed() -> void:
pass
func _on_settings_closing() -> void:
if _closing_menu: if _closing_menu:
return 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: func _confirm_return_to_title() -> void:
@ -364,7 +361,7 @@ func _open_confirmation(
func _show_confirmation() -> void: func _show_confirmation() -> void:
_confirmation_page.transition_in( _confirmation_page.transition_in(
PAGE_INCOMING_DURATION, 0.0,
_finish_confirmation_open _finish_confirmation_open
) )
@ -379,7 +376,7 @@ func _close_confirmation() -> void:
_confirmation_action = ConfirmationAction.NONE _confirmation_action = ConfirmationAction.NONE
_emit_transition_flurry() _emit_transition_flurry()
_confirmation_page.transition_out( _confirmation_page.transition_out(
PAGE_OUTGOING_DURATION, 0.0,
_finish_confirmation_cancel _finish_confirmation_cancel
) )
@ -448,7 +445,7 @@ func _begin_root_entry(flurry_already_emitted: bool) -> void:
_root_page.transition_in( _root_page.transition_in(
true, true,
_finish_root_entry.bind(generation), _finish_root_entry.bind(generation),
PAGE_INCOMING_DURATION 0.0
) )
@ -466,19 +463,23 @@ func _begin_root_exit(completed: Callable) -> void:
_emit_transition_flurry() _emit_transition_flurry()
var generation: int = _root_transition_generation var generation: int = _root_transition_generation
_root_page.transition_out( _root_page.transition_out(
_finish_root_exit.bind(generation, completed), _finish_root_exit.bind(generation),
PAGE_OUTGOING_DURATION 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( func _finish_root_exit(
generation: int, generation: int,
completed: Callable,
) -> void: ) -> void:
if generation != _root_transition_generation or not visible: if generation != _root_transition_generation or not visible:
return return
_root_transition_active = false _root_transition_active = false
completed.call()
func _finish_user_close() -> void: func _finish_user_close() -> void:

View file

@ -77,16 +77,14 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("4_page") script = ExtResource("4_page")
page_id = &"pause" 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")]) 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/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/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")])
initial_focus_path = NodePath("BubbleCluster/ResumeButton") initial_focus_path = NodePath("BubbleCluster/ResumeButton")
back_focus_path = NodePath("BubbleCluster/ResumeButton") back_focus_path = NodePath("BubbleCluster/ResumeButton")
maximum_layout_size = Vector2(720, 520) maximum_layout_size = Vector2(720, 520)
compact_maximum_layout_size = Vector2(544, 400) compact_maximum_layout_size = Vector2(544, 400)
compact_width_threshold = 680.0 compact_width_threshold = 680.0
compact_height_threshold = 500.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"] [node name="BubbleCluster" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage"]
layout_mode = 0 layout_mode = 0
@ -135,18 +133,6 @@ minimum_font_size = 16
maximum_font_size = 23 maximum_font_size = 23
motion_phase = 2.05 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")] [node name="JoinGameButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0) custom_minimum_size = Vector2(0, 0)

View file

@ -62,8 +62,6 @@ const LogbookEntryScene = preload(
"res://ui/components/bubble_menu/logbook_entry.tscn" "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_OUT_DURATION: float = 0.34
const PAGE_IN_DURATION: float = 0.42 const PAGE_IN_DURATION: float = 0.42
const LOGBOOK_PAGE_DURATION: float = 0.18 const LOGBOOK_PAGE_DURATION: float = 0.18
@ -150,7 +148,6 @@ enum CloseReason {
@onready var _bag_host: Control = %BagHost @onready var _bag_host: Control = %BagHost
@onready var _bag_item_field: Control = %BagItemField @onready var _bag_item_field: Control = %BagItemField
@onready var _bag_empty_state: Label = %BagEmptyState @onready var _bag_empty_state: Label = %BagEmptyState
@onready var _bag_instruction_bubble: PanelContainer = %BagInstructionBubble
@onready var _bag_detail_constellation: Control = %BagDetailConstellation @onready var _bag_detail_constellation: Control = %BagDetailConstellation
@onready var _bag_sprite_detail_bubble: Control = %BagDetailBubble @onready var _bag_sprite_detail_bubble: Control = %BagDetailBubble
@onready var _bag_sprite_detail_texture: TextureRect = %BagSpriteDetailTexture @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_top = 9.0
bubble.content_margin_right = 12.0 bubble.content_margin_right = 12.0
bubble.content_margin_bottom = 9.0 bubble.content_margin_bottom = 9.0
_bag_instruction_bubble.add_theme_stylebox_override("panel", bubble)
_bag_sprite_detail_bubble.add_theme_stylebox_override( _bag_sprite_detail_bubble.add_theme_stylebox_override(
"panel", "panel",
bubble.duplicate(), bubble.duplicate(),
@ -1133,7 +1129,7 @@ func _update_shell_layout() -> void:
_bag_page.position = Vector2.ZERO _bag_page.position = Vector2.ZERO
_bag_rest_position = Vector2.ZERO _bag_rest_position = Vector2.ZERO
_bag_outer_wall.position = ( _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 = ( _bag_outer_wall.size = (
Vector2(612.0, 200.0) if compact else Vector2(896.0, 442.0) 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) Vector2(520.0, 140.0) if compact else Vector2(820.0, 350.0)
) )
_bag_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED _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 = ( _bag_detail_constellation.position = (
Vector2.ZERO if compact else Vector2(984.0, 266.0) 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 _profile_page.position = _profile_rest_position + Vector2.DOWN * start_offset
var navigation_rest: Vector2 = _navigation_cluster.position var navigation_rest: Vector2 = _navigation_cluster.position
_navigation_cluster.position = navigation_rest + Vector2.DOWN * start_offset _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 = create_tween().set_parallel(true)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_content_shell, _content_shell,
"position", "position",
_presentation_rest_position, _presentation_rest_position,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_cooler_page, _cooler_page,
"position", "position",
_cooler_rest_position, _cooler_rest_position,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_bag_page, _bag_page,
"position", "position",
_bag_rest_position, _bag_rest_position,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_logbook_page, _logbook_page,
"position", "position",
_logbook_rest_position, _logbook_rest_position,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_mail_page, _mail_page,
"position", "position",
_mail_rest_position, _mail_rest_position,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_profile_page, _profile_page,
"position", "position",
_profile_rest_position, _profile_rest_position,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_navigation_cluster, _navigation_cluster,
"position", "position",
navigation_rest, navigation_rest,
MENU_ENTER_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.chain().tween_callback( _presentation_tween.chain().tween_callback(
_finish_menu_entry.bind(generation) _finish_menu_entry.bind(generation)
@ -1407,48 +1398,51 @@ func _begin_menu_exit(reason: CloseReason, restore_controls: bool) -> void:
_content_shell.size.y, _content_shell.size.y,
_navigation_cluster.size.y _navigation_cluster.size.y
) - TRANSITION_SAFE_MARGIN ) - TRANSITION_SAFE_MARGIN
var transition_duration := UIMotion.bubble_duration(
end_y - _navigation_cluster.position.y
)
_presentation_tween = create_tween().set_parallel(true) _presentation_tween = create_tween().set_parallel(true)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_content_shell, _content_shell,
"position:y", "position:y",
end_y, end_y,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_cooler_page, _cooler_page,
"position:y", "position:y",
-_cooler_page.size.y - TRANSITION_SAFE_MARGIN, -_cooler_page.size.y - TRANSITION_SAFE_MARGIN,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_bag_page, _bag_page,
"position:y", "position:y",
-_bag_page.size.y - TRANSITION_SAFE_MARGIN, -_bag_page.size.y - TRANSITION_SAFE_MARGIN,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_logbook_page, _logbook_page,
"position:y", "position:y",
-_logbook_page.size.y - TRANSITION_SAFE_MARGIN, -_logbook_page.size.y - TRANSITION_SAFE_MARGIN,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_mail_page, _mail_page,
"position:y", "position:y",
-_mail_page.size.y - TRANSITION_SAFE_MARGIN, -_mail_page.size.y - TRANSITION_SAFE_MARGIN,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_profile_page, _profile_page,
"position:y", "position:y",
-_profile_page.size.y - TRANSITION_SAFE_MARGIN, -_profile_page.size.y - TRANSITION_SAFE_MARGIN,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.tween_property( _presentation_tween.tween_property(
_navigation_cluster, _navigation_cluster,
"position:y", "position:y",
-_navigation_cluster.size.y - TRANSITION_SAFE_MARGIN, -_navigation_cluster.size.y - TRANSITION_SAFE_MARGIN,
MENU_EXIT_DURATION transition_duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_presentation_tween.chain().tween_callback( _presentation_tween.chain().tween_callback(
_finish_menu_exit.bind( _finish_menu_exit.bind(
@ -2009,14 +2003,10 @@ func _update_bag_detail() -> void:
_bag_detail_constellation.visible = ( _bag_detail_constellation.visible = (
item != null and _current_section == Section.BAG item != null and _current_section == Section.BAG
) )
_bag_instruction_bubble.visible = (
item == null and _current_section == Section.BAG
)
func _close_bag_detail() -> void: func _close_bag_detail() -> void:
_bag_detail_constellation.visible = false _bag_detail_constellation.visible = false
_bag_instruction_bubble.visible = _current_section == Section.BAG
func _on_bag_field_gui_input(event: InputEvent) -> void: func _on_bag_field_gui_input(event: InputEvent) -> void:

View file

@ -448,9 +448,9 @@ mouse_filter = 1
[node name="BagOuterWall" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"] [node name="BagOuterWall" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 0 layout_mode = 0
offset_left = 72.0 offset_left = 122.0
offset_top = 132.0 offset_top = 132.0
offset_right = 968.0 offset_right = 1018.0
offset_bottom = 574.0 offset_bottom = 574.0
[node name="BagOuterMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall"] [node name="BagOuterMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall"]
@ -507,25 +507,6 @@ text = "your bag is empty"
horizontal_alignment = 1 horizontal_alignment = 1
vertical_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"] [node name="BagDetailConstellation" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false

View file

@ -11,6 +11,7 @@ var _current_tab := 0
func _ready() -> void: func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
UtilityPageStyle.apply_page(self)
_build() _build()
@ -26,6 +27,7 @@ func setup(service: NetworkPlayerListService) -> void:
func activate() -> void: func activate() -> void:
_refresh() _refresh()
UtilityPageStyle.animate_in(self)
_focus_first() _focus_first()
@ -38,16 +40,7 @@ func _build() -> void:
paper.position = Vector2(58, 128) paper.position = Vector2(58, 128)
paper.size = Vector2(1164, 476) paper.size = Vector2(1164, 476)
add_child(paper) add_child(paper)
var style := StyleBoxFlat.new() paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style())
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)
var root := VBoxContainer.new() var root := VBoxContainer.new()
root.add_theme_constant_override("separation", 10) root.add_theme_constant_override("separation", 10)
paper.add_child(root) paper.add_child(root)
@ -63,6 +56,7 @@ func _build() -> void:
button.text = ["players", "relationships", "banned"][index] button.text = ["players", "relationships", "banned"][index]
button.toggle_mode = true button.toggle_mode = true
button.pressed.connect(_select_tab.bind(index)) button.pressed.connect(_select_tab.bind(index))
UtilityPageStyle.apply_button(button)
_tabs.add_child(button) _tabs.add_child(button)
var scroll := ScrollContainer.new() var scroll := ScrollContainer.new()
scroll.custom_minimum_size = Vector2(0, 330) scroll.custom_minimum_size = Vector2(0, 330)
@ -88,7 +82,7 @@ func _select_tab(index: int) -> void:
func _refresh() -> void: func _refresh() -> void:
if _service == null or _list == null: if _service == null or _list == null:
return return
_header.text = "players %d / %d connected" % [ _header.text = "Players\n%d / %d connected" % [
_service.get_connected_count(), _service.get_max_players(), _service.get_connected_count(), _service.get_max_players(),
] ]
for index: int in _tabs.get_child_count(): for index: int in _tabs.get_child_count():
@ -129,6 +123,9 @@ func _build_active_rows() -> void:
identity.tooltip_text = NetworkIdentityCrypto.format_fingerprint( identity.tooltip_text = NetworkIdentityCrypto.format_fingerprint(
entry.full_fingerprint entry.full_fingerprint
) )
identity.add_theme_color_override(
"font_color", UtilityPageStyle.INK
)
row.add_child(identity) row.add_child(identity)
var ping := Label.new() var ping := Label.new()
ping.custom_minimum_size.x = 80 ping.custom_minimum_size.x = 80
@ -137,26 +134,31 @@ func _build_active_rows() -> void:
else "%d ms" % entry.ping_to_host_ms else "%d ms" % entry.ping_to_host_ms
if entry.ping_to_host_ms >= 0 else "" if entry.ping_to_host_ms >= 0 else ""
) )
ping.add_theme_color_override("font_color", UtilityPageStyle.INK)
row.add_child(ping) row.add_child(ping)
var mute := Button.new() var mute := Button.new()
mute.text = "unmute" if entry.muted else "mute" mute.text = "unmute" if entry.muted else "mute"
mute.disabled = entry.is_local_player mute.disabled = entry.is_local_player
mute.pressed.connect(_toggle_mute.bind(entry)) mute.pressed.connect(_toggle_mute.bind(entry))
UtilityPageStyle.apply_button(mute)
row.add_child(mute) row.add_child(mute)
var block := Button.new() var block := Button.new()
block.text = "block" block.text = "block"
block.disabled = entry.is_local_player block.disabled = entry.is_local_player
block.pressed.connect(_confirm_block.bind(entry)) block.pressed.connect(_confirm_block.bind(entry))
UtilityPageStyle.apply_button(block)
row.add_child(block) row.add_child(block)
var kick := Button.new() var kick := Button.new()
kick.text = "kick" kick.text = "kick"
kick.disabled = not entry.can_kick kick.disabled = not entry.can_kick
kick.pressed.connect(_confirm_kick.bind(entry)) kick.pressed.connect(_confirm_kick.bind(entry))
UtilityPageStyle.apply_button(kick)
row.add_child(kick) row.add_child(kick)
var ban := Button.new() var ban := Button.new()
ban.text = "ban" ban.text = "ban"
ban.disabled = not entry.can_ban ban.disabled = not entry.can_ban
ban.pressed.connect(_confirm_ban.bind(entry)) ban.pressed.connect(_confirm_ban.bind(entry))
UtilityPageStyle.apply_button(ban)
row.add_child(ban) row.add_child(ban)
_list.add_child(row) _list.add_child(row)
@ -177,6 +179,7 @@ func _build_relationship_rows() -> void:
"Blocked" if bool(record.get("blocked", false)) else "Muted", "Blocked" if bool(record.get("blocked", false)) else "Muted",
] ]
label.tooltip_text = NetworkIdentityCrypto.format_fingerprint(fingerprint) label.tooltip_text = NetworkIdentityCrypto.format_fingerprint(fingerprint)
label.add_theme_color_override("font_color", UtilityPageStyle.INK)
row.add_child(label) row.add_child(label)
if bool(record.get("blocked", false)): if bool(record.get("blocked", false)):
var unblock := Button.new() var unblock := Button.new()
@ -184,6 +187,7 @@ func _build_relationship_rows() -> void:
unblock.pressed.connect(func() -> void: unblock.pressed.connect(func() -> void:
_service.set_blocked(fingerprint, str(record["last_known_display_name"]), false) _service.set_blocked(fingerprint, str(record["last_known_display_name"]), false)
) )
UtilityPageStyle.apply_button(unblock)
row.add_child(unblock) row.add_child(unblock)
var unmute := Button.new() var unmute := Button.new()
unmute.text = "unmute" unmute.text = "unmute"
@ -191,6 +195,7 @@ func _build_relationship_rows() -> void:
unmute.pressed.connect(func() -> void: unmute.pressed.connect(func() -> void:
_service.set_muted(fingerprint, str(record["last_known_display_name"]), false) _service.set_muted(fingerprint, str(record["last_known_display_name"]), false)
) )
UtilityPageStyle.apply_button(unmute)
row.add_child(unmute) row.add_child(unmute)
_list.add_child(row) _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))), Time.get_date_string_from_unix_time(int(record.get("banned_unix", 0))),
] ]
label.tooltip_text = NetworkIdentityCrypto.format_fingerprint(fingerprint) label.tooltip_text = NetworkIdentityCrypto.format_fingerprint(fingerprint)
label.add_theme_color_override("font_color", UtilityPageStyle.INK)
row.add_child(label) row.add_child(label)
var unban := Button.new() var unban := Button.new()
unban.text = "unban" unban.text = "unban"
unban.pressed.connect(_confirm_unban.bind(fingerprint)) unban.pressed.connect(_confirm_unban.bind(fingerprint))
UtilityPageStyle.apply_button(unban)
row.add_child(unban) row.add_child(unban)
_list.add_child(row) _list.add_child(row)
@ -223,6 +230,7 @@ func _make_row() -> HBoxContainer:
var row := HBoxContainer.new() var row := HBoxContainer.new()
row.custom_minimum_size.y = 58 row.custom_minimum_size.y = 58
row.add_theme_constant_override("separation", 8) row.add_theme_constant_override("separation", 8)
row.add_theme_constant_override("outline_size", 1)
return row return row
@ -231,6 +239,7 @@ func _add_empty(text: String) -> void:
label.text = text label.text = text
label.custom_minimum_size.y = 100 label.custom_minimum_size.y = 100
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.add_theme_color_override("font_color", UtilityPageStyle.MUTED_INK)
_list.add_child(label) _list.add_child(label)

View file

@ -45,15 +45,18 @@ func setup(service: NetworkProfileService) -> void:
_load_persisted() _load_persisted()
var identity_value := find_child("IdentityFingerprint", true, false) as Label var identity_value := find_child("IdentityFingerprint", true, false) as Label
if identity_value != null: if identity_value != null:
identity_value.text = "%s • Stored on this device" % ( var fingerprint := _service.get_identity_fingerprint()
NetworkIdentityCrypto.format_fingerprint( identity_value.text = "Identity • %s • Stored on this device" % (
_service.get_identity_fingerprint() NetworkIdentityCrypto.compact_suffix(fingerprint)
) )
identity_value.tooltip_text = (
NetworkIdentityCrypto.format_fingerprint(fingerprint)
) )
func activate() -> void: func activate() -> void:
visible = true visible = true
UtilityPageStyle.animate_in(self)
if _service != null: if _service != null:
_load_persisted() _load_persisted()
_name_edit.grab_focus() _name_edit.grab_focus()
@ -100,29 +103,27 @@ func request_close_confirmation() -> bool:
func _build_ui() -> void: func _build_ui() -> void:
var paper := PanelContainer.new() var paper := PanelContainer.new()
paper.set_anchors_preset(Control.PRESET_FULL_RECT) paper.set_anchors_preset(Control.PRESET_FULL_RECT)
paper.offset_left = 42.0 paper.offset_left = 64.0
paper.offset_top = 14.0 paper.offset_top = 28.0
paper.offset_right = -42.0 paper.offset_right = -64.0
paper.offset_bottom = -14.0 paper.offset_bottom = -28.0
var style := StyleBoxFlat.new() paper.add_theme_stylebox_override(
style.bg_color = Color("f2ead3") "panel", UtilityPageStyle.panel_style()
style.border_color = Color("4a4238") )
style.set_border_width_all(4)
style.set_corner_radius_all(18)
paper.add_theme_stylebox_override("panel", style)
add_child(paper) add_child(paper)
UtilityPageStyle.apply_page(self)
var margin := MarginContainer.new() var margin := MarginContainer.new()
for side: String in ["left", "right", "top", "bottom"]: 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) paper.add_child(margin)
var layout := VBoxContainer.new() var layout := VBoxContainer.new()
layout.add_theme_constant_override("separation", 8) layout.add_theme_constant_override("separation", 5)
margin.add_child(layout) margin.add_child(layout)
var heading := Label.new() var heading := Label.new()
heading.text = "player profile" heading.text = "Player Profile"
heading.add_theme_font_size_override("font_size", 28) heading.add_theme_font_size_override("font_size", 25)
heading.add_theme_color_override("font_color", Color("302b27")) heading.add_theme_color_override("font_color", Color("302b27"))
layout.add_child(heading) layout.add_child(heading)
@ -139,7 +140,8 @@ func _build_ui() -> void:
_name_edit = LineEdit.new() _name_edit = LineEdit.new()
_name_edit.max_length = NetworkProtocol.MAX_DISPLAY_NAME_LENGTH _name_edit.max_length = NetworkProtocol.MAX_DISPLAY_NAME_LENGTH
_name_edit.placeholder_text = "Player" _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_edit.text_changed.connect(_on_name_changed)
name_stack.add_child(_name_edit) name_stack.add_child(_name_edit)
var helper := Label.new() var helper := Label.new()
@ -148,17 +150,20 @@ func _build_ui() -> void:
name_stack.add_child(helper) name_stack.add_child(helper)
_apply_button = Button.new() _apply_button = Button.new()
_apply_button.text = "apply" _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) _apply_button.pressed.connect(_apply)
name_row.add_child(_apply_button) name_row.add_child(_apply_button)
_revert_button = Button.new() _revert_button = Button.new()
_revert_button.text = "revert" _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) _revert_button.pressed.connect(_revert)
name_row.add_child(_revert_button) name_row.add_child(_revert_button)
var defaults_button := Button.new() var defaults_button := Button.new()
defaults_button.text = "defaults" 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")) defaults_button.pressed.connect(_show_confirmation.bind("defaults"))
name_row.add_child(defaults_button) name_row.add_child(defaults_button)
@ -169,13 +174,9 @@ func _build_ui() -> void:
_suggestions.add_theme_constant_override("separation", 8) _suggestions.add_theme_constant_override("separation", 8)
layout.add_child(_suggestions) 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() var identity_value := Label.new()
identity_value.name = "IdentityFingerprint" identity_value.name = "IdentityFingerprint"
identity_value.text = "Stored on this device" identity_value.text = "Identity • Stored on this device"
identity_value.tooltip_text = ( identity_value.tooltip_text = (
"This identity helps other players recognize you between sessions." "This identity helps other players recognize you between sessions."
) )
@ -184,13 +185,13 @@ func _build_ui() -> void:
var body := HBoxContainer.new() var body := HBoxContainer.new()
body.size_flags_vertical = Control.SIZE_EXPAND_FILL 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) layout.add_child(body)
_category_list = VBoxContainer.new() _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) body.add_child(_category_list)
_option_list = VBoxContainer.new() _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) body.add_child(_option_list)
var preview_stack := VBoxContainer.new() var preview_stack := VBoxContainer.new()
preview_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL preview_stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@ -198,15 +199,19 @@ func _build_ui() -> void:
body.add_child(preview_stack) body.add_child(preview_stack)
_preview = preload("res://ui/profile_preview.tscn").instantiate() _preview = preload("res://ui/profile_preview.tscn").instantiate()
preview_stack.add_child(_preview) 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() var preview_note := Label.new()
preview_note.text = "capsule preview • drag or use left / right" preview_note.text = "Current player • drag or use left / right"
preview_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
preview_note.add_theme_color_override("font_color", Color("514a42")) 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() var reset_view := Button.new()
reset_view.text = "reset view" reset_view.text = "reset view"
reset_view.pressed.connect(_preview.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 = PanelContainer.new()
_discard_confirmation.visible = false _discard_confirmation.visible = false
@ -249,6 +254,7 @@ func _build_categories() -> void:
button.custom_minimum_size = Vector2(0, 34) button.custom_minimum_size = Vector2(0, 34)
button.button_pressed = category_id == _category_id button.button_pressed = category_id == _category_id
button.pressed.connect(_select_category.bind(category_id)) button.pressed.connect(_select_category.bind(category_id))
UtilityPageStyle.apply_button(button)
_category_list.add_child(button) _category_list.add_child(button)
_refresh_options() _refresh_options()
@ -287,6 +293,7 @@ func _refresh_options() -> void:
button.custom_minimum_size = Vector2(0, 34) button.custom_minimum_size = Vector2(0, 34)
button.button_pressed = _draft_appearance.get(_category_id) == option_id button.button_pressed = _draft_appearance.get(_category_id) == option_id
button.pressed.connect(_select_option.bind(_category_id, option_id)) button.pressed.connect(_select_option.bind(_category_id, option_id))
UtilityPageStyle.apply_button(button)
_option_list.add_child(button) _option_list.add_child(button)

View file

@ -7,16 +7,25 @@ extends SubViewportContainer
@onready var _preview_root: Node3D = %PreviewRoot @onready var _preview_root: Node3D = %PreviewRoot
var _dragging: bool = false var _dragging: bool = false
var _visuals: Node3D
func _ready() -> void: func _ready() -> void:
focus_mode = Control.FOCUS_ALL focus_mode = Control.FOCUS_ALL
gui_input.connect(_on_gui_input) 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: func apply_appearance_profile(profile: Dictionary) -> void:
# The capsule is deliberately neutral until modular character assets exist. if _visuals != null:
pass PlayerVisualPresenter.apply_appearance(_visuals, profile)
func reset_view() -> void: func reset_view() -> void:

View file

@ -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"] [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"] [sub_resource type="Environment" id="Environment"]
background_mode = 1 background_mode = 1
background_color = Color(0.075, 0.13, 0.16, 1) background_color = Color(0.075, 0.13, 0.16, 1)
@ -21,13 +13,13 @@ ambient_light_energy = 0.65
environment = SubResource("Environment") environment = SubResource("Environment")
[node name="ProfilePreview" type="SubViewportContainer"] [node name="ProfilePreview" type="SubViewportContainer"]
custom_minimum_size = Vector2(360, 250) custom_minimum_size = Vector2(300, 220)
stretch = true stretch = true
script = ExtResource("1") script = ExtResource("1")
[node name="Viewport" type="SubViewport" parent="."] [node name="Viewport" type="SubViewport" parent="."]
transparent_bg = false transparent_bg = false
size = Vector2i(360, 390) size = Vector2i(320, 330)
render_target_update_mode = 4 render_target_update_mode = 4
world_3d = SubResource("PreviewWorld") world_3d = SubResource("PreviewWorld")
@ -48,10 +40,6 @@ light_energy = 1.3
[node name="PreviewRoot" type="Node3D" parent="Viewport"] [node name="PreviewRoot" type="Node3D" parent="Viewport"]
unique_name_in_owner = true 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"] [node name="Camera3D" type="Camera3D" parent="Viewport"]
position = Vector3(0, 0.15, 4.25) position = Vector3(0, 0.95, 1.9)
current = true current = true

View file

@ -11,8 +11,6 @@ extends Control
@export var compact_maximum_layout_size: Vector2 = Vector2.ZERO @export var compact_maximum_layout_size: Vector2 = Vector2.ZERO
@export var compact_width_threshold: float = 680.0 @export var compact_width_threshold: float = 680.0
@export var compact_height_threshold: float = 500.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 @export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
var _cluster: BubbleCluster var _cluster: BubbleCluster
@ -49,30 +47,35 @@ func show_page(should_focus: bool = true) -> void:
_cluster.position = _resting_cluster_position _cluster.position = _resting_cluster_position
set_process(true) set_process(true)
_set_interactive(true) _set_interactive(true)
var paper := get_node_or_null("Paper") as Control
if paper != null:
UtilityPageStyle.animate_in(paper)
if should_focus: if should_focus:
focus_initial() focus_initial()
func transition_out( func transition_out(
completed: Callable, completed: Callable,
duration_override: float = -1.0, _duration_override: float = -1.0,
) -> void: ) -> void:
_transition_generation += 1 _transition_generation += 1
_cancel_transition() _cancel_transition()
_is_transitioning = true _is_transitioning = true
_set_interactive(false) _set_interactive(false)
var paper := get_node_or_null("Paper") as Control
if paper != null:
UtilityPageStyle.animate_out(paper, Callable())
set_process(true) set_process(true)
var duration: float = ( var target_y: float = -_cluster.size.y - transition_safe_margin
duration_override var duration := UIMotion.bubble_duration(
if duration_override > 0.0 target_y - _cluster.position.y
else outgoing_rise_duration
) )
var generation: int = _transition_generation var generation: int = _transition_generation
_transition = create_tween() _transition = create_tween()
_transition.tween_property( _transition.tween_property(
_cluster, _cluster,
"position:y", "position:y",
-_cluster.size.y - transition_safe_margin, target_y,
duration duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_transition.finished.connect( _transition.finished.connect(
@ -84,7 +87,7 @@ func transition_out(
func transition_in( func transition_in(
should_focus: bool, should_focus: bool,
completed: Callable, completed: Callable,
duration_override: float = -1.0, _duration_override: float = -1.0,
) -> void: ) -> void:
_transition_generation += 1 _transition_generation += 1
_cancel_transition() _cancel_transition()
@ -92,15 +95,16 @@ func transition_in(
_is_transitioning = true _is_transitioning = true
_set_interactive(false) _set_interactive(false)
set_process(true) set_process(true)
var duration: float = (
duration_override
if duration_override > 0.0
else incoming_rise_duration
)
_cluster.position = Vector2( _cluster.position = Vector2(
_resting_cluster_position.x, _resting_cluster_position.x,
size.y + transition_safe_margin 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 var generation: int = _transition_generation
_transition = create_tween() _transition = create_tween()
_transition.tween_property( _transition.tween_property(
@ -208,7 +212,8 @@ func _finish_transition_out(generation: int, completed: Callable) -> void:
_is_transitioning = false _is_transitioning = false
hide() hide()
_cluster.position = _resting_cluster_position _cluster.position = _resting_cluster_position
completed.call() if completed.is_valid():
completed.call()
func _finish_transition_in( func _finish_transition_in(

View file

@ -8,8 +8,6 @@ const PAGE_DISPLAY: StringName = &"display"
const PAGE_CONTROLS: StringName = &"controls" const PAGE_CONTROLS: StringName = &"controls"
const PAGE_ACCESSIBILITY: StringName = &"accessibility" const PAGE_ACCESSIBILITY: StringName = &"accessibility"
const PAGE_DATA: StringName = &"data" 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 = [ const PIXELATION_NAMES: PackedStringArray = [
"legible", "legible",
"cute", "cute",
@ -23,6 +21,7 @@ const SettingsManagerType = preload(
signal applied signal applied
signal closed signal closed
signal closing
signal opened signal opened
signal crisp_reset_focus_requested signal crisp_reset_focus_requested
signal panel_visibility_changed(is_visible: bool) signal panel_visibility_changed(is_visible: bool)
@ -151,6 +150,19 @@ func _ready() -> void:
_invert_y_toggle.pressed.connect(_toggle_invert_y) _invert_y_toggle.pressed.connect(_toggle_invert_y)
_auto_click_toggle.pressed.connect(_toggle_auto_click) _auto_click_toggle.pressed.connect(_toggle_auto_click)
_readable_font_toggle.pressed.connect(_toggle_readable_font) _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)) %IntervalDecrease.pressed.connect(_adjust_auto_click_interval.bind(-1))
%IntervalIncrease.pressed.connect(_adjust_auto_click_interval.bind(1)) %IntervalIncrease.pressed.connect(_adjust_auto_click_interval.bind(1))
_auto_click_interval.pressed.connect( _auto_click_interval.pressed.connect(
@ -171,6 +183,7 @@ func _ready() -> void:
parent_control.resized.connect(_refresh_panel_size) parent_control.resized.connect(_refresh_panel_size)
for page: SettingsBubblePage in _pages.values(): for page: SettingsBubblePage in _pages.values():
page.hide_page() page.hide_page()
_style_data_page()
call_deferred("_refresh_panel_size") call_deferred("_refresh_panel_size")
@ -234,7 +247,7 @@ func open_panel(
_root_page.transition_in( _root_page.transition_in(
true, true,
_finish_animated_open.bind(generation), _finish_animated_open.bind(generation),
TITLE_HOST_INCOMING_DURATION 0.0
) )
else: else:
_show_active_page(true) _show_active_page(true)
@ -304,34 +317,29 @@ func _start_page_transition(page_id: StringName, push_page: bool) -> void:
_page_transition_active = true _page_transition_active = true
navigation_transition_started.emit() navigation_transition_started.emit()
var generation: int = _page_transition_generation 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: if push_page:
_page_stack.append(page_id) _page_stack.append(page_id)
else: else:
_page_stack.pop_back() _page_stack.pop_back()
for page: SettingsBubblePage in _pages.values(): for page: SettingsBubblePage in _pages.values():
if page != incoming_page: if page != outgoing_page and page != incoming_page:
page.hide_page() page.hide_page()
incoming_page.transition_in( outgoing_page.transition_out(
true, Callable()
_finish_incoming_page.bind(generation) )
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_generation += 1
_page_transition_active = true _page_transition_active = true
navigation_transition_started.emit() navigation_transition_started.emit()
closing.emit()
var generation: int = _page_transition_generation var generation: int = _page_transition_generation
active_page.transition_out( active_page.transition_out(
_finish_animated_close.bind(generation, applied_result), _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: if not is_node_ready() or _data_root == null:
return return
%DataFolderPath.text = _data_root.root_path %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 = ( %ChangeDataFolder.disabled = (
_data_root.override_active _data_root.override_active
or (_network_session != null and _network_session.is_session_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) _show_existing_data_folder_choice(path)
return return
if bool(result.get("ok", false)): 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() _refresh_data_page()
get_tree().call_deferred("quit") get_tree().call_deferred("quit")
else: else:
@ -441,16 +450,16 @@ func _change_data_folder(path: String) -> void:
func _show_existing_data_folder_choice(path: String) -> void: func _show_existing_data_folder_choice(path: String) -> void:
var dialog: ConfirmationDialog = ConfirmationDialog.new() var dialog: ConfirmationDialog = ConfirmationDialog.new()
dialog.title = "Existing NETFISHING data" dialog.title = "Existing NETfishing data"
dialog.ok_button_text = "Use Selected Data" dialog.ok_button_text = "Use Selected Data"
dialog.dialog_text = ( 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." + "Choose one complete data set. Data will not be merged."
) )
dialog.add_button("Replace Selected Data", false, "replace") dialog.add_button("Replace Selected Data", false, "replace")
dialog.confirmed.connect(func() -> void: dialog.confirmed.connect(func() -> void:
if _data_root.use_existing_root(path): 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") get_tree().call_deferred("quit")
else: else:
_feedback.text = _data_root.error_message _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.file_mode = FileDialog.FILE_MODE_SAVE_FILE
_export_file_dialog.access = FileDialog.ACCESS_FILESYSTEM _export_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_export_file_dialog.use_native_dialog = false _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) _export_file_dialog.file_selected.connect(_identity_export_file_selected)
_interface_fonts.apply_utility_theme(_export_file_dialog) _interface_fonts.apply_utility_theme(_export_file_dialog)
add_child(_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.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_backup_file_dialog.access = FileDialog.ACCESS_FILESYSTEM _backup_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_backup_file_dialog.use_native_dialog = false _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) _backup_file_dialog.file_selected.connect(_identity_import_file_selected)
_interface_fonts.apply_utility_theme(_backup_file_dialog) _interface_fonts.apply_utility_theme(_backup_file_dialog)
add_child(_backup_file_dialog) add_child(_backup_file_dialog)
@ -707,7 +716,7 @@ func _refresh_value_labels() -> void:
+ ("on" if _auto_click_enabled else "off") + ("on" if _auto_click_enabled else "off")
) )
_readable_font_toggle.text = ( _readable_font_toggle.text = (
"readable\ninterface font\n" "alternate\nfont\n"
+ ( + (
"on" "on"
if settings.use_readable_interface_font 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 _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: func _set_world_pixelation(pixel_size: int) -> void:
if _settings_manager == null: if _settings_manager == null:
return return

View file

@ -553,8 +553,8 @@ unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0) custom_minimum_size = Vector2(0, 0)
mouse_filter = 2 mouse_filter = 2
focus_mode = 0 focus_mode = 0
text = "lower intervals\nclick barriers\nfaster\nwhile held" text = "Focus a control\nfor a helpful\npreview."
neutral_size = Vector2(170, 160) neutral_size = Vector2(210, 176)
desktop_anchor = Vector2(315, 405) desktop_anchor = Vector2(315, 405)
compact_anchor = Vector2(280, 370) compact_anchor = Vector2(280, 370)
compact_minimum_size = Vector2(160, 152) compact_minimum_size = Vector2(160, 152)
@ -566,8 +566,8 @@ motion_phase = 4.1
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0) custom_minimum_size = Vector2(0, 0)
toggle_mode = true toggle_mode = true
text = "readable\ninterface font\noff" text = "alternate\nfont\noff"
neutral_size = Vector2(180, 132) neutral_size = Vector2(150, 150)
desktop_anchor = Vector2(92, 405) desktop_anchor = Vector2(92, 405)
compact_anchor = Vector2(102, 374) compact_anchor = Vector2(102, 374)
compact_minimum_size = Vector2(156, 118) compact_minimum_size = Vector2(156, 118)
@ -576,6 +576,7 @@ maximum_font_size = 21
motion_phase = 4.6 motion_phase = 4.6
[node name="ReadableFontSample" type="Label" parent="AccessibilityPage/BubbleCluster"] [node name="ReadableFontSample" type="Label" parent="AccessibilityPage/BubbleCluster"]
visible = false
layout_mode = 0 layout_mode = 0
offset_left = 250.0 offset_left = 250.0
offset_top = 20.0 offset_top = 20.0
@ -588,6 +589,7 @@ vertical_alignment = 1
autowrap_mode = 2 autowrap_mode = 2
[node name="ReadableFontHelper" type="Label" parent="AccessibilityPage/BubbleCluster"] [node name="ReadableFontHelper" type="Label" parent="AccessibilityPage/BubbleCluster"]
visible = false
layout_mode = 0 layout_mode = 0
offset_left = 250.0 offset_left = 250.0
offset_top = 82.0 offset_top = 82.0
@ -647,6 +649,7 @@ offset_right = 330.0
offset_bottom = 210.0 offset_bottom = 210.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
z_index = 1
[node name="Content" type="VBoxContainer" parent="DataPage/Paper"] [node name="Content" type="VBoxContainer" parent="DataPage/Paper"]
layout_mode = 2 layout_mode = 2
@ -655,12 +658,12 @@ theme_override_constants/separation = 10
[node name="Heading" type="Label" parent="DataPage/Paper/Content"] [node name="Heading" type="Label" parent="DataPage/Paper/Content"]
layout_mode = 2 layout_mode = 2
theme_override_font_sizes/font_size = 28 theme_override_font_sizes/font_size = 28
text = "data & identity" text = "Data & Identity"
horizontal_alignment = 1 horizontal_alignment = 1
[node name="DataFolderLabel" type="Label" parent="DataPage/Paper/Content"] [node name="DataFolderLabel" type="Label" parent="DataPage/Paper/Content"]
layout_mode = 2 layout_mode = 2
text = "Data Folder" text = "Data Folder:"
[node name="DataFolderPath" type="Label" parent="DataPage/Paper/Content"] [node name="DataFolderPath" type="Label" parent="DataPage/Paper/Content"]
unique_name_in_owner = true unique_name_in_owner = true
@ -671,7 +674,7 @@ autowrap_mode = 2
[node name="DataStorageMode" type="Label" parent="DataPage/Paper/Content"] [node name="DataStorageMode" type="Label" parent="DataPage/Paper/Content"]
unique_name_in_owner = true unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "storage mode: unavailable" text = "Storage Mode: unavailable"
[node name="OpenDataFolder" type="Button" parent="DataPage/Paper/Content"] [node name="OpenDataFolder" type="Button" parent="DataPage/Paper/Content"]
unique_name_in_owner = true unique_name_in_owner = true
@ -714,11 +717,12 @@ text = "Import Host Identity"
[node name="DataBackButton" parent="DataPage/BubbleCluster" instance=ExtResource("4_bubble")] [node name="DataBackButton" parent="DataPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true unique_name_in_owner = true
z_index = 4
layout_mode = 0 layout_mode = 0
text = "back" text = "back"
neutral_size = Vector2(96, 90) neutral_size = Vector2(96, 90)
desktop_anchor = Vector2(135, 470) desktop_anchor = Vector2(-25, 470)
compact_anchor = Vector2(115, 420) compact_anchor = Vector2(-20, 420)
compact_minimum_size = Vector2(82, 78) compact_minimum_size = Vector2(82, 78)
minimum_font_size = 13 minimum_font_size = 13
maximum_font_size = 16 maximum_font_size = 16

View file

@ -102,7 +102,7 @@ func set_stage_rect(stage_rect: Rect2) -> void:
func transition_in( func transition_in(
duration: float, _duration: float,
completed: Callable, completed: Callable,
) -> void: ) -> void:
_transition_generation += 1 _transition_generation += 1
@ -115,6 +115,9 @@ func transition_in(
_resting_cluster_position.x, _resting_cluster_position.x,
size.y + transition_safe_margin size.y + transition_safe_margin
) )
var duration := UIMotion.bubble_duration(
_cluster.position.y - _resting_cluster_position.y
)
var generation: int = _transition_generation var generation: int = _transition_generation
_transition = create_tween() _transition = create_tween()
_transition.tween_property( _transition.tween_property(
@ -130,7 +133,7 @@ func transition_in(
func transition_out( func transition_out(
duration: float, _duration: float,
completed: Callable, completed: Callable,
) -> void: ) -> void:
if not visible or _is_transitioning: if not visible or _is_transitioning:
@ -139,12 +142,16 @@ func transition_out(
_cancel_transition() _cancel_transition()
_is_transitioning = true _is_transitioning = true
_set_interactive(false) _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 var generation: int = _transition_generation
_transition = create_tween() _transition = create_tween()
_transition.tween_property( _transition.tween_property(
_cluster, _cluster,
"position:y", "position:y",
-_cluster.size.y - transition_safe_margin, target_y,
duration duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_transition.finished.connect( _transition.finished.connect(

View file

@ -83,8 +83,6 @@ const INTRO_PROMPT_FADE_DURATION: float = 0.55
const INTRO_BUBBLE_TRAVEL_DURATION: float = 2.40 const INTRO_BUBBLE_TRAVEL_DURATION: float = 2.40
const INTRO_BRANDING_TRAVEL_DURATION: float = 2.0 const INTRO_BRANDING_TRAVEL_DURATION: float = 2.0
const INTRO_BRANDING_START_DELAY: float = 0.35 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 const HOST_CLUSTER_SAFE_MARGIN: float = 24.0
signal new_game_requested signal new_game_requested
@ -203,6 +201,7 @@ func _ready() -> void:
_confirmation_page.cancelled.connect(_request_close_confirmation) _confirmation_page.cancelled.connect(_request_close_confirmation)
_settings_panel.applied.connect(_on_settings_applied) _settings_panel.applied.connect(_on_settings_applied)
_settings_panel.closed.connect(_on_settings_closed) _settings_panel.closed.connect(_on_settings_closed)
_settings_panel.closing.connect(_on_settings_closing)
_settings_panel.opened.connect(_on_settings_opened) _settings_panel.opened.connect(_on_settings_opened)
_settings_panel.navigation_transition_started.connect( _settings_panel.navigation_transition_started.connect(
_emit_navigation_bubble_flurry _emit_navigation_bubble_flurry
@ -1032,7 +1031,7 @@ func _begin_confirmation_open() -> void:
_presentation_center, _presentation_center,
"position:y", "position:y",
_confirmation_title_content_rest_position.y - exit_distance, _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) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_confirmation_transition.finished.connect( _confirmation_transition.finished.connect(
_finish_confirmation_title_exit.bind(generation), _finish_confirmation_title_exit.bind(generation),
@ -1050,7 +1049,7 @@ func _finish_confirmation_title_exit(generation: int) -> void:
_presentation_center.hide() _presentation_center.hide()
_presentation_center.position = _confirmation_title_content_rest_position _presentation_center.position = _confirmation_title_content_rest_position
_confirmation_page.transition_in( _confirmation_page.transition_in(
HOST_PRESENTATION_IN_DURATION, 0.0,
_finish_confirmation_open.bind(generation) _finish_confirmation_open.bind(generation)
) )
@ -1074,7 +1073,7 @@ func _begin_confirmation_return() -> void:
_confirmation_page.lock_interaction() _confirmation_page.lock_interaction()
var generation: int = _confirmation_transition_generation var generation: int = _confirmation_transition_generation
_confirmation_page.transition_out( _confirmation_page.transition_out(
HOST_PRESENTATION_OUT_DURATION, 0.0,
_finish_confirmation_page_exit.bind(generation) _finish_confirmation_page_exit.bind(generation)
) )
@ -1092,12 +1091,16 @@ func _finish_confirmation_page_exit(generation: int) -> void:
+ HOST_CLUSTER_SAFE_MARGIN + HOST_CLUSTER_SAFE_MARGIN
) )
_presentation_center.show() _presentation_center.show()
var entry_distance: float = absf(
_presentation_center.position.y
- _confirmation_title_content_rest_position.y
)
_confirmation_transition = create_tween() _confirmation_transition = create_tween()
_confirmation_transition.tween_property( _confirmation_transition.tween_property(
_presentation_center, _presentation_center,
"position", "position",
_confirmation_title_content_rest_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) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_confirmation_transition.finished.connect( _confirmation_transition.finished.connect(
_finish_confirmation_return.bind(generation), _finish_confirmation_return.bind(generation),
@ -1177,15 +1180,23 @@ func _close_settings() -> void:
func _on_settings_applied() -> void: func _on_settings_applied() -> void:
_begin_title_cluster_return("settings saved.") _feedback_text_before_settings = "settings saved."
func _on_settings_closed() -> void: 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: func _on_settings_opened() -> void:
_title_settings_transition_active = false pass
func _begin_title_cluster_exit() -> void: func _begin_title_cluster_exit() -> void:
@ -1207,12 +1218,23 @@ func _begin_title_cluster_exit() -> void:
_presentation_center, _presentation_center,
"position:y", "position:y",
_title_content_rest_position.y - exit_distance, _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) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_title_settings_transition.finished.connect( _title_settings_transition.finished.connect(
_finish_title_cluster_exit.bind(generation), _finish_title_cluster_exit.bind(generation),
CONNECT_ONE_SHOT 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: func _finish_title_cluster_exit(generation: int) -> void:
@ -1222,12 +1244,9 @@ func _finish_title_cluster_exit(generation: int) -> void:
): ):
return return
_title_settings_transition = null _title_settings_transition = null
_title_settings_transition_active = false
_presentation_center.hide() _presentation_center.hide()
_presentation_center.position = _title_content_rest_position _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: 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 _title_presentation_scale_root.size.y + HOST_CLUSTER_SAFE_MARGIN
) )
_presentation_center.show() _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 = create_tween()
_title_settings_transition.tween_property( _title_settings_transition.tween_property(
_presentation_center, _presentation_center,
"position", "position",
_title_content_rest_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) ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_title_settings_transition.finished.connect( _title_settings_transition.finished.connect(
_finish_title_cluster_return.bind(generation), _finish_title_cluster_return.bind(generation),

View file

@ -33,6 +33,7 @@ script = ExtResource("1_script")
[node name="Background" type="ColorRect" parent="."] [node name="Background" type="ColorRect" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
visible = false
material = SubResource("ShaderMaterial_title_water") material = SubResource("ShaderMaterial_title_water")
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15

12
ui/ui_motion.gd Normal file
View file

@ -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)

1
ui/ui_motion.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://biwyjd2usqsm6

150
ui/utility_page_style.gd Normal file
View file

@ -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)

View file

@ -0,0 +1 @@
uid://bmfqwan211x0j