diff --git a/ui/components/bubble_menu/bubble_transition_flurry.gd b/ui/components/bubble_menu/bubble_transition_flurry.gd new file mode 100644 index 0000000..49a2c59 --- /dev/null +++ b/ui/components/bubble_menu/bubble_transition_flurry.gd @@ -0,0 +1,195 @@ +class_name BubbleTransitionFlurry +extends Control + +const BUBBLE_TEXTURES: Array[Texture2D] = [ + preload("res://ui/assets/title/bubbles/bubble1.png"), + preload("res://ui/assets/title/bubbles/bubble2.png"), + preload("res://ui/assets/title/bubbles/bubble3.png"), +] +const BUBBLE_COUNT_MIN: int = 4 +const BUBBLE_COUNT_MAX: int = 5 +const BUBBLE_SCALE_MIN: float = 0.65 +const BUBBLE_SCALE_MAX: float = 1.15 +const BUBBLE_OPACITY_MIN: float = 0.55 +const BUBBLE_OPACITY_MAX: float = 0.90 +const BUBBLE_DRIFT_MIN: float = 10.0 +const BUBBLE_DRIFT_MAX: float = 45.0 +const BUBBLE_WOBBLE_MIN: float = 3.0 +const BUBBLE_WOBBLE_MAX: float = 9.0 +const BUBBLE_TRAVEL_DURATION_MIN: float = 7.0 +const BUBBLE_TRAVEL_DURATION_MAX: float = 14.0 +const BUBBLE_EDGE_MARGIN: float = 16.0 +const BURST_X_MIN: float = 0.36 +const BURST_X_MAX: float = 0.64 +const BURST_Y_MIN: float = 0.60 +const BURST_Y_MAX: float = 0.78 +const MAX_ACTIVE_BUBBLES: int = 30 + +var _rng := RandomNumberGenerator.new() +var _generation: int = 0 +var _bubbles: Array[TextureRect] = [] +var _tweens: Dictionary[int, Tween] = {} + + +func _ready() -> void: + _rng.randomize() + + +func emit_flurry() -> void: + if not visible or BUBBLE_TEXTURES.is_empty(): + return + var bubble_count: int = _rng.randi_range( + BUBBLE_COUNT_MIN, + BUBBLE_COUNT_MAX + ) + for _bubble_index: int in bubble_count: + if not _spawn_bubble( + _rng.randf_range(BURST_X_MIN, BURST_X_MAX), + _rng.randf_range(BURST_Y_MIN, BURST_Y_MAX) + ): + break + + +func clear_flurries() -> void: + _generation += 1 + for tween: Tween in _tweens.values(): + if tween != null and tween.is_valid(): + tween.kill() + _tweens.clear() + for bubble: TextureRect in _bubbles: + if is_instance_valid(bubble): + bubble.queue_free() + _bubbles.clear() + + +func get_active_bubble_count() -> int: + return _bubbles.size() + + +func _spawn_bubble(normalized_x: float, start_y_ratio: float) -> bool: + if _bubbles.size() >= MAX_ACTIVE_BUBBLES: + return false + var layer_size: Vector2 = size + if layer_size.x <= 1.0 or layer_size.y <= 1.0: + return false + var texture: Texture2D = BUBBLE_TEXTURES[ + _rng.randi_range(0, BUBBLE_TEXTURES.size() - 1) + ] + var presentation_size: Vector2 = texture.get_size() * _rng.randf_range( + BUBBLE_SCALE_MIN, + BUBBLE_SCALE_MAX + ) + var bubble := TextureRect.new() + bubble.name = "TransitionBubble" + bubble.texture = texture + bubble.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + bubble.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + bubble.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE + bubble.size = presentation_size + bubble.modulate.a = _rng.randf_range( + BUBBLE_OPACITY_MIN, + BUBBLE_OPACITY_MAX + ) + add_child(bubble) + _bubbles.append(bubble) + + var drift_direction: float = ( + 1.0 if _rng.randi_range(0, 1) == 1 else -1.0 + ) + var horizontal_drift: float = _rng.randf_range( + BUBBLE_DRIFT_MIN, + BUBBLE_DRIFT_MAX + ) * drift_direction + var wobble_amplitude: float = _rng.randf_range( + BUBBLE_WOBBLE_MIN, + BUBBLE_WOBBLE_MAX + ) + var wobble_cycles: float = _rng.randf_range(0.8, 1.6) + var wobble_phase: float = _rng.randf_range(0.0, TAU) + var full_start_y: float = ( + layer_size.y + presentation_size.y + BUBBLE_EDGE_MARGIN + ) + var start_y: float = ( + start_y_ratio * layer_size.y - presentation_size.y * 0.5 + ) + var end_y: float = -presentation_size.y - BUBBLE_EDGE_MARGIN + var full_distance: float = maxf(full_start_y - end_y, 1.0) + var remaining_distance: float = maxf(start_y - end_y, 1.0) + var travel_duration: float = _rng.randf_range( + BUBBLE_TRAVEL_DURATION_MIN, + BUBBLE_TRAVEL_DURATION_MAX + ) * remaining_distance / full_distance + var generation: int = _generation + _update_bubble( + 0.0, + bubble, + normalized_x, + start_y_ratio, + horizontal_drift, + wobble_amplitude, + wobble_cycles, + wobble_phase, + generation + ) + var tween: Tween = create_tween() + _tweens[bubble.get_instance_id()] = tween + tween.tween_method( + _update_bubble.bind( + bubble, + normalized_x, + start_y_ratio, + horizontal_drift, + wobble_amplitude, + wobble_cycles, + wobble_phase, + generation + ), + 0.0, + 1.0, + travel_duration + ) + tween.finished.connect( + _finish_bubble.bind(bubble, generation), + CONNECT_ONE_SHOT + ) + return true + + +func _update_bubble( + progress: float, + bubble: TextureRect, + normalized_x: float, + start_y_ratio: float, + horizontal_drift: float, + wobble_amplitude: float, + wobble_cycles: float, + wobble_phase: float, + generation: int, +) -> void: + if generation != _generation or not is_instance_valid(bubble): + return + var start_y: float = start_y_ratio * size.y - bubble.size.y * 0.5 + var end_y: float = -bubble.size.y - BUBBLE_EDGE_MARGIN + var wobble: float = sin( + wobble_phase + progress * TAU * wobble_cycles + ) * wobble_amplitude + bubble.position = Vector2( + normalized_x * size.x + + horizontal_drift * progress + + wobble + - bubble.size.x * 0.5, + lerpf(start_y, end_y, progress) + ) + + +func _finish_bubble(bubble: TextureRect, generation: int) -> void: + if generation != _generation or not is_instance_valid(bubble): + return + _tweens.erase(bubble.get_instance_id()) + _bubbles.erase(bubble) + bubble.queue_free() + + +func _exit_tree() -> void: + clear_flurries() diff --git a/ui/components/bubble_menu/bubble_transition_flurry.gd.uid b/ui/components/bubble_menu/bubble_transition_flurry.gd.uid new file mode 100644 index 0000000..1c56e59 --- /dev/null +++ b/ui/components/bubble_menu/bubble_transition_flurry.gd.uid @@ -0,0 +1 @@ +uid://bxqtrj8vujks3 diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 2a71c0a..d63f940 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -56,7 +56,7 @@ signal interactive_pointer_ui_changed(is_open: bool) $UIRoot/TitleScreen/ResponsiveTitleStage/TitlePresentationScaleRoot/SettingsPanel ) @onready var _pause_settings_panel: SettingsPanelType = ( - $UIRoot/PauseMenu/SettingsCenter/SettingsPanel + $UIRoot/PauseMenu/ResponsivePauseStage/PausePresentationScaleRoot/SettingsCenter/SettingsPanel ) var _showcase_active: bool = false diff --git a/ui/pause_menu.gd b/ui/pause_menu.gd index 57e8d8b..9d569af 100644 --- a/ui/pause_menu.gd +++ b/ui/pause_menu.gd @@ -2,6 +2,13 @@ class_name PauseMenu extends Control 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") const SaveManagerType = preload("res://save/player_save_manager.gd") const SettingsManagerType = preload( @@ -32,15 +39,20 @@ enum CloseReason { TEARDOWN, } -@onready var _root_panel: PanelContainer = %RootPanel +@onready var _presentation_scale_root: Control = %PausePresentationScaleRoot +@onready var _root_page: SettingsBubblePage = %RootPage @onready var _settings_panel: SettingsPanelType = %SettingsPanel +@onready var _dim_background: ColorRect = %DimBackground +@onready var _transition_flurry: BubbleTransitionFlurry = ( + %TransitionBubbleLayer +) @onready var _confirmation_panel: PanelContainer = %ConfirmationPanel @onready var _confirmation_title: Label = %ConfirmationTitle @onready var _confirmation_text: Label = %ConfirmationText @onready var _confirm_button: Button = %ConfirmButton @onready var _cancel_button: Button = %CancelConfirmButton @onready var _feedback: Label = %FeedbackLabel -@onready var _save_button: Button = %SaveButton +@onready var _save_button: BubbleButton = %SaveButton var _player: PlayerType var _save_manager: SaveManagerType @@ -53,6 +65,14 @@ var _control_snapshot_stored: bool = false var _mouse_snapshot_stored: bool = false var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE var _action_in_progress: bool = false +var _root_transition_active: bool = false +var _root_transition_generation: int = 0 +var _closing_menu: bool = false +var _backdrop_fade: Tween +var _backdrop_fade_generation: int = 0 +var _confirmation_fade: Tween +var _confirmation_fade_generation: int = 0 +var _confirmation_fade_active: bool = false func _ready() -> void: @@ -66,6 +86,14 @@ func _ready() -> void: _cancel_button.pressed.connect(_close_confirmation) _settings_panel.applied.connect(_on_settings_applied) _settings_panel.closed.connect(_on_settings_closed) + _settings_panel.navigation_transition_started.connect( + _emit_transition_flurry + ) + _dim_background.color.a = 0.0 + _confirmation_panel.modulate.a = 0.0 + _set_confirmation_interactive(false) + resized.connect(_update_responsive_pause_stage) + call_deferred("_update_responsive_pause_stage") func setup( @@ -95,19 +123,29 @@ func open_menu() -> void: _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _feedback.text = "" - _root_panel.show() _settings_panel.hide() _confirmation_panel.hide() _confirmation_action = ConfirmationAction.NONE + _action_in_progress = false + _root_page.hide_page() show() - %ResumeButton.grab_focus() + _fade_backdrop(true) + _update_responsive_pause_stage() + _begin_root_entry(false) menu_visibility_changed.emit(true) func resume() -> void: - if not visible or _action_in_progress: + if ( + not visible + or _action_in_progress + or _root_transition_active + or _settings_panel.visible + or _confirmation_action != ConfirmationAction.NONE + ): return - close_menu(CloseReason.USER_RETURN) + _fade_backdrop(false) + _begin_root_exit(_finish_user_close) func close_for_title_transition() -> void: @@ -124,28 +162,14 @@ func close_menu( ) -> void: if not visible: return - if _settings_panel.visible: - _settings_panel.close_panel() - hide() - _root_panel.show() - _settings_panel.hide() - _confirmation_panel.hide() - _confirmation_action = ConfirmationAction.NONE - _action_in_progress = false - if _fishing_spot != null and is_instance_valid(_fishing_spot): - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) - get_viewport().gui_release_focus() - if restore_controls: - _restore_controls() - else: - _control_snapshot_stored = false - _apply_mouse_close_policy(reason) - menu_visibility_changed.emit(false) + _finish_close(reason, restore_controls) func handle_escape() -> bool: if not visible: return false + if _root_transition_active or _confirmation_fade_active: + return true if _confirmation_panel.visible: _close_confirmation() elif _settings_panel.visible: @@ -156,7 +180,7 @@ func handle_escape() -> bool: func _save_now() -> void: - if _action_in_progress: + if _action_in_progress or _root_transition_active: return _action_in_progress = true _save_button.disabled = true @@ -173,24 +197,35 @@ func _reenable_save_button() -> void: func _open_settings() -> void: - if _action_in_progress or _confirmation_panel.visible: + if ( + _action_in_progress + or _root_transition_active + or _confirmation_action != ConfirmationAction.NONE + ): return - _root_panel.hide() + _begin_root_exit(_finish_open_settings) + + +func _finish_open_settings() -> void: _settings_panel.open_panel( _settings_manager, - SettingsPanelType.PresentationMode.GAMEPLAY_MODAL + SettingsPanelType.PresentationMode.GAMEPLAY_MODAL, + true, + true ) func _on_settings_applied() -> void: - _root_panel.show() + if _closing_menu: + return _feedback.text = "settings saved." - %SettingsButton.grab_focus() + _begin_root_entry(true) func _on_settings_closed() -> void: - _root_panel.show() - %SettingsButton.grab_focus() + if _closing_menu: + return + _begin_root_entry(true) func _confirm_return_to_title() -> void: @@ -218,7 +253,7 @@ func _confirm_reset_progress() -> void: func _request_quit() -> void: - if _action_in_progress: + if _action_in_progress or _root_transition_active: return _action_in_progress = true var settings_saved: bool = _settings_manager.save_if_dirty() @@ -243,7 +278,7 @@ func _open_confirmation( confirm_text: String, dangerous: bool, ) -> void: - if _action_in_progress: + if _action_in_progress or _root_transition_active: return _confirmation_action = action _confirmation_title.text = title @@ -252,8 +287,18 @@ func _open_confirmation( _confirm_button.theme_type_variation = ( &"DangerButton" if dangerous else StringName() ) - _root_panel.hide() - _confirmation_panel.show() + _begin_root_exit(_show_confirmation.bind(dangerous)) + + +func _show_confirmation(dangerous: bool) -> void: + _fade_confirmation( + true, + _finish_confirmation_open.bind(dangerous) + ) + + +func _finish_confirmation_open(dangerous: bool) -> void: + _set_confirmation_interactive(true) if dangerous: _cancel_button.grab_focus() else: @@ -261,41 +306,281 @@ func _open_confirmation( func _close_confirmation() -> void: + if _root_transition_active or _confirmation_fade_active: + return _confirmation_action = ConfirmationAction.NONE - _confirmation_panel.hide() - _root_panel.show() - %ResumeButton.grab_focus() + _fade_confirmation(false, _finish_confirmation_cancel) + + +func _finish_confirmation_cancel() -> void: + _begin_root_entry(false) func _accept_confirmation() -> void: - if _action_in_progress: + if ( + _action_in_progress + or _root_transition_active + or _confirmation_fade_active + ): return var action: ConfirmationAction = _confirmation_action _confirmation_action = ConfirmationAction.NONE - _confirmation_panel.hide() _action_in_progress = true + _fade_confirmation( + false, + _finish_confirmation_accept.bind(action) + ) + + +func _finish_confirmation_accept(action: ConfirmationAction) -> void: match action: ConfirmationAction.RETURN_TO_TITLE: if _save_manager.save_now(): return_to_title_requested.emit() else: _feedback.text = "save failed. previous save was preserved." - _root_panel.show() + _action_in_progress = false + _begin_root_entry(false) + return ConfirmationAction.RESET_PROGRESS: reset_progress_requested.emit() ConfirmationAction.QUIT_ANYWAY: quit_requested.emit() _: - _root_panel.show() + _action_in_progress = false + _begin_root_entry(false) + return _action_in_progress = false func report_reset_failure() -> void: _action_in_progress = false - _root_panel.show() - _confirmation_panel.hide() + _confirmation_action = ConfirmationAction.NONE _feedback.text = "reset failed. your progression was preserved." - %ResumeButton.grab_focus() + if visible: + _begin_root_entry(false) + + +func _begin_root_entry(flurry_already_emitted: bool) -> void: + if _root_transition_active: + return + _root_transition_generation += 1 + _root_transition_active = true + if not flurry_already_emitted: + _emit_transition_flurry() + var generation: int = _root_transition_generation + _root_page.transition_in( + true, + _finish_root_entry.bind(generation), + PAGE_INCOMING_DURATION + ) + + +func _finish_root_entry(generation: int) -> void: + if generation != _root_transition_generation or not visible: + return + _root_transition_active = false + + +func _begin_root_exit(completed: Callable) -> void: + if _root_transition_active or not _root_page.visible: + return + _root_transition_generation += 1 + _root_transition_active = true + _emit_transition_flurry() + var generation: int = _root_transition_generation + _root_page.transition_out( + _finish_root_exit.bind(generation, completed), + PAGE_OUTGOING_DURATION + ) + + +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: + _finish_close(CloseReason.USER_RETURN, true) + + +func _finish_close(reason: CloseReason, restore_controls: bool) -> void: + _closing_menu = true + if _settings_panel.visible: + _settings_panel.close_panel(true) + _closing_menu = false + _root_transition_generation += 1 + _root_transition_active = false + _cancel_backdrop_fade() + _cancel_confirmation_fade() + _dim_background.color.a = 0.0 + _dim_background.hide() + _confirmation_panel.modulate.a = 0.0 + _set_confirmation_interactive(false) + _settings_panel.hide() + _root_page.hide_page() + _confirmation_panel.hide() + _confirmation_action = ConfirmationAction.NONE + _action_in_progress = false + _transition_flurry.clear_flurries() + hide() + if _fishing_spot != null and is_instance_valid(_fishing_spot): + _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) + get_viewport().gui_release_focus() + if restore_controls: + _restore_controls() + else: + _control_snapshot_stored = false + _apply_mouse_close_policy(reason) + menu_visibility_changed.emit(false) + + +func _emit_transition_flurry() -> void: + _transition_flurry.emit_flurry() + + +func _fade_backdrop(fade_in: bool) -> void: + _backdrop_fade_generation += 1 + _cancel_backdrop_fade() + _dim_background.show() + var target_alpha: float = BACKDROP_TARGET_ALPHA if fade_in else 0.0 + if is_equal_approx(_dim_background.color.a, target_alpha): + _finish_backdrop_fade(_backdrop_fade_generation, fade_in) + return + var generation: int = _backdrop_fade_generation + _backdrop_fade = create_tween() + _backdrop_fade.tween_property( + _dim_background, + "color:a", + target_alpha, + VISIBILITY_FADE_DURATION + ).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT) + _backdrop_fade.finished.connect( + _finish_backdrop_fade.bind(generation, fade_in), + CONNECT_ONE_SHOT + ) + + +func _finish_backdrop_fade(generation: int, faded_in: bool) -> void: + if generation != _backdrop_fade_generation: + return + _backdrop_fade = null + _dim_background.color.a = ( + BACKDROP_TARGET_ALPHA if faded_in else 0.0 + ) + if not faded_in: + _dim_background.hide() + + +func _cancel_backdrop_fade() -> void: + if _backdrop_fade != null: + _backdrop_fade.kill() + _backdrop_fade = null + + +func _fade_confirmation( + fade_in: bool, + completed: Callable, +) -> void: + _confirmation_fade_generation += 1 + _cancel_confirmation_fade() + _confirmation_fade_active = true + _set_confirmation_interactive(false) + _confirmation_panel.show() + var target_alpha: float = 1.0 if fade_in else 0.0 + if is_equal_approx(_confirmation_panel.modulate.a, target_alpha): + _finish_confirmation_fade( + _confirmation_fade_generation, + fade_in, + completed + ) + return + var generation: int = _confirmation_fade_generation + _confirmation_fade = create_tween() + _confirmation_fade.tween_property( + _confirmation_panel, + "modulate:a", + target_alpha, + VISIBILITY_FADE_DURATION + ).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT) + _confirmation_fade.finished.connect( + _finish_confirmation_fade.bind( + generation, + fade_in, + completed + ), + CONNECT_ONE_SHOT + ) + + +func _finish_confirmation_fade( + generation: int, + faded_in: bool, + completed: Callable, +) -> void: + if generation != _confirmation_fade_generation or not visible: + return + _confirmation_fade = null + _confirmation_fade_active = false + _confirmation_panel.modulate.a = 1.0 if faded_in else 0.0 + if not faded_in: + _confirmation_panel.hide() + completed.call() + + +func _cancel_confirmation_fade() -> void: + _confirmation_fade_generation += 1 + _confirmation_fade_active = false + if _confirmation_fade != null: + _confirmation_fade.kill() + _confirmation_fade = null + + +func _set_confirmation_interactive(interactive: bool) -> void: + _confirmation_panel.mouse_filter = ( + Control.MOUSE_FILTER_STOP + if interactive + else Control.MOUSE_FILTER_IGNORE + ) + for button: Button in [_confirm_button, _cancel_button]: + if not interactive and button.has_focus(): + button.release_focus() + button.focus_mode = ( + Control.FOCUS_ALL if interactive else Control.FOCUS_NONE + ) + button.mouse_filter = ( + Control.MOUSE_FILTER_STOP + if interactive + else Control.MOUSE_FILTER_IGNORE + ) + + +func _update_responsive_pause_stage() -> void: + if not is_node_ready(): + return + var display_size := Vector2( + maxf(1.0, size.x), + maxf(1.0, size.y) + ) + var reference_size: Vector2 = ( + PAUSE_COMPACT_REFERENCE_SIZE + if display_size.y < COMPACT_HEIGHT_THRESHOLD + else PAUSE_DESKTOP_REFERENCE_SIZE + ) + var presentation_scale: float = minf( + display_size.x / reference_size.x, + display_size.y / reference_size.y + ) + _presentation_scale_root.size = reference_size + _presentation_scale_root.scale = Vector2.ONE * presentation_scale + _presentation_scale_root.position = ( + display_size - reference_size * presentation_scale + ) * 0.5 func _restore_controls() -> void: diff --git a/ui/pause_menu.tscn b/ui/pause_menu.tscn index ec504ac..79d8f1c 100644 --- a/ui/pause_menu.tscn +++ b/ui/pause_menu.tscn @@ -1,8 +1,13 @@ -[gd_scene load_steps=4 format=3] +[gd_scene load_steps=9 format=3] [ext_resource type="Script" path="res://ui/pause_menu.gd" id="1_script"] [ext_resource type="PackedScene" path="res://ui/settings_panel.tscn" id="2_settings"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="3_theme"] +[ext_resource type="Script" path="res://ui/settings/settings_bubble_page.gd" id="4_page"] +[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_button.tscn" id="5_bubble"] +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="6_cluster"] +[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="7_profile"] +[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_transition_flurry.gd" id="8_flurry"] [node name="PauseMenu" type="Control"] unique_name_in_owner = true @@ -19,6 +24,7 @@ theme = ExtResource("3_theme") script = ExtResource("1_script") [node name="DimBackground" type="ColorRect" parent="."] +unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -26,84 +32,162 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 mouse_filter = 0 -color = Color(0.02, 0.05, 0.07, 0.78) +color = Color(0.025, 0.12, 0.19, 0.68) -[node name="RootCenter" type="CenterContainer" parent="."] +[node name="TransitionBubbleLayer" type="Control" parent="."] +unique_name_in_owner = true +z_index = 1 +clip_contents = true layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("8_flurry") -[node name="RootPanel" type="PanelContainer" parent="RootCenter"] +[node name="ResponsivePauseStage" type="Control" parent="."] unique_name_in_owner = true -custom_minimum_size = Vector2(560, 0) -layout_mode = 2 +z_index = 2 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 1 -[node name="Margin" type="MarginContainer" parent="RootCenter/RootPanel"] -layout_mode = 2 -theme_override_constants/margin_left = 32 -theme_override_constants/margin_top = 26 -theme_override_constants/margin_right = 32 -theme_override_constants/margin_bottom = 26 - -[node name="Content" type="VBoxContainer" parent="RootCenter/RootPanel/Margin"] -layout_mode = 2 -theme_override_constants/separation = 11 - -[node name="Title" type="Label" parent="RootCenter/RootPanel/Margin/Content"] -layout_mode = 2 -theme_override_font_sizes/font_size = 45 -text = "game menu" -horizontal_alignment = 1 - -[node name="ResumeButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] +[node name="PausePresentationScaleRoot" type="Control" parent="ResponsivePauseStage"] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 64) -layout_mode = 2 -text = "return to game" +layout_mode = 0 +offset_right = 1280.0 +offset_bottom = 720.0 +mouse_filter = 1 -[node name="SaveButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] +[node name="RootPage" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot"] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 64) -layout_mode = 2 -text = "save now" +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +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/SettingsButton"), NodePath("BubbleCluster/ReturnToTitleButton"), NodePath("BubbleCluster/ResetProgressButton"), NodePath("BubbleCluster/QuitButton")]) +focus_paths = Array[NodePath]([NodePath("BubbleCluster/ResumeButton"), NodePath("BubbleCluster/SaveButton"), 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="SettingsButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] +[node name="BubbleCluster" type="Control" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage"] +layout_mode = 0 +offset_right = 720.0 +offset_bottom = 520.0 +script = ExtResource("6_cluster") +profile = ExtResource("7_profile") +desktop_reference_size = Vector2(720, 520) +compact_reference_size = Vector2(608, 448) + +[node name="ResumeButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 64) -layout_mode = 2 +custom_minimum_size = Vector2(0, 0) +text = "return\nto game" +accessibility_name = "return to game" +neutral_size = Vector2(174, 164) +desktop_anchor = Vector2(335, 220) +compact_anchor = Vector2(350, 225) +compact_minimum_size = Vector2(132, 126) +minimum_font_size = 18 +maximum_font_size = 27 +motion_phase = 0.3 + +[node name="SaveButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 0) +text = "save\nnow" +accessibility_name = "save now" +neutral_size = Vector2(126, 120) +desktop_anchor = Vector2(185, 185) +compact_anchor = Vector2(165, 180) +compact_minimum_size = Vector2(100, 96) +minimum_font_size = 15 +maximum_font_size = 21 +motion_phase = 1.15 + +[node name="SettingsButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 0) text = "settings" +neutral_size = Vector2(144, 136) +desktop_anchor = Vector2(485, 170) +compact_anchor = Vector2(515, 175) +compact_minimum_size = Vector2(112, 108) +minimum_font_size = 16 +maximum_font_size = 23 +motion_phase = 2.05 -[node name="ReturnToTitleButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] +[node name="ReturnToTitleButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 64) -layout_mode = 2 -text = "return to title" +custom_minimum_size = Vector2(0, 0) +text = "return\nto title" +accessibility_name = "return to title" +neutral_size = Vector2(132, 126) +desktop_anchor = Vector2(520, 340) +compact_anchor = Vector2(535, 360) +compact_minimum_size = Vector2(106, 102) +minimum_font_size = 14 +maximum_font_size = 21 +motion_phase = 3.0 -[node name="ResetProgressButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] +[node name="ResetProgressButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 64) -layout_mode = 2 -theme_type_variation = &"DangerButton" -text = "reset progress" +custom_minimum_size = Vector2(0, 0) +text = "reset\nprogress" +accessibility_name = "reset progress" +neutral_size = Vector2(126, 120) +desktop_anchor = Vector2(245, 365) +compact_anchor = Vector2(200, 380) +compact_minimum_size = Vector2(102, 98) +minimum_font_size = 14 +maximum_font_size = 20 +motion_phase = 3.9 -[node name="QuitButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"] +[node name="QuitButton" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster" instance=ExtResource("5_bubble")] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 64) -layout_mode = 2 +custom_minimum_size = Vector2(0, 0) text = "quit" +neutral_size = Vector2(102, 98) +desktop_anchor = Vector2(390, 395) +compact_anchor = Vector2(390, 410) +compact_minimum_size = Vector2(84, 82) +minimum_font_size = 14 +maximum_font_size = 18 +motion_phase = 4.75 -[node name="FeedbackLabel" type="Label" parent="RootCenter/RootPanel/Margin/Content"] +[node name="FeedbackLabel" type="Label" parent="ResponsivePauseStage/PausePresentationScaleRoot/RootPage/BubbleCluster"] unique_name_in_owner = true -custom_minimum_size = Vector2(0, 54) -layout_mode = 2 +layout_mode = 1 +anchors_preset = 12 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = -38.0 +offset_bottom = -6.0 +grow_horizontal = 2 +grow_vertical = 0 +mouse_filter = 2 horizontal_alignment = 1 vertical_alignment = 1 autowrap_mode = 2 -[node name="SettingsCenter" type="CenterContainer" parent="."] +[node name="SettingsCenter" type="CenterContainer" parent="ResponsivePauseStage/PausePresentationScaleRoot"] +unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -112,11 +196,12 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -[node name="SettingsPanel" parent="SettingsCenter" instance=ExtResource("2_settings")] +[node name="SettingsPanel" parent="ResponsivePauseStage/PausePresentationScaleRoot/SettingsCenter" instance=ExtResource("2_settings")] unique_name_in_owner = true layout_mode = 2 [node name="ConfirmationCenter" type="CenterContainer" parent="."] +z_index = 3 layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index 850db9d..1b3d0d7 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -69,6 +69,7 @@ var _pages: Dictionary[StringName, SettingsBubblePage] = {} var _page_transition_generation: int = 0 var _page_transition_active: bool = false var _presentation_mode: PresentationMode = PresentationMode.GAMEPLAY_MODAL +var _animate_gameplay_host_transitions: bool = false var _auto_click_enabled: bool = false var _auto_click_interval_value: float = 0.20 var _mouse_sensitivity: float = 0.005 @@ -145,10 +146,16 @@ func _ready() -> void: func open_panel( settings_manager: SettingsManagerType, presentation_mode: PresentationMode = PresentationMode.GAMEPLAY_MODAL, + animate_gameplay_host_transitions: bool = false, + uses_external_backdrop: bool = false, ) -> void: _cancel_page_transition() _presentation_mode = presentation_mode - _backdrop.visible = presentation_mode == PresentationMode.GAMEPLAY_MODAL + _animate_gameplay_host_transitions = animate_gameplay_host_transitions + _backdrop.visible = ( + presentation_mode == PresentationMode.GAMEPLAY_MODAL + and not uses_external_backdrop + ) _settings_manager = settings_manager _load_controls() _feedback.text = "" @@ -156,7 +163,10 @@ func open_panel( _page_stack.clear() _page_stack.append(PAGE_ROOT) panel_visibility_changed.emit(true) - if presentation_mode == PresentationMode.TITLE_EMBEDDED: + if ( + presentation_mode == PresentationMode.TITLE_EMBEDDED + or _animate_gameplay_host_transitions + ): for page: SettingsBubblePage in _pages.values(): page.hide_page() _page_transition_generation += 1 @@ -164,7 +174,7 @@ func open_panel( var generation: int = _page_transition_generation _root_page.transition_in( true, - _finish_embedded_open.bind(generation), + _finish_animated_open.bind(generation), TITLE_HOST_INCOMING_DURATION ) else: @@ -172,13 +182,19 @@ func open_panel( opened.emit() -func close_panel() -> void: +func close_panel(immediate: bool = false) -> void: if not visible: return - if _presentation_mode == PresentationMode.TITLE_EMBEDDED: + if immediate: + _finish_panel_close(false) + return + if ( + _presentation_mode == PresentationMode.TITLE_EMBEDDED + or _animate_gameplay_host_transitions + ): if _page_transition_active: return - _begin_embedded_close(false) + _begin_animated_close(false) return _finish_panel_close(false) @@ -266,14 +282,14 @@ func _finish_incoming_page(generation: int) -> void: _page_transition_active = false -func _finish_embedded_open(generation: int) -> void: +func _finish_animated_open(generation: int) -> void: if generation != _page_transition_generation or not _page_transition_active: return _page_transition_active = false opened.emit() -func _begin_embedded_close(applied_result: bool) -> void: +func _begin_animated_close(applied_result: bool) -> void: var active_page: SettingsBubblePage = _get_active_page() if active_page == null: return @@ -282,12 +298,12 @@ func _begin_embedded_close(applied_result: bool) -> void: navigation_transition_started.emit() var generation: int = _page_transition_generation active_page.transition_out( - _finish_embedded_close.bind(generation, applied_result), + _finish_animated_close.bind(generation, applied_result), TITLE_HOST_OUTGOING_DURATION ) -func _finish_embedded_close( +func _finish_animated_close( generation: int, applied_result: bool, ) -> void: @@ -329,8 +345,11 @@ func _apply_settings() -> void: edited.world_pixel_size = _settings_manager.current_settings.world_pixel_size edited.ui_pixel_size = _settings_manager.current_settings.ui_pixel_size if _settings_manager.apply_settings(edited): - if _presentation_mode == PresentationMode.TITLE_EMBEDDED: - _begin_embedded_close(true) + if ( + _presentation_mode == PresentationMode.TITLE_EMBEDDED + or _animate_gameplay_host_transitions + ): + _begin_animated_close(true) else: _finish_panel_close(true) else: