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/product_version="0.2.0.2"
application/company_name=""
application/product_name="NETFISHING"
application/file_description="NETFISHING Pre-Alpha Playtest 0.2"
application/product_name="NETfishing"
application/file_description="NETfishing Pre-Alpha Playtest 0.2"
application/copyright=""
application/trademarks=""
application/export_angle=0

View file

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

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://player/player.tscn" id="2_player"]
@ -39,6 +39,10 @@
[ext_resource type="Script" path="res://network/player_data_root.gd" id="37_data_root"]
[ext_resource type="Script" path="res://network/identity_backup_service.gd" id="38_identity_backup"]
[ext_resource type="Script" path="res://ui/interface_font_controller.gd" id="39_fonts"]
[ext_resource type="Shader" path="res://ui/title_water_background.gdshader" id="40_title_water"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"]
shader = ExtResource("40_title_water")
[node name="Main" type="Node3D"]
script = ExtResource("3_main")
@ -47,6 +51,20 @@ pelican_buyer_profile = ExtResource("7_pelicans")
main_shop_buyer_profile = ExtResource("12_main_shop")
item_catalog = ExtResource("11_items")
[node name="TitleBackgroundLayer" type="CanvasLayer" parent="."]
unique_name_in_owner = true
layer = -1
[node name="TitleBackground" type="ColorRect" parent="TitleBackgroundLayer"]
unique_name_in_owner = true
material = SubResource("ShaderMaterial_title_water_native")
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="InterfaceFontController" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("39_fonts")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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
Version 0.2.0-prealpha.2
Thank you for trying this early private playtest.
NETFISHING is currently a local gameplay prototype for a future
NETfishing is currently a local gameplay prototype for a future
multiplayer-first game. Networking and multiplayer are NOT implemented in
this build.

View file

@ -96,6 +96,9 @@ func _unhandled_input(event: InputEvent) -> void:
):
open_chat()
get_viewport().set_input_as_handled()
elif event.keycode == KEY_T and not _opened and _available:
open_chat()
get_viewport().set_input_as_handled()
elif event.keycode == KEY_ESCAPE and _opened:
close_chat()
get_viewport().set_input_as_handled()

View file

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

View file

@ -49,6 +49,7 @@ var _current_mail_id := ""
func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
UtilityPageStyle.apply_page(self)
_build_ui()
@ -75,6 +76,7 @@ func setup(
func activate() -> void:
_show_inbox()
UtilityPageStyle.animate_in(self)
call_deferred("_focus_first")
@ -103,14 +105,7 @@ func _build_ui() -> void:
var paper := PanelContainer.new()
paper.position = Vector2(116, 126)
paper.size = Vector2(1048, 470)
var style := StyleBoxFlat.new()
style.bg_color = Color("f3ecd7")
style.border_color = Color("665a46")
style.set_border_width_all(4)
style.set_corner_radius_all(18)
style.shadow_color = Color(0.05, 0.04, 0.03, 0.42)
style.shadow_size = 7
paper.add_theme_stylebox_override("panel", style)
paper.add_theme_stylebox_override("panel", UtilityPageStyle.panel_style())
add_child(paper)
var margin := MarginContainer.new()
margin.add_theme_constant_override("margin_left", 34)
@ -132,13 +127,14 @@ func _build_ui() -> void:
_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_status.add_theme_color_override("font_color", Color("4a3f31"))
root.add_child(_status)
_style_controls(root)
_show_inbox()
func _build_inbox() -> Control:
var page := Control.new()
var title := Label.new()
title.text = "session mail"
title.text = "Mail"
title.position = Vector2(12, 0)
title.size = Vector2(500, 42)
title.add_theme_font_size_override("font_size", 30)
@ -367,6 +363,7 @@ func _refresh_inbox() -> void:
button.custom_minimum_size = Vector2(930, 54)
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.pressed.connect(_open_letter.bind(str(letter["mail_id"])))
UtilityPageStyle.apply_button(button)
_inbox_list.add_child(button)
@ -601,3 +598,24 @@ func _focus_first() -> void:
var buttons := _inbox_list.find_children("*", "Button", true, false)
if not buttons.is_empty():
(buttons.front() as Button).grab_focus()
func _style_controls(root: Node) -> void:
for node: Node in root.find_children("*", "", true, false):
if node is BaseButton:
UtilityPageStyle.apply_button(node)
elif node is LineEdit:
UtilityPageStyle.apply_line_edit(node)
elif node is Label:
node.add_theme_color_override(
"font_color", UtilityPageStyle.INK
)
elif node is TextEdit:
node.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
node.add_theme_color_override(
"font_color", UtilityPageStyle.LIGHT_TEXT
)
node.add_theme_color_override(
"font_placeholder_color",
Color(0.82, 0.80, 0.71, 0.84)
)

View file

@ -26,17 +26,17 @@ const DIRECT_WORKFLOW_HELP: String = (
@onready var _name_helper: Label = %NameHelper
@onready var _server_list: ItemList = %ServerList
@onready var _details: Label = %Details
@onready var _direct_button: BubbleButton = %DirectButton
@onready var _saved_button: BubbleButton = %SavedButton
@onready var _recent_button: BubbleButton = %RecentButton
@onready var _join_button: BubbleButton = %JoinButton
@onready var _save_button: BubbleButton = %SaveButton
@onready var _edit_button: BubbleButton = %EditButton
@onready var _favorite_button: BubbleButton = %FavoriteButton
@onready var _delete_button: BubbleButton = %DeleteButton
@onready var _cancel_button: BubbleButton = %CancelButton
@onready var _back_button: BubbleButton = %BackButton
@onready var _open_close_button: BubbleButton = %OpenCloseButton
@onready var _direct_button: Button = %DirectButton
@onready var _saved_button: Button = %SavedButton
@onready var _recent_button: Button = %RecentButton
@onready var _join_button: Button = %JoinButton
@onready var _save_button: Button = %SaveButton
@onready var _edit_button: Button = %EditButton
@onready var _favorite_button: Button = %FavoriteButton
@onready var _delete_button: Button = %DeleteButton
@onready var _cancel_button: Button = %CancelButton
@onready var _back_button: Button = %BackButton
@onready var _open_close_button: Button = %OpenCloseButton
@onready var _status: Label = %Status
@onready var _session_summary: Label = %SessionSummary
@onready var _delete_confirmation: BubbleConfirmationPage = (
@ -56,6 +56,19 @@ var _delete_armed: bool = false
func _ready() -> void:
UtilityPageStyle.apply_page(self)
var paper := get_node("Paper") as PanelContainer
paper.add_theme_stylebox_override(
"panel", UtilityPageStyle.panel_style()
)
for button: BaseButton in [
_direct_button, _saved_button, _recent_button, _join_button,
_save_button, _edit_button, _favorite_button, _delete_button,
_cancel_button, _back_button, _open_close_button,
]:
UtilityPageStyle.apply_button(button)
UtilityPageStyle.apply_line_edit(_address)
UtilityPageStyle.apply_line_edit(_name_edit)
_direct_button.pressed.connect(_set_mode.bind(Mode.DIRECT))
_saved_button.pressed.connect(_set_mode.bind(Mode.SAVED))
_recent_button.pressed.connect(_set_mode.bind(Mode.RECENT))
@ -126,6 +139,7 @@ func open_page(preserved_endpoint: String = "") -> void:
elif _address.text.is_empty():
_address.text = "127.0.0.1:7777"
show()
UtilityPageStyle.animate_in(self)
_set_mode(_mode)
if _mode == Mode.DIRECT:
_address.grab_focus()

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="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_button.tscn" id="3_bubble"]
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="4_confirmation"]
[sub_resource type="StyleBoxFlat" id="StyleBox_paper"]
@ -128,41 +127,23 @@ layout_mode = 2
theme_override_constants/separation = 14
alignment = 1
[node name="DirectButton" parent="Paper/Margin/Layout/Modes" instance=ExtResource("3_bubble")]
[node name="DirectButton" type="Button" parent="Paper/Margin/Layout/Modes"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 72)
layout_mode = 2
text = "direct"
neutral_size = Vector2(100, 72)
minimum_font_size = 16
maximum_font_size = 19
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="SavedButton" parent="Paper/Margin/Layout/Modes" instance=ExtResource("3_bubble")]
[node name="SavedButton" type="Button" parent="Paper/Margin/Layout/Modes"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 72)
layout_mode = 2
text = "saved"
neutral_size = Vector2(100, 72)
minimum_font_size = 16
maximum_font_size = 19
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="RecentButton" parent="Paper/Margin/Layout/Modes" instance=ExtResource("3_bubble")]
[node name="RecentButton" type="Button" parent="Paper/Margin/Layout/Modes"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 72)
layout_mode = 2
text = "recent"
neutral_size = Vector2(100, 72)
minimum_font_size = 16
maximum_font_size = 19
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="AddressLabel" type="Label" parent="Paper/Margin/Layout"]
unique_name_in_owner = true
@ -290,106 +271,58 @@ layout_mode = 2
theme_override_constants/separation = 7
alignment = 1
[node name="JoinButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="JoinButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
custom_minimum_size = Vector2(82, 72)
layout_mode = 2
text = "join"
neutral_size = Vector2(82, 72)
minimum_font_size = 15
maximum_font_size = 18
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="SaveButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="SaveButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
custom_minimum_size = Vector2(82, 72)
layout_mode = 2
text = "save\nserver"
neutral_size = Vector2(82, 72)
minimum_font_size = 14
maximum_font_size = 17
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="EditButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="EditButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(76, 68)
layout_mode = 2
text = "edit"
neutral_size = Vector2(76, 68)
minimum_font_size = 14
maximum_font_size = 17
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="FavoriteButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="FavoriteButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(88, 68)
layout_mode = 2
text = "favorite"
neutral_size = Vector2(88, 68)
minimum_font_size = 13
maximum_font_size = 16
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="DeleteButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="DeleteButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(82, 68)
layout_mode = 2
text = "delete"
neutral_size = Vector2(82, 68)
minimum_font_size = 13
maximum_font_size = 16
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="OpenCloseButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="OpenCloseButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(88, 72)
layout_mode = 2
text = "open\ngame"
neutral_size = Vector2(88, 72)
minimum_font_size = 14
maximum_font_size = 17
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="CancelButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="CancelButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(76, 68)
layout_mode = 2
text = "cancel"
neutral_size = Vector2(76, 68)
minimum_font_size = 14
maximum_font_size = 17
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="BackButton" parent="Paper/Margin/Layout/Actions" instance=ExtResource("3_bubble")]
[node name="BackButton" type="Button" parent="Paper/Margin/Layout/Actions"]
unique_name_in_owner = true
custom_minimum_size = Vector2(76, 68)
layout_mode = 2
text = "back"
neutral_size = Vector2(76, 68)
minimum_font_size = 14
maximum_font_size = 17
horizontal_amplitude = 0.0
vertical_amplitude = 0.0
deformation_amplitude = 0.0
[node name="DeleteConfirmation" parent="." instance=ExtResource("4_confirmation")]
unique_name_in_owner = true

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

View file

@ -77,16 +77,14 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("4_page")
page_id = &"pause"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/ChatButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/ChatButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")])
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), NodePath("BubbleCluster/JoinGameButton"), NodePath("BubbleCluster/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")])
initial_focus_path = NodePath("BubbleCluster/ResumeButton")
back_focus_path = NodePath("BubbleCluster/ResumeButton")
maximum_layout_size = Vector2(720, 520)
compact_maximum_layout_size = Vector2(544, 400)
compact_width_threshold = 680.0
compact_height_threshold = 500.0
outgoing_rise_duration = 1.7
incoming_rise_duration = 1.85
[node name="BubbleCluster" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage"]
layout_mode = 0
@ -135,18 +133,6 @@ minimum_font_size = 16
maximum_font_size = 23
motion_phase = 2.05
[node name="ChatButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "chat"
neutral_size = Vector2(112, 106)
desktop_anchor = Vector2(620, 255)
compact_anchor = Vector2(595, 270)
compact_minimum_size = Vector2(92, 88)
minimum_font_size = 15
maximum_font_size = 20
motion_phase = 2.3
[node name="JoinGameButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)

View file

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

View file

@ -448,9 +448,9 @@ mouse_filter = 1
[node name="BagOuterWall" type="PanelContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
unique_name_in_owner = true
layout_mode = 0
offset_left = 72.0
offset_left = 122.0
offset_top = 132.0
offset_right = 968.0
offset_right = 1018.0
offset_bottom = 574.0
[node name="BagOuterMargin" type="MarginContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagOuterWall"]
@ -507,25 +507,6 @@ text = "your bag is empty"
horizontal_alignment = 1
vertical_alignment = 1
[node name="BagInstructionBubble" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage" instance=ExtResource("9_info_scene")]
unique_name_in_owner = true
layout_mode = 0
offset_left = 992.0
offset_top = 140.0
offset_right = 1232.0
offset_bottom = 238.0
mouse_filter = 2
[node name="BagInstructionText" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage/BagInstructionBubble"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 2
theme_override_colors/font_color = Color(0.22, 0.15, 0.08, 1)
theme_override_font_sizes/font_size = 18
text = "drag items\nto the hotbar"
horizontal_alignment = 1
vertical_alignment = 1
[node name="BagDetailConstellation" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/BagPage"]
unique_name_in_owner = true
visible = false

View file

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

View file

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

View file

@ -7,16 +7,25 @@ extends SubViewportContainer
@onready var _preview_root: Node3D = %PreviewRoot
var _dragging: bool = false
var _visuals: Node3D
func _ready() -> void:
focus_mode = Control.FOCUS_ALL
gui_input.connect(_on_gui_input)
_visuals = PlayerVisualPresenter.instantiate_visuals()
_preview_root.add_child(_visuals)
var rod := _visuals.find_child("FishingRod", true, false) as Node3D
if rod != null:
rod.visible = false
var catch_display := _visuals.find_child("CatchDisplay", true, false) as Node3D
if catch_display != null:
catch_display.visible = false
func apply_appearance_profile(_profile: Dictionary) -> void:
# The capsule is deliberately neutral until modular character assets exist.
pass
func apply_appearance_profile(profile: Dictionary) -> void:
if _visuals != null:
PlayerVisualPresenter.apply_appearance(_visuals, profile)
func reset_view() -> void:

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

View file

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

View file

@ -8,8 +8,6 @@ const PAGE_DISPLAY: StringName = &"display"
const PAGE_CONTROLS: StringName = &"controls"
const PAGE_ACCESSIBILITY: StringName = &"accessibility"
const PAGE_DATA: StringName = &"data"
const TITLE_HOST_OUTGOING_DURATION: float = 1.70
const TITLE_HOST_INCOMING_DURATION: float = 1.80
const PIXELATION_NAMES: PackedStringArray = [
"legible",
"cute",
@ -23,6 +21,7 @@ const SettingsManagerType = preload(
signal applied
signal closed
signal closing
signal opened
signal crisp_reset_focus_requested
signal panel_visibility_changed(is_visible: bool)
@ -151,6 +150,19 @@ func _ready() -> void:
_invert_y_toggle.pressed.connect(_toggle_invert_y)
_auto_click_toggle.pressed.connect(_toggle_auto_click)
_readable_font_toggle.pressed.connect(_toggle_readable_font)
for control: Control in [_auto_click_toggle, _auto_click_interval]:
control.mouse_entered.connect(
_show_accessibility_helper.bind(&"auto_click")
)
control.focus_entered.connect(
_show_accessibility_helper.bind(&"auto_click")
)
_readable_font_toggle.mouse_entered.connect(
_show_accessibility_helper.bind(&"alternate_font")
)
_readable_font_toggle.focus_entered.connect(
_show_accessibility_helper.bind(&"alternate_font")
)
%IntervalDecrease.pressed.connect(_adjust_auto_click_interval.bind(-1))
%IntervalIncrease.pressed.connect(_adjust_auto_click_interval.bind(1))
_auto_click_interval.pressed.connect(
@ -171,6 +183,7 @@ func _ready() -> void:
parent_control.resized.connect(_refresh_panel_size)
for page: SettingsBubblePage in _pages.values():
page.hide_page()
_style_data_page()
call_deferred("_refresh_panel_size")
@ -234,7 +247,7 @@ func open_panel(
_root_page.transition_in(
true,
_finish_animated_open.bind(generation),
TITLE_HOST_INCOMING_DURATION
0.0
)
else:
_show_active_page(true)
@ -304,34 +317,29 @@ func _start_page_transition(page_id: StringName, push_page: bool) -> void:
_page_transition_active = true
navigation_transition_started.emit()
var generation: int = _page_transition_generation
outgoing_page.transition_out(
_finish_outgoing_page.bind(
generation,
page_id,
push_page,
incoming_page
)
)
func _finish_outgoing_page(
generation: int,
page_id: StringName,
push_page: bool,
incoming_page: SettingsBubblePage,
) -> void:
if generation != _page_transition_generation or not _page_transition_active:
return
if push_page:
_page_stack.append(page_id)
else:
_page_stack.pop_back()
for page: SettingsBubblePage in _pages.values():
if page != incoming_page:
if page != outgoing_page and page != incoming_page:
page.hide_page()
outgoing_page.transition_out(
Callable()
)
get_tree().create_timer(
UIMotion.BUBBLE_TRANSITION_OVERLAP_DELAY
).timeout.connect(
func() -> void:
if (
generation == _page_transition_generation
and _page_transition_active
):
incoming_page.transition_in(
true,
_finish_incoming_page.bind(generation)
),
CONNECT_ONE_SHOT
)
@ -355,10 +363,11 @@ func _begin_animated_close(applied_result: bool) -> void:
_page_transition_generation += 1
_page_transition_active = true
navigation_transition_started.emit()
closing.emit()
var generation: int = _page_transition_generation
active_page.transition_out(
_finish_animated_close.bind(generation, applied_result),
TITLE_HOST_OUTGOING_DURATION
0.0
)
@ -393,7 +402,7 @@ func _refresh_data_page() -> void:
if not is_node_ready() or _data_root == null:
return
%DataFolderPath.text = _data_root.root_path
%DataStorageMode.text = "storage mode: " + _data_root.storage_mode_text()
%DataStorageMode.text = "Storage Mode: " + _data_root.storage_mode_text()
%ChangeDataFolder.disabled = (
_data_root.override_active
or (_network_session != null and _network_session.is_session_active())
@ -432,7 +441,7 @@ func _change_data_folder(path: String) -> void:
_show_existing_data_folder_choice(path)
return
if bool(result.get("ok", false)):
_feedback.text = "Data folder changed. NETFISHING will close safely."
_feedback.text = "Data folder changed. NETfishing will close safely."
_refresh_data_page()
get_tree().call_deferred("quit")
else:
@ -441,16 +450,16 @@ func _change_data_folder(path: String) -> void:
func _show_existing_data_folder_choice(path: String) -> void:
var dialog: ConfirmationDialog = ConfirmationDialog.new()
dialog.title = "Existing NETFISHING data"
dialog.title = "Existing NETfishing data"
dialog.ok_button_text = "Use Selected Data"
dialog.dialog_text = (
"The selected folder contains different NETFISHING data.\n\n"
"The selected folder contains different NETfishing data.\n\n"
+ "Choose one complete data set. Data will not be merged."
)
dialog.add_button("Replace Selected Data", false, "replace")
dialog.confirmed.connect(func() -> void:
if _data_root.use_existing_root(path):
_feedback.text = "Data folder changed. NETFISHING will close safely."
_feedback.text = "Data folder changed. NETfishing will close safely."
get_tree().call_deferred("quit")
else:
_feedback.text = _data_root.error_message
@ -483,7 +492,7 @@ func _choose_identity_export(identity_type: String) -> void:
_export_file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
_export_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_export_file_dialog.use_native_dialog = false
_export_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETFISHING identity backup"])
_export_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETfishing identity backup"])
_export_file_dialog.file_selected.connect(_identity_export_file_selected)
_interface_fonts.apply_utility_theme(_export_file_dialog)
add_child(_export_file_dialog)
@ -509,7 +518,7 @@ func _choose_identity_import(identity_type: String) -> void:
_backup_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_backup_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_backup_file_dialog.use_native_dialog = false
_backup_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETFISHING identity backup"])
_backup_file_dialog.filters = PackedStringArray(["*.nfidentity ; NETfishing identity backup"])
_backup_file_dialog.file_selected.connect(_identity_import_file_selected)
_interface_fonts.apply_utility_theme(_backup_file_dialog)
add_child(_backup_file_dialog)
@ -707,7 +716,7 @@ func _refresh_value_labels() -> void:
+ ("on" if _auto_click_enabled else "off")
)
_readable_font_toggle.text = (
"readable\ninterface font\n"
"alternate\nfont\n"
+ (
"on"
if settings.use_readable_interface_font
@ -725,6 +734,35 @@ func _refresh_value_labels() -> void:
_ui_options[index].button_pressed = index + 1 == settings.ui_pixel_size
func _show_accessibility_helper(kind: StringName) -> void:
var helper := %IntervalHelp as BubbleButton
if kind == &"alternate_font":
helper.text = (
"The quick brown fox\njumps over the lazy dog.\n0123456789"
)
else:
helper.text = (
"Lower intervals click\nbarriers faster\nwhile held."
)
func _style_data_page() -> void:
UtilityPageStyle.apply_page(_data_page)
var paper := _data_page.get_node("Paper") as PanelContainer
paper.add_theme_stylebox_override(
"panel", UtilityPageStyle.panel_style()
)
var content := _data_page.get_node("Paper/Content") as VBoxContainer
content.add_theme_constant_override("separation", 12)
for node: Node in content.find_children("*", "", true, false):
if node is BaseButton:
UtilityPageStyle.apply_button(node)
elif node is Label:
node.add_theme_color_override(
"font_color", UtilityPageStyle.INK
)
func _set_world_pixelation(pixel_size: int) -> void:
if _settings_manager == null:
return

View file

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

View file

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

View file

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

View file

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

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