Migrate settings to bubble menus

This commit is contained in:
Alexander Sellite 2026-07-27 17:33:53 -04:00
parent 053cfe037d
commit 359a96f6e2
7 changed files with 1741 additions and 289 deletions

View file

@ -149,7 +149,7 @@ func handle_escape() -> bool:
if _confirmation_panel.visible:
_close_confirmation()
elif _settings_panel.visible:
_settings_panel.close_panel()
_settings_panel.handle_back()
else:
resume()
return true
@ -176,7 +176,10 @@ func _open_settings() -> void:
if _action_in_progress or _confirmation_panel.visible:
return
_root_panel.hide()
_settings_panel.open_panel(_settings_manager)
_settings_panel.open_panel(
_settings_manager,
SettingsPanelType.PresentationMode.GAMEPLAY_MODAL
)
func _on_settings_applied() -> void:

View file

@ -0,0 +1,237 @@
class_name SettingsBubblePage
extends Control
@export var page_id: StringName
@export var cluster_path: NodePath = ^"BubbleCluster"
@export var bubble_paths: Array[NodePath] = []
@export var focus_paths: Array[NodePath] = []
@export var initial_focus_path: NodePath
@export var back_focus_path: NodePath
@export var maximum_layout_size: Vector2 = Vector2(720.0, 520.0)
@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 = 0.95
@export_range(0.1, 2.0, 0.01) var incoming_rise_duration: float = 1.05
@export_range(0.0, 128.0, 1.0) var transition_safe_margin: float = 24.0
var _cluster: BubbleCluster
var _bubbles: Array[BubbleButton] = []
var _focus_bubbles: Array[BubbleButton] = []
var _transition: Tween
var _transition_generation: int = 0
var _resting_cluster_position: Vector2 = Vector2.ZERO
var _is_transitioning: bool = false
func _ready() -> void:
_cluster = get_node(cluster_path) as BubbleCluster
for path: NodePath in bubble_paths:
var bubble := get_node(path) as BubbleButton
if bubble != null:
_bubbles.append(bubble)
for path: NodePath in focus_paths:
var bubble := get_node(path) as BubbleButton
if bubble != null:
_focus_bubbles.append(bubble)
_cluster.configure(_bubbles)
_configure_focus_order()
resized.connect(_update_layout)
call_deferred("_update_layout")
set_process(false)
func show_page(should_focus: bool = true) -> void:
_transition_generation += 1
_cancel_transition()
show()
_is_transitioning = false
_cluster.position = _resting_cluster_position
set_process(true)
_set_interactive(true)
if should_focus:
focus_initial()
func transition_out(
completed: Callable,
duration_override: float = -1.0,
) -> void:
_transition_generation += 1
_cancel_transition()
_is_transitioning = true
_set_interactive(false)
set_process(true)
var duration: float = (
duration_override
if duration_override > 0.0
else outgoing_rise_duration
)
var generation: int = _transition_generation
_transition = create_tween()
_transition.tween_property(
_cluster,
"position:y",
-_cluster.size.y - transition_safe_margin,
duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_transition.finished.connect(
_finish_transition_out.bind(generation, completed),
CONNECT_ONE_SHOT
)
func transition_in(
should_focus: bool,
completed: Callable,
duration_override: float = -1.0,
) -> void:
_transition_generation += 1
_cancel_transition()
show()
_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 generation: int = _transition_generation
_transition = create_tween()
_transition.tween_property(
_cluster,
"position",
_resting_cluster_position,
duration
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_transition.finished.connect(
_finish_transition_in.bind(generation, should_focus, completed),
CONNECT_ONE_SHOT
)
func hide_page() -> void:
_transition_generation += 1
_cancel_transition()
_is_transitioning = false
_set_interactive(false)
set_process(false)
hide()
if _cluster != null:
_cluster.position = _resting_cluster_position
func focus_initial() -> void:
var initial := get_node_or_null(initial_focus_path) as Control
if initial != null and initial.visible and initial.focus_mode != Control.FOCUS_NONE:
initial.grab_focus()
func focus_back() -> void:
var back := get_node_or_null(back_focus_path) as Control
if back != null and back.visible and back.focus_mode != Control.FOCUS_NONE:
back.grab_focus()
func get_page_id() -> StringName:
return page_id
func _process(delta: float) -> void:
if visible:
_cluster.advance_motion(delta)
func _update_layout() -> void:
if not is_node_ready() or _cluster == null:
return
var compact: bool = (
size.x < compact_width_threshold
or size.y < compact_height_threshold
)
var layout_maximum: Vector2 = maximum_layout_size
if compact and compact_maximum_layout_size != Vector2.ZERO:
layout_maximum = compact_maximum_layout_size
var field_size := Vector2(
minf(size.x, layout_maximum.x),
minf(size.y, layout_maximum.y)
)
field_size.x = maxf(1.0, field_size.x)
field_size.y = maxf(1.0, field_size.y)
_resting_cluster_position = (size - field_size) * 0.5
if not _is_transitioning:
_cluster.position = _resting_cluster_position
_cluster.size = field_size
_cluster.apply_layout(field_size, compact)
func _configure_focus_order() -> void:
for bubble: BubbleButton in _bubbles:
bubble.focus_mode = Control.FOCUS_NONE
for index: int in _focus_bubbles.size():
var bubble: BubbleButton = _focus_bubbles[index]
if index > 0:
bubble.focus_neighbor_top = bubble.get_path_to(
_focus_bubbles[index - 1]
)
bubble.focus_neighbor_left = bubble.focus_neighbor_top
if index + 1 < _focus_bubbles.size():
bubble.focus_neighbor_bottom = bubble.get_path_to(
_focus_bubbles[index + 1]
)
bubble.focus_neighbor_right = bubble.focus_neighbor_bottom
func _set_interactive(interactive: bool) -> void:
mouse_filter = (
Control.MOUSE_FILTER_PASS
if interactive
else Control.MOUSE_FILTER_IGNORE
)
for bubble: BubbleButton in _focus_bubbles:
bubble.focus_mode = (
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
)
bubble.mouse_filter = (
Control.MOUSE_FILTER_STOP
if interactive
else Control.MOUSE_FILTER_IGNORE
)
func _finish_transition_out(generation: int, completed: Callable) -> void:
if generation != _transition_generation or not visible:
return
_transition = null
_is_transitioning = false
hide()
_cluster.position = _resting_cluster_position
completed.call()
func _finish_transition_in(
generation: int,
should_focus: bool,
completed: Callable,
) -> void:
if generation != _transition_generation or not visible:
return
_transition = null
_is_transitioning = false
_cluster.position = _resting_cluster_position
set_process(true)
_set_interactive(true)
if should_focus:
focus_initial()
completed.call()
func _cancel_transition() -> void:
if _transition != null:
_transition.kill()
_transition = null

View file

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

View file

@ -1,86 +1,335 @@
class_name SettingsPanel
extends PanelContainer
extends Control
const MAX_PANEL_SIZE: Vector2 = Vector2(720.0, 600.0)
const MAX_PANEL_SIZE: Vector2 = Vector2(960.0, 700.0)
const PANEL_EDGE_MARGIN: float = 16.0
const PAGE_ROOT: StringName = &"root"
const PAGE_DISPLAY: StringName = &"display"
const PAGE_CONTROLS: StringName = &"controls"
const PAGE_ACCESSIBILITY: StringName = &"accessibility"
const TITLE_HOST_OUTGOING_DURATION: float = 1.70
const TITLE_HOST_INCOMING_DURATION: float = 1.80
const PIXELATION_NAMES: PackedStringArray = [
"legible",
"cute",
"retro",
"hardcore",
"wtf",
]
const SettingsManagerType = preload(
"res://settings/player_settings_manager.gd"
)
signal applied
signal closed
signal opened
signal crisp_reset_focus_requested
signal panel_visibility_changed(is_visible: bool)
@onready var _auto_click_toggle: CheckButton = %AutoClickToggle
@onready var _auto_click_interval: HSlider = %AutoClickInterval
@onready var _auto_click_interval_value: Label = %AutoClickIntervalValue
@onready var _mouse_sensitivity: HSlider = %MouseSensitivity
@onready var _mouse_sensitivity_value: Label = %MouseSensitivityValue
@onready var _controller_sensitivity: HSlider = %ControllerSensitivity
@onready var _controller_sensitivity_value: Label = %ControllerSensitivityValue
@onready var _invert_y_toggle: CheckButton = %InvertYToggle
@onready var _world_pixel_size: HSlider = %WorldPixelSize
@onready var _world_pixel_size_value: Label = %WorldPixelSizeValue
@onready var _ui_pixel_size: HSlider = %UIPixelSize
@onready var _ui_pixel_size_value: Label = %UIPixelSizeValue
enum PresentationMode {
TITLE_EMBEDDED,
GAMEPLAY_MODAL,
}
@onready var _backdrop: ColorRect = $Backdrop
@onready var _root_page: SettingsBubblePage = %RootPage
@onready var _display_page: SettingsBubblePage = %DisplayPage
@onready var _controls_page: SettingsBubblePage = %ControlsPage
@onready var _accessibility_page: SettingsBubblePage = %AccessibilityPage
@onready var _feedback: Label = %SettingsFeedback
@onready var _world_value: BubbleButton = %WorldValue
@onready var _ui_value: BubbleButton = %UIValue
@onready var _mouse_value: BubbleButton = %MouseValue
@onready var _controller_value: BubbleButton = %ControllerValue
@onready var _invert_y_toggle: BubbleButton = %InvertYToggle
@onready var _auto_click_toggle: BubbleButton = %AutoClickToggle
@onready var _auto_click_interval: BubbleButton = %AutoClickIntervalValue
@onready var _world_options: Array[BubbleButton] = [
%WorldLegible,
%WorldCute,
%WorldRetro,
%WorldHardcore,
%WorldWtf,
]
@onready var _ui_options: Array[BubbleButton] = [
%UILegible,
%UICute,
%UIRetro,
%UIHardcore,
%UIWtf,
]
var _settings_manager: SettingsManagerType
var _loading_controls: bool = false
var _effective_ui_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
var _page_stack: Array[StringName] = []
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 _auto_click_enabled: bool = false
var _auto_click_interval_value: float = 0.20
var _mouse_sensitivity: float = 0.005
var _controller_sensitivity: float = 2.5
var _invert_camera_y: bool = false
func _ready() -> void:
_pages = {
PAGE_ROOT: _root_page,
PAGE_DISPLAY: _display_page,
PAGE_CONTROLS: _controls_page,
PAGE_ACCESSIBILITY: _accessibility_page,
}
%DisplayCategory.pressed.connect(_push_page.bind(PAGE_DISPLAY))
%ControlsCategory.pressed.connect(_push_page.bind(PAGE_CONTROLS))
%AccessibilityCategory.pressed.connect(
_push_page.bind(PAGE_ACCESSIBILITY)
)
%ApplySettingsButton.pressed.connect(_apply_settings)
%CancelSettingsButton.pressed.connect(close_panel)
_auto_click_interval.value_changed.connect(_refresh_value_labels)
_mouse_sensitivity.value_changed.connect(_refresh_value_labels)
_controller_sensitivity.value_changed.connect(_refresh_value_labels)
_world_pixel_size.value_changed.connect(_on_pixel_size_changed)
_ui_pixel_size.value_changed.connect(_on_pixel_size_changed)
%CancelSettingsButton.gui_input.connect(_on_back_button_gui_input)
get_viewport().size_changed.connect(_refresh_panel_size)
%RootBackButton.pressed.connect(close_panel)
%DisplayBackButton.pressed.connect(handle_back)
%ControlsBackButton.pressed.connect(handle_back)
%AccessibilityBackButton.pressed.connect(handle_back)
%RootBackButton.gui_input.connect(_on_back_bubble_gui_input)
%DisplayBackButton.gui_input.connect(_on_back_bubble_gui_input)
%ControlsBackButton.gui_input.connect(_on_back_bubble_gui_input)
%AccessibilityBackButton.gui_input.connect(_on_back_bubble_gui_input)
for index: int in _world_options.size():
_world_options[index].pressed.connect(
_set_world_pixelation.bind(index + 1)
)
for index: int in _ui_options.size():
_ui_options[index].pressed.connect(
_set_ui_pixelation.bind(index + 1)
)
%MouseDecrease.pressed.connect(_adjust_mouse_sensitivity.bind(-1))
%MouseIncrease.pressed.connect(_adjust_mouse_sensitivity.bind(1))
_mouse_value.pressed.connect(_adjust_mouse_sensitivity.bind(1))
%ControllerDecrease.pressed.connect(
_adjust_controller_sensitivity.bind(-1)
)
%ControllerIncrease.pressed.connect(
_adjust_controller_sensitivity.bind(1)
)
_controller_value.pressed.connect(
_adjust_controller_sensitivity.bind(1)
)
_invert_y_toggle.pressed.connect(_toggle_invert_y)
_auto_click_toggle.pressed.connect(_toggle_auto_click)
%IntervalDecrease.pressed.connect(_adjust_auto_click_interval.bind(-1))
%IntervalIncrease.pressed.connect(_adjust_auto_click_interval.bind(1))
_auto_click_interval.pressed.connect(
_adjust_auto_click_interval.bind(1)
)
_mouse_value.gui_input.connect(
_on_continuous_value_input.bind(&"mouse")
)
_controller_value.gui_input.connect(
_on_continuous_value_input.bind(&"controller")
)
_auto_click_interval.gui_input.connect(
_on_continuous_value_input.bind(&"interval")
)
resized.connect(_refresh_panel_size)
var parent_control := get_parent() as Control
if parent_control != null:
parent_control.resized.connect(_refresh_panel_size)
for page: SettingsBubblePage in _pages.values():
page.hide_page()
call_deferred("_refresh_panel_size")
func open_panel(settings_manager: SettingsManagerType) -> void:
func open_panel(
settings_manager: SettingsManagerType,
presentation_mode: PresentationMode = PresentationMode.GAMEPLAY_MODAL,
) -> void:
_cancel_page_transition()
_presentation_mode = presentation_mode
_backdrop.visible = presentation_mode == PresentationMode.GAMEPLAY_MODAL
_settings_manager = settings_manager
_load_controls()
_feedback.text = ""
show()
_page_stack.clear()
_page_stack.append(PAGE_ROOT)
panel_visibility_changed.emit(true)
_auto_click_toggle.grab_focus()
if presentation_mode == PresentationMode.TITLE_EMBEDDED:
for page: SettingsBubblePage in _pages.values():
page.hide_page()
_page_transition_generation += 1
_page_transition_active = true
var generation: int = _page_transition_generation
_root_page.transition_in(
true,
_finish_embedded_open.bind(generation),
TITLE_HOST_INCOMING_DURATION
)
else:
_show_active_page(true)
opened.emit()
func close_panel() -> void:
if not visible:
return
if _presentation_mode == PresentationMode.TITLE_EMBEDDED:
if _page_transition_active:
return
_begin_embedded_close(false)
return
_finish_panel_close(false)
func _finish_panel_close(applied_result: bool) -> void:
_cancel_page_transition()
for page: SettingsBubblePage in _pages.values():
page.hide_page()
_page_stack.clear()
hide()
panel_visibility_changed.emit(false)
if applied_result:
applied.emit()
else:
_load_controls()
closed.emit()
func handle_back() -> void:
if _page_transition_active:
return
if _page_stack.size() > 1:
_start_page_transition(_page_stack[-2], false)
return
close_panel()
func get_active_page_id() -> StringName:
return _page_stack.back() if not _page_stack.is_empty() else StringName()
func _push_page(page_id: StringName) -> void:
if (
_page_transition_active
or not _pages.has(page_id)
or get_active_page_id() == page_id
):
return
_start_page_transition(page_id, true)
func _start_page_transition(page_id: StringName, push_page: bool) -> void:
var outgoing_page: SettingsBubblePage = _get_active_page()
var incoming_page: SettingsBubblePage = _pages.get(page_id)
if outgoing_page == null or incoming_page == null:
return
_page_transition_generation += 1
_page_transition_active = true
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:
page.hide_page()
incoming_page.transition_in(
true,
_finish_incoming_page.bind(generation)
)
func _finish_incoming_page(generation: int) -> void:
if generation != _page_transition_generation or not _page_transition_active:
return
_page_transition_active = false
func _finish_embedded_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:
var active_page: SettingsBubblePage = _get_active_page()
if active_page == null:
return
_page_transition_generation += 1
_page_transition_active = true
var generation: int = _page_transition_generation
active_page.transition_out(
_finish_embedded_close.bind(generation, applied_result),
TITLE_HOST_OUTGOING_DURATION
)
func _finish_embedded_close(
generation: int,
applied_result: bool,
) -> void:
if generation != _page_transition_generation or not _page_transition_active:
return
_finish_panel_close(applied_result)
func _cancel_page_transition() -> void:
_page_transition_generation += 1
_page_transition_active = false
func _show_active_page(should_focus: bool) -> void:
for page_id: StringName in _pages:
var page: SettingsBubblePage = _pages[page_id]
if page_id == get_active_page_id():
page.show_page(should_focus)
else:
page.hide_page()
func _get_active_page() -> SettingsBubblePage:
return _pages.get(get_active_page_id()) as SettingsBubblePage
func _apply_settings() -> void:
if _page_transition_active:
return
if _settings_manager == null:
_feedback.text = "settings are unavailable."
return
var edited := PlayerSettings.new()
edited.auto_click_enabled = _auto_click_toggle.button_pressed
edited.auto_click_interval = float(_auto_click_interval.value)
edited.mouse_camera_sensitivity = float(_mouse_sensitivity.value)
edited.controller_camera_sensitivity = float(_controller_sensitivity.value)
edited.invert_camera_y = _invert_y_toggle.button_pressed
edited.world_pixel_size = roundi(_world_pixel_size.value)
edited.ui_pixel_size = roundi(_ui_pixel_size.value)
edited.auto_click_enabled = _auto_click_enabled
edited.auto_click_interval = _auto_click_interval_value
edited.mouse_camera_sensitivity = _mouse_sensitivity
edited.controller_camera_sensitivity = _controller_sensitivity
edited.invert_camera_y = _invert_camera_y
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):
hide()
panel_visibility_changed.emit(false)
applied.emit()
if _presentation_mode == PresentationMode.TITLE_EMBEDDED:
_begin_embedded_close(true)
else:
_finish_panel_close(true)
else:
_feedback.text = "failed to save settings."
@ -88,54 +337,139 @@ func _apply_settings() -> void:
func _load_controls() -> void:
if _settings_manager == null or not is_node_ready():
return
_loading_controls = true
var settings: PlayerSettings = _settings_manager.current_settings
_auto_click_toggle.button_pressed = settings.auto_click_enabled
_auto_click_interval.value = settings.auto_click_interval
_mouse_sensitivity.value = settings.mouse_camera_sensitivity
_controller_sensitivity.value = settings.controller_camera_sensitivity
_invert_y_toggle.button_pressed = settings.invert_camera_y
_world_pixel_size.value = settings.world_pixel_size
_ui_pixel_size.value = settings.ui_pixel_size
_loading_controls = false
_refresh_value_labels(0.0)
_auto_click_enabled = settings.auto_click_enabled
_auto_click_interval_value = settings.auto_click_interval
_mouse_sensitivity = settings.mouse_camera_sensitivity
_controller_sensitivity = settings.controller_camera_sensitivity
_invert_camera_y = settings.invert_camera_y
_refresh_value_labels()
func _refresh_value_labels(_unused: float) -> void:
_auto_click_interval_value.text = "%.2f s" % _auto_click_interval.value
_mouse_sensitivity_value.text = "%.4f" % _mouse_sensitivity.value
_controller_sensitivity_value.text = "%.1f" % _controller_sensitivity.value
_world_pixel_size_value.text = _pixel_size_label(
roundi(_world_pixel_size.value),
true
func _refresh_value_labels() -> void:
if _settings_manager == null:
return
var settings: PlayerSettings = _settings_manager.current_settings
_world_value.text = (
"world\npixelation\n"
+ _pixel_size_label(settings.world_pixel_size)
)
_ui_pixel_size_value.text = _pixel_size_label(
roundi(_ui_pixel_size.value),
false
_ui_value.text = (
"ui\npixelation\n"
+ _pixel_size_label(settings.ui_pixel_size)
)
_mouse_value.text = "mouse\nsensitivity\n%.4f" % _mouse_sensitivity
_controller_value.text = (
"controller\nsensitivity\n%.1f" % _controller_sensitivity
)
_invert_y_toggle.text = (
"invert\nvertical\ncamera\n"
+ ("on" if _invert_camera_y else "off")
)
_auto_click_toggle.text = (
"accessibility\nauto-click\n"
+ ("on" if _auto_click_enabled else "off")
)
_auto_click_interval.text = (
"auto-click\ninterval\n%.2f s" % _auto_click_interval_value
)
for index: int in _world_options.size():
_world_options[index].button_pressed = (
index + 1 == settings.world_pixel_size
)
for index: int in _ui_options.size():
_ui_options[index].button_pressed = index + 1 == settings.ui_pixel_size
func _on_pixel_size_changed(_unused: float) -> void:
_refresh_value_labels(0.0)
if _loading_controls or _settings_manager == null:
func _set_world_pixelation(pixel_size: int) -> void:
if _settings_manager == null:
return
if not _settings_manager.apply_pixelation(
roundi(_world_pixel_size.value),
roundi(_ui_pixel_size.value)
pixel_size,
_settings_manager.current_settings.ui_pixel_size
):
_feedback.text = "failed to save pixelation settings."
return
_refresh_value_labels()
func _pixel_size_label(pixel_size: int, _is_world: bool) -> String:
var names: PackedStringArray = [
"legible",
"cute",
"retro",
"hardcore",
"wtf",
]
var index: int = clampi(pixel_size - 1, 0, names.size() - 1)
return names[index]
func _set_ui_pixelation(pixel_size: int) -> void:
if _settings_manager == null:
return
if not _settings_manager.apply_pixelation(
_settings_manager.current_settings.world_pixel_size,
pixel_size
):
_feedback.text = "failed to save pixelation settings."
return
_refresh_value_labels()
func _adjust_mouse_sensitivity(direction: int) -> void:
_mouse_sensitivity = clampf(
_mouse_sensitivity + float(direction) * 0.0005,
PlayerSettings.MIN_MOUSE_SENSITIVITY,
PlayerSettings.MAX_MOUSE_SENSITIVITY
)
_refresh_value_labels()
func _adjust_controller_sensitivity(direction: int) -> void:
_controller_sensitivity = clampf(
_controller_sensitivity + float(direction) * 0.1,
PlayerSettings.MIN_CONTROLLER_SENSITIVITY,
PlayerSettings.MAX_CONTROLLER_SENSITIVITY
)
_refresh_value_labels()
func _adjust_auto_click_interval(direction: int) -> void:
_auto_click_interval_value = clampf(
_auto_click_interval_value + float(direction) * 0.01,
PlayerSettings.MIN_AUTO_CLICK_INTERVAL,
PlayerSettings.MAX_AUTO_CLICK_INTERVAL
)
_refresh_value_labels()
func _toggle_invert_y() -> void:
_invert_camera_y = not _invert_camera_y
_refresh_value_labels()
func _toggle_auto_click() -> void:
_auto_click_enabled = not _auto_click_enabled
_refresh_value_labels()
func _on_continuous_value_input(
event: InputEvent,
control_id: StringName,
) -> void:
var direction: int = 0
if event.is_action_pressed("ui_left"):
direction = -1
elif event.is_action_pressed("ui_right"):
direction = 1
if direction == 0:
return
match control_id:
&"mouse":
_adjust_mouse_sensitivity(direction)
&"controller":
_adjust_controller_sensitivity(direction)
&"interval":
_adjust_auto_click_interval(direction)
accept_event()
func _pixel_size_label(pixel_size: int) -> String:
var index: int = clampi(
pixel_size - 1,
0,
PIXELATION_NAMES.size() - 1
)
return PIXELATION_NAMES[index]
func set_effective_ui_pixel_size(pixel_size: int) -> void:
@ -145,11 +479,19 @@ func set_effective_ui_pixel_size(pixel_size: int) -> void:
PlayerSettings.MAX_UI_PIXEL_SIZE
)
if is_node_ready():
_refresh_value_labels(0.0)
_refresh_value_labels()
func focus_back_button() -> void:
%CancelSettingsButton.grab_focus()
var active_page: SettingsBubblePage = _get_active_page()
if active_page != null:
active_page.focus_back()
func _on_back_bubble_gui_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_down") or event.is_action_pressed("ui_focus_next"):
crisp_reset_focus_requested.emit()
accept_event()
func refresh_open_panel() -> void:
@ -157,15 +499,6 @@ func refresh_open_panel() -> void:
_load_controls()
func _on_back_button_gui_input(event: InputEvent) -> void:
if (
event.is_action_pressed("ui_down")
or event.is_action_pressed("ui_focus_next")
):
crisp_reset_focus_requested.emit()
accept_event()
func _refresh_panel_size() -> void:
if not is_node_ready():
return

View file

@ -1,195 +1,563 @@
[gd_scene load_steps=3 format=3]
[gd_scene load_steps=7 format=3]
[ext_resource type="Script" path="res://ui/settings_panel.gd" id="1_script"]
[ext_resource type="Script" path="res://ui/settings_panel.gd" id="1_panel"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[ext_resource type="Script" path="res://ui/settings/settings_bubble_page.gd" id="3_page"]
[ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_button.tscn" id="4_bubble"]
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_cluster.gd" id="5_cluster"]
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="6_profile"]
[node name="SettingsPanel" type="PanelContainer"]
[node name="SettingsPanel" type="Control"]
visible = false
custom_minimum_size = Vector2(720, 600)
custom_minimum_size = Vector2(960, 700)
layout_mode = 3
anchors_preset = 0
theme = ExtResource("2_theme")
script = ExtResource("1_script")
script = ExtResource("1_panel")
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 28
theme_override_constants/margin_top = 24
theme_override_constants/margin_right = 28
theme_override_constants/margin_bottom = 24
[node name="Backdrop" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
color = Color(0.025, 0.085, 0.115, 0.72)
[node name="Scroll" type="ScrollContainer" parent="Margin"]
layout_mode = 2
horizontal_scroll_mode = 0
[node name="Content" type="VBoxContainer" parent="Margin/Scroll"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 12
[node name="Heading" type="Label" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_font_sizes/font_size = 39
text = "settings"
[node name="SettingsFeedback" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 12
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -42.0
offset_bottom = -8.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 2
horizontal_alignment = 1
vertical_alignment = 1
[node name="AutoClickToggle" type="CheckButton" parent="Margin/Scroll/Content"]
[node name="RootPage" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 2
text = "accessibility auto-click"
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"root"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/DisplayCategory"), NodePath("BubbleCluster/ControlsCategory"), NodePath("BubbleCluster/AccessibilityCategory"), NodePath("BubbleCluster/ApplySettingsButton"), NodePath("BubbleCluster/RootBackButton")])
initial_focus_path = NodePath("BubbleCluster/DisplayCategory")
back_focus_path = NodePath("BubbleCluster/RootBackButton")
compact_maximum_layout_size = Vector2(544, 400)
[node name="AutoClickHelp" type="Label" parent="Margin/Scroll/Content"]
layout_mode = 2
text = "lower intervals click barriers faster while held."
autowrap_mode = 2
[node name="BubbleCluster" type="Control" parent="RootPage"]
layout_mode = 0
offset_right = 720.0
offset_bottom = 520.0
script = ExtResource("5_cluster")
profile = ExtResource("6_profile")
desktop_reference_size = Vector2(720, 520)
compact_reference_size = Vector2(608, 448)
[node name="AutoClickRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/AutoClickRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "auto-click interval"
autowrap_mode = 2
[node name="AutoClickInterval" type="HSlider" parent="Margin/Scroll/Content/AutoClickRow"]
[node name="DisplayCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 0.1
max_value = 0.5
step = 0.01
value = 0.2
custom_minimum_size = Vector2(0, 0)
offset_left = 135.0
offset_top = 90.0
offset_right = 305.0
offset_bottom = 250.0
text = "display"
neutral_size = Vector2(170, 160)
desktop_anchor = Vector2(280, 180)
compact_anchor = Vector2(220, 150)
compact_minimum_size = Vector2(130, 124)
minimum_font_size = 18
maximum_font_size = 27
motion_phase = 0.35
[node name="AutoClickIntervalValue" type="Label" parent="Margin/Scroll/Content/AutoClickRow"]
[node name="ControlsCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "0.20 s"
custom_minimum_size = Vector2(0, 0)
offset_left = 377.0
offset_top = 96.0
offset_right = 533.0
offset_bottom = 244.0
text = "controls"
neutral_size = Vector2(156, 148)
desktop_anchor = Vector2(440, 180)
compact_anchor = Vector2(385, 150)
compact_minimum_size = Vector2(124, 118)
minimum_font_size = 17
maximum_font_size = 25
motion_phase = 1.45
[node name="MouseRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/MouseRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "mouse camera sensitivity"
autowrap_mode = 2
[node name="MouseSensitivity" type="HSlider" parent="Margin/Scroll/Content/MouseRow"]
[node name="AccessibilityCategory" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 0.001
max_value = 0.012
step = 0.0005
value = 0.005
custom_minimum_size = Vector2(0, 0)
offset_left = 240.0
offset_top = 262.0
offset_right = 420.0
offset_bottom = 428.0
text = "accessibility"
neutral_size = Vector2(180, 168)
desktop_anchor = Vector2(367, 328)
compact_anchor = Vector2(307, 311)
compact_minimum_size = Vector2(150, 142)
minimum_font_size = 17
maximum_font_size = 27
motion_phase = 2.65
[node name="MouseSensitivityValue" type="Label" parent="Margin/Scroll/Content/MouseRow"]
[node name="ApplySettingsButton" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "0.0050"
[node name="ControllerRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/ControllerRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "controller camera sensitivity"
autowrap_mode = 2
[node name="ControllerSensitivity" type="HSlider" parent="Margin/Scroll/Content/ControllerRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 0.5
max_value = 5.0
step = 0.1
value = 2.5
[node name="ControllerSensitivityValue" type="Label" parent="Margin/Scroll/Content/ControllerRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "2.5"
[node name="InvertYToggle" type="CheckButton" parent="Margin/Scroll/Content"]
unique_name_in_owner = true
layout_mode = 2
text = "invert vertical camera"
[node name="WorldPixelRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/WorldPixelRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "world pixelation"
autowrap_mode = 2
[node name="WorldPixelSize" type="HSlider" parent="Margin/Scroll/Content/WorldPixelRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 5.0
step = 1.0
value = 3.0
[node name="WorldPixelSizeValue" type="Label" parent="Margin/Scroll/Content/WorldPixelRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(170, 0)
layout_mode = 2
text = "retro"
[node name="UIPixelRow" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Label" type="Label" parent="Margin/Scroll/Content/UIPixelRow"]
custom_minimum_size = Vector2(260, 0)
layout_mode = 2
text = "ui pixelation"
autowrap_mode = 2
[node name="UIPixelSize" type="HSlider" parent="Margin/Scroll/Content/UIPixelRow"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 5.0
step = 1.0
value = 3.0
[node name="UIPixelSizeValue" type="Label" parent="Margin/Scroll/Content/UIPixelRow"]
unique_name_in_owner = true
custom_minimum_size = Vector2(170, 0)
layout_mode = 2
text = "retro"
[node name="SettingsFeedback" type="Label" parent="Margin/Scroll/Content"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
horizontal_alignment = 1
[node name="Buttons" type="HBoxContainer" parent="Margin/Scroll/Content"]
layout_mode = 2
theme_override_constants/separation = 12
alignment = 2
[node name="ApplySettingsButton" type="Button" parent="Margin/Scroll/Content/Buttons"]
unique_name_in_owner = true
custom_minimum_size = Vector2(150, 64)
layout_mode = 2
custom_minimum_size = Vector2(0, 0)
offset_left = 474.0
offset_top = 308.0
offset_right = 586.0
offset_bottom = 412.0
text = "apply"
neutral_size = Vector2(112, 104)
desktop_anchor = Vector2(511, 332)
compact_anchor = Vector2(470, 330)
compact_minimum_size = Vector2(94, 90)
minimum_font_size = 15
maximum_font_size = 19
motion_phase = 3.7
[node name="CancelSettingsButton" type="Button" parent="Margin/Scroll/Content/Buttons"]
[node name="RootBackButton" parent="RootPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(150, 64)
layout_mode = 2
custom_minimum_size = Vector2(0, 0)
offset_left = 70.0
offset_top = 343.0
offset_right = 170.0
offset_bottom = 437.0
text = "back"
neutral_size = Vector2(100, 94)
desktop_anchor = Vector2(220, 300)
compact_anchor = Vector2(155, 275)
compact_minimum_size = Vector2(84, 80)
minimum_font_size = 14
maximum_font_size = 17
motion_phase = 4.8
[node name="DisplayPage" type="Control" parent="."]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"display"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/DisplayBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/DisplayBackButton")])
initial_focus_path = NodePath("BubbleCluster/WorldRetro")
back_focus_path = NodePath("BubbleCluster/DisplayBackButton")
compact_maximum_layout_size = Vector2(544, 400)
[node name="BubbleCluster" type="Control" parent="DisplayPage"]
layout_mode = 0
offset_right = 720.0
offset_bottom = 520.0
script = ExtResource("5_cluster")
profile = ExtResource("6_profile")
desktop_reference_size = Vector2(720, 520)
compact_reference_size = Vector2(608, 448)
[node name="WorldValue" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
mouse_filter = 2
focus_mode = 0
text = "world\npixelation\nretro"
neutral_size = Vector2(160, 150)
desktop_anchor = Vector2(160, 240)
compact_anchor = Vector2(175, 210)
compact_minimum_size = Vector2(132, 126)
minimum_font_size = 15
maximum_font_size = 25
motion_phase = 0.2
[node name="WorldLegible" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "legible"
neutral_size = Vector2(90, 84)
desktop_anchor = Vector2(55, 175)
compact_anchor = Vector2(57, 135)
compact_minimum_size = Vector2(76, 72)
minimum_font_size = 12
maximum_font_size = 15
motion_phase = 0.8
[node name="WorldCute" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "cute"
neutral_size = Vector2(82, 78)
desktop_anchor = Vector2(130, 124)
compact_anchor = Vector2(145, 78)
compact_minimum_size = Vector2(70, 68)
minimum_font_size = 12
maximum_font_size = 14
motion_phase = 1.35
[node name="WorldRetro" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
button_pressed = true
text = "retro"
neutral_size = Vector2(84, 80)
desktop_anchor = Vector2(237, 149)
compact_anchor = Vector2(272, 115)
compact_minimum_size = Vector2(72, 68)
minimum_font_size = 12
maximum_font_size = 14
motion_phase = 1.9
[node name="WorldHardcore" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "hardcore"
neutral_size = Vector2(100, 94)
desktop_anchor = Vector2(288, 240)
compact_anchor = Vector2(70, 310)
compact_minimum_size = Vector2(88, 84)
minimum_font_size = 12
maximum_font_size = 16
motion_phase = 2.45
[node name="WorldWtf" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "wtf"
neutral_size = Vector2(76, 72)
desktop_anchor = Vector2(225, 337)
compact_anchor = Vector2(284, 284)
compact_minimum_size = Vector2(66, 64)
minimum_font_size = 12
maximum_font_size = 13
motion_phase = 3.0
[node name="UIValue" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
mouse_filter = 2
focus_mode = 0
text = "ui\npixelation\nretro"
neutral_size = Vector2(160, 150)
desktop_anchor = Vector2(560, 240)
compact_anchor = Vector2(545, 210)
compact_minimum_size = Vector2(132, 126)
minimum_font_size = 15
maximum_font_size = 25
motion_phase = 3.55
[node name="UILegible" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "legible"
neutral_size = Vector2(90, 84)
desktop_anchor = Vector2(665, 175)
compact_anchor = Vector2(663, 135)
compact_minimum_size = Vector2(76, 72)
minimum_font_size = 12
maximum_font_size = 15
motion_phase = 4.1
[node name="UICute" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "cute"
neutral_size = Vector2(82, 78)
desktop_anchor = Vector2(590, 124)
compact_anchor = Vector2(575, 78)
compact_minimum_size = Vector2(70, 68)
minimum_font_size = 12
maximum_font_size = 14
motion_phase = 4.65
[node name="UIRetro" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
button_pressed = true
text = "retro"
neutral_size = Vector2(84, 80)
desktop_anchor = Vector2(483, 149)
compact_anchor = Vector2(448, 115)
compact_minimum_size = Vector2(72, 68)
minimum_font_size = 12
maximum_font_size = 14
motion_phase = 5.2
[node name="UIHardcore" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "hardcore"
neutral_size = Vector2(100, 94)
desktop_anchor = Vector2(432, 240)
compact_anchor = Vector2(650, 310)
compact_minimum_size = Vector2(88, 84)
minimum_font_size = 12
maximum_font_size = 16
motion_phase = 5.75
[node name="UIWtf" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
toggle_mode = true
text = "wtf"
neutral_size = Vector2(76, 72)
desktop_anchor = Vector2(495, 337)
compact_anchor = Vector2(436, 284)
compact_minimum_size = Vector2(66, 64)
minimum_font_size = 12
maximum_font_size = 13
motion_phase = 0.55
[node name="DisplayBackButton" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "back"
neutral_size = Vector2(96, 90)
desktop_anchor = Vector2(350, 445)
compact_anchor = Vector2(315, 390)
compact_minimum_size = Vector2(82, 78)
minimum_font_size = 13
maximum_font_size = 16
motion_phase = 1.1
[node name="ControlsPage" type="Control" parent="."]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"controls"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/MouseDecrease"), NodePath("BubbleCluster/MouseValue"), NodePath("BubbleCluster/MouseIncrease"), NodePath("BubbleCluster/ControllerDecrease"), NodePath("BubbleCluster/ControllerValue"), NodePath("BubbleCluster/ControllerIncrease"), NodePath("BubbleCluster/InvertYToggle"), NodePath("BubbleCluster/ControlsBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/MouseDecrease"), NodePath("BubbleCluster/MouseValue"), NodePath("BubbleCluster/MouseIncrease"), NodePath("BubbleCluster/ControllerDecrease"), NodePath("BubbleCluster/ControllerValue"), NodePath("BubbleCluster/ControllerIncrease"), NodePath("BubbleCluster/InvertYToggle"), NodePath("BubbleCluster/ControlsBackButton")])
initial_focus_path = NodePath("BubbleCluster/MouseValue")
back_focus_path = NodePath("BubbleCluster/ControlsBackButton")
[node name="BubbleCluster" type="Control" parent="ControlsPage"]
layout_mode = 0
offset_right = 720.0
offset_bottom = 520.0
script = ExtResource("5_cluster")
profile = ExtResource("6_profile")
desktop_reference_size = Vector2(720, 520)
compact_reference_size = Vector2(608, 448)
[node name="MouseDecrease" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = ""
neutral_size = Vector2(80, 76)
desktop_anchor = Vector2(75, 140)
compact_anchor = Vector2(49, 125)
compact_minimum_size = Vector2(68, 66)
minimum_font_size = 17
maximum_font_size = 24
motion_phase = 0.3
[node name="MouseValue" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "mouse\nsensitivity\n0.0050"
neutral_size = Vector2(190, 180)
desktop_anchor = Vector2(210, 140)
compact_anchor = Vector2(200, 125)
compact_minimum_size = Vector2(160, 152)
minimum_font_size = 15
maximum_font_size = 25
motion_phase = 1.0
[node name="MouseIncrease" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "+"
neutral_size = Vector2(80, 76)
desktop_anchor = Vector2(345, 140)
compact_anchor = Vector2(351, 125)
compact_minimum_size = Vector2(68, 66)
minimum_font_size = 17
maximum_font_size = 24
motion_phase = 1.7
[node name="ControllerDecrease" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = ""
neutral_size = Vector2(80, 76)
desktop_anchor = Vector2(75, 365)
compact_anchor = Vector2(49, 340)
compact_minimum_size = Vector2(68, 66)
minimum_font_size = 17
maximum_font_size = 24
motion_phase = 2.4
[node name="ControllerValue" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "controller\nsensitivity\n2.5"
neutral_size = Vector2(190, 180)
desktop_anchor = Vector2(210, 365)
compact_anchor = Vector2(200, 340)
compact_minimum_size = Vector2(160, 152)
minimum_font_size = 15
maximum_font_size = 25
motion_phase = 3.1
[node name="ControllerIncrease" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "+"
neutral_size = Vector2(80, 76)
desktop_anchor = Vector2(345, 365)
compact_anchor = Vector2(351, 340)
compact_minimum_size = Vector2(68, 66)
minimum_font_size = 17
maximum_font_size = 24
motion_phase = 3.8
[node name="InvertYToggle" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "invert\nvertical\ncamera\noff"
neutral_size = Vector2(176, 170)
desktop_anchor = Vector2(525, 230)
compact_anchor = Vector2(485, 225)
compact_minimum_size = Vector2(170, 164)
minimum_font_size = 14
maximum_font_size = 24
motion_phase = 4.5
[node name="ControlsBackButton" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "back"
neutral_size = Vector2(100, 94)
desktop_anchor = Vector2(525, 425)
compact_anchor = Vector2(540, 395)
compact_minimum_size = Vector2(84, 80)
minimum_font_size = 14
maximum_font_size = 17
motion_phase = 5.2
[node name="AccessibilityPage" type="Control" parent="."]
unique_name_in_owner = true
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"accessibility"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/AutoClickToggle"), NodePath("BubbleCluster/IntervalDecrease"), NodePath("BubbleCluster/AutoClickIntervalValue"), NodePath("BubbleCluster/IntervalIncrease"), NodePath("BubbleCluster/IntervalHelp"), NodePath("BubbleCluster/AccessibilityBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/AutoClickToggle"), NodePath("BubbleCluster/IntervalDecrease"), NodePath("BubbleCluster/AutoClickIntervalValue"), NodePath("BubbleCluster/IntervalIncrease"), NodePath("BubbleCluster/AccessibilityBackButton")])
initial_focus_path = NodePath("BubbleCluster/AutoClickToggle")
back_focus_path = NodePath("BubbleCluster/AccessibilityBackButton")
[node name="BubbleCluster" type="Control" parent="AccessibilityPage"]
layout_mode = 0
offset_right = 720.0
offset_bottom = 520.0
script = ExtResource("5_cluster")
profile = ExtResource("6_profile")
desktop_reference_size = Vector2(720, 520)
compact_reference_size = Vector2(608, 448)
[node name="AutoClickToggle" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "accessibility\nauto-click\noff"
neutral_size = Vector2(190, 180)
desktop_anchor = Vector2(200, 195)
compact_anchor = Vector2(129, 185)
compact_minimum_size = Vector2(160, 152)
minimum_font_size = 14
maximum_font_size = 27
motion_phase = 0.5
[node name="IntervalDecrease" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = ""
neutral_size = Vector2(80, 76)
desktop_anchor = Vector2(340, 210)
compact_anchor = Vector2(280, 205)
compact_minimum_size = Vector2(68, 66)
minimum_font_size = 17
maximum_font_size = 24
motion_phase = 1.4
[node name="AutoClickIntervalValue" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "auto-click\ninterval\n0.20 s"
neutral_size = Vector2(190, 180)
desktop_anchor = Vector2(475, 210)
compact_anchor = Vector2(431, 205)
compact_minimum_size = Vector2(160, 152)
minimum_font_size = 14
maximum_font_size = 26
motion_phase = 2.3
[node name="IntervalIncrease" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "+"
neutral_size = Vector2(80, 76)
desktop_anchor = Vector2(610, 210)
compact_anchor = Vector2(582, 205)
compact_minimum_size = Vector2(68, 66)
minimum_font_size = 17
maximum_font_size = 24
motion_phase = 3.2
[node name="IntervalHelp" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
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)
desktop_anchor = Vector2(315, 405)
compact_anchor = Vector2(280, 370)
compact_minimum_size = Vector2(160, 152)
minimum_font_size = 13
maximum_font_size = 22
motion_phase = 4.1
[node name="AccessibilityBackButton" parent="AccessibilityPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "back"
neutral_size = Vector2(96, 90)
desktop_anchor = Vector2(560, 420)
compact_anchor = Vector2(520, 380)
compact_minimum_size = Vector2(82, 78)
minimum_font_size = 13
maximum_font_size = 16
motion_phase = 5.0

View file

@ -64,6 +64,13 @@ const START_PROMPT_MIN_SCALE: float = 0.985
const START_PROMPT_MAX_SCALE: float = 1.015
const START_PROMPT_CYCLE_SECONDS: float = 3.0
const DISABLED_BUBBLE_LABEL_ALPHA: float = 0.55
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 gameplay_requested
signal quit_requested
@ -93,7 +100,16 @@ enum ConfirmationAction {
@onready var _decorative_bubble_event_timer: Timer = %DecorativeBubbleEventTimer
@onready var _decorative_bubble_cluster_timer: Timer = %DecorativeBubbleClusterTimer
@onready var _title_logo: TextureRect = %TitleLogo
@onready var _playtest_label: Label = %PlaytestLabel
@onready var _intro_branding_anchor: Control = %IntroBrandingAnchor
@onready var _presentation_center: CenterContainer = $Center
@onready var _main_content: VBoxContainer = $Center/MainContent
@onready var _branding_slot: Control = $Center/MainContent/BrandingSlot
@onready var _branding_motion_root: Control = %BrandingMotionRoot
@onready var _title_spacer: Control = $Center/MainContent/Spacer
@onready var _button_center: CenterContainer = %ButtonCenter
@onready var _bubble_layout_slot: Control = $Center/MainContent/ButtonCenter/BubbleLayoutSlot
@onready var _bubble_motion_root: Control = %BubbleMotionRoot
@onready var _bubble_field: BubbleClusterType = %BubbleField
@onready var _start_prompt_center: CenterContainer = %StartPromptCenter
@onready var _start_prompt_label: Label = %StartPromptLabel
@ -117,10 +133,39 @@ var _start_prompt_elapsed: float = 0.0
var _navigation_focus_active: bool = false
var _modal_restore_navigation_focus: bool = false
var _world_pixel_size: int = PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE
var _title_settings_transition: Tween
var _title_settings_transition_generation: int = 0
var _title_settings_transition_active: bool = false
var _title_bubble_rest_position: Vector2 = Vector2.ZERO
var _title_content_rest_position: Vector2 = Vector2.ZERO
var _title_entry_transition: Tween
var _title_entry_generation: int = 0
var _title_entry_transition_active: bool = false
var _intro_geometry_ready: bool = false
var _intro_layout_generation: int = 0
var _branding_intro_position: Vector2 = Vector2.ZERO
var _bubble_intro_position: Vector2 = Vector2.ZERO
var _feedback_visible_before_settings: bool = false
var _feedback_text_before_settings: String = ""
var _continue_stats_hovered: bool = false
var _continue_stats_focused: bool = false
var _continue_stats_fade: Tween
func _ready() -> void:
_continue_button.pressed.connect(_on_continue_pressed)
_continue_button.mouse_entered.connect(
_on_continue_stats_hover_changed.bind(true)
)
_continue_button.mouse_exited.connect(
_on_continue_stats_hover_changed.bind(false)
)
_continue_button.focus_entered.connect(
_on_continue_stats_focus_changed.bind(true)
)
_continue_button.focus_exited.connect(
_on_continue_stats_focus_changed.bind(false)
)
_new_game_button.pressed.connect(_on_new_game_pressed)
_settings_button.pressed.connect(_open_settings)
_delete_button.pressed.connect(_on_delete_pressed)
@ -129,6 +174,7 @@ func _ready() -> void:
%CancelConfirmButton.pressed.connect(_close_confirmation)
_settings_panel.applied.connect(_on_settings_applied)
_settings_panel.closed.connect(_on_settings_closed)
_settings_panel.opened.connect(_on_settings_opened)
_bubble_field.configure(_get_title_buttons())
_decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout)
_decorative_bubble_event_timer.timeout.connect(
@ -144,6 +190,8 @@ func _ready() -> void:
set_process(false)
call_deferred("_update_title_layout")
call_deferred("_update_start_prompt_pivot")
call_deferred("_capture_title_bubble_rest_position")
call_deferred("_capture_title_content_rest_position")
func setup(
@ -161,6 +209,8 @@ func setup(
func reopen() -> void:
_cancel_title_entry_transition()
_cancel_title_settings_transition()
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_prepare_awaiting_start_input()
_close_confirmation()
@ -213,6 +263,17 @@ func _update_title_layout() -> void:
logo_width,
logo_width / LOGO_ASPECT_RATIO
)
var branding_separation: float = float(
_main_content.get_theme_constant("separation")
)
var branding_size := Vector2(
logo_width,
logo_width / LOGO_ASPECT_RATIO
+ branding_separation
+ _playtest_label.get_combined_minimum_size().y
)
_branding_slot.custom_minimum_size = branding_size
_branding_motion_root.size = branding_size
var field_width: float = minf(BUBBLE_FIELD_MAX_WIDTH, available_width)
var field_height: float = (
BUBBLE_FIELD_COMPACT_HEIGHT
@ -221,16 +282,22 @@ func _update_title_layout() -> void:
)
if size.y < BUBBLE_COMPACT_HEIGHT_THRESHOLD:
field_width = minf(BUBBLE_FIELD_COMPACT_WIDTH, available_width)
_bubble_layout_slot.custom_minimum_size = Vector2(field_width, field_height)
_bubble_motion_root.size = Vector2(field_width, field_height)
_bubble_field.custom_minimum_size = Vector2(field_width, field_height)
var compact_layout: bool = field_height == BUBBLE_FIELD_COMPACT_HEIGHT
_bubble_field.apply_layout(
Vector2(field_width, field_height),
compact_layout
)
if not _title_settings_transition_active:
call_deferred("_capture_title_bubble_rest_position")
_new_game_label.text = "new\ngame" if compact_layout else "new game"
_delete_save_label.text = (
"delete\nsave" if compact_layout else "delete save"
)
if _awaiting_start_input and not _title_entry_transition_active:
_schedule_intro_presentation()
_update_world_preview_resolution()
@ -311,16 +378,22 @@ func _process(delta: float) -> void:
pulse_weight
)
_start_prompt_label.scale = Vector2.ONE * prompt_scale
if _button_center.visible:
if _main_content.visible and _button_center.visible:
_bubble_field.advance_motion(delta)
func _input(event: InputEvent) -> void:
if not visible:
return
if _title_settings_transition_active or _title_entry_transition_active:
get_viewport().set_input_as_handled()
return
if _awaiting_start_input:
if not _is_start_prompt_reveal_event(event):
return
if not _intro_geometry_ready:
get_viewport().set_input_as_handled()
return
_reveal_primary_menu()
get_viewport().set_input_as_handled()
return
@ -332,7 +405,7 @@ func _input(event: InputEvent) -> void:
if _confirmation_panel.visible:
_close_confirmation()
elif _settings_panel.visible:
_close_settings()
_settings_panel.handle_back()
else:
return
get_viewport().set_input_as_handled()
@ -359,6 +432,7 @@ func _handle_primary_menu_focus_input(event: InputEvent) -> bool:
if event is InputEventMouseMotion:
_navigation_focus_active = false
_release_primary_menu_focus()
_update_continue_stats_visibility()
return false
if event is InputEventKey and (event as InputEventKey).echo:
return false
@ -409,15 +483,25 @@ func _begin_title_entry() -> void:
func _prepare_awaiting_start_input() -> void:
_cancel_title_entry_transition()
_awaiting_start_input = true
_intro_geometry_ready = false
_navigation_focus_active = false
_modal_restore_navigation_focus = false
_button_center.hide()
_feedback_label.hide()
_button_center.show()
_feedback_label.show()
_feedback_label.modulate.a = 0.0
_continue_stats_hovered = false
_continue_stats_focused = false
_cancel_continue_stats_fade()
_main_content.show()
_start_prompt_center.show()
_start_prompt_center.modulate.a = 1.0
_set_title_bubbles_interactive(false)
var focus_owner: Control = get_viewport().gui_get_focus_owner()
if focus_owner != null and is_ancestor_of(focus_owner):
focus_owner.release_focus()
_schedule_intro_presentation()
func _start_entry_prompt_animation() -> void:
@ -440,16 +524,240 @@ func _update_start_prompt_pivot() -> void:
func _reveal_primary_menu() -> void:
if not _awaiting_start_input:
if (
not _awaiting_start_input
or not _intro_geometry_ready
or _title_entry_transition_active
):
return
_awaiting_start_input = false
_stop_entry_prompt_animation()
_start_prompt_center.hide()
_button_center.show()
_feedback_label.show()
_title_entry_generation += 1
_title_entry_transition_active = true
_set_title_bubbles_interactive(false)
var generation: int = _title_entry_generation
set_process(true)
_navigation_focus_active = false
_release_title_focus()
_title_entry_transition = create_tween()
_title_entry_transition.set_parallel(true)
_title_entry_transition.tween_property(
_start_prompt_center,
"modulate:a",
0.0,
INTRO_PROMPT_FADE_DURATION
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
_title_entry_transition.tween_property(
_bubble_motion_root,
"position",
Vector2.ZERO,
INTRO_BUBBLE_TRAVEL_DURATION
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_title_entry_transition.tween_property(
_branding_motion_root,
"position",
Vector2.ZERO,
INTRO_BRANDING_TRAVEL_DURATION
).set_delay(INTRO_BRANDING_START_DELAY).set_trans(
Tween.TRANS_QUAD
).set_ease(Tween.EASE_IN_OUT)
_title_entry_transition.finished.connect(
_finish_primary_menu_reveal.bind(generation),
CONNECT_ONE_SHOT
)
func _finish_primary_menu_reveal(generation: int) -> void:
if (
generation != _title_entry_generation
or not _title_entry_transition_active
):
return
_title_entry_transition = null
_title_entry_transition_active = false
_start_prompt_center.hide()
_start_prompt_center.modulate.a = 1.0
_feedback_label.modulate.a = 0.0
_set_title_bubbles_interactive(true)
func _cancel_title_entry_transition() -> void:
_title_entry_generation += 1
if _title_entry_transition != null:
_title_entry_transition.kill()
_title_entry_transition = null
_title_entry_transition_active = false
if is_node_ready():
_start_prompt_center.modulate.a = 1.0
func _schedule_intro_presentation() -> void:
_intro_layout_generation += 1
_intro_geometry_ready = false
call_deferred(
"_prepare_intro_presentation",
_intro_layout_generation
)
func _prepare_intro_presentation(generation: int) -> void:
await get_tree().process_frame
await get_tree().process_frame
if (
generation != _intro_layout_generation
or not is_node_ready()
or not _awaiting_start_input
or _title_entry_transition_active
):
return
_branding_motion_root.size = _branding_slot.size
_bubble_motion_root.size = _bubble_layout_slot.size
var intro_tail_height: float = (
_title_spacer.get_combined_minimum_size().y
+ float(_main_content.get_theme_constant("separation"))
)
var branding_target_global := Vector2(
floorf(
_intro_branding_anchor.global_position.x
- _branding_motion_root.size.x * 0.5
),
floorf(
_intro_branding_anchor.global_position.y
- (_branding_motion_root.size.y + intro_tail_height) * 0.5
)
)
_branding_intro_position = (
branding_target_global
- _branding_slot.global_position
)
var bubble_bounds: Rect2 = _get_title_bubble_global_bounds()
bubble_bounds.position -= _bubble_motion_root.position
var presentation_allowance: float = _get_bubble_offscreen_allowance()
_bubble_intro_position = Vector2(
0.0,
size.y
+ presentation_allowance
- bubble_bounds.position.y
)
_branding_motion_root.position = _branding_intro_position
_bubble_motion_root.position = _bubble_intro_position
_title_content_rest_position = _presentation_center.position
_title_bubble_rest_position = _bubble_field.position
_intro_geometry_ready = true
func _get_title_bubble_global_bounds() -> Rect2:
var bubbles: Array[BubbleButton] = _get_title_buttons()
var bounds: Rect2 = bubbles[0].get_global_rect()
for index: int in range(1, bubbles.size()):
bounds = bounds.merge(bubbles[index].get_global_rect())
return bounds
func _get_bubble_offscreen_allowance() -> float:
var maximum_vertical_amplitude: float = 0.0
var maximum_deformation_growth: float = 0.0
for bubble: BubbleButton in _get_title_buttons():
maximum_vertical_amplitude = maxf(
maximum_vertical_amplitude,
bubble.vertical_amplitude
)
var maximum_scale: float = (
1.0 + bubble.deformation_amplitude
) * _bubble_field.profile.hover_focus_scale
maximum_deformation_growth = maxf(
maximum_deformation_growth,
bubble.presented_size.y * (maximum_scale - 1.0) * 0.5
)
return (
HOST_CLUSTER_SAFE_MARGIN
+ maximum_vertical_amplitude
+ maximum_deformation_growth
+ _bubble_field.profile.hover_focus_lift
+ _bubble_field.profile.maximum_separation
)
func _on_continue_stats_hover_changed(hovered: bool) -> void:
_continue_stats_hovered = hovered
_update_continue_stats_visibility()
func _on_continue_stats_focus_changed(focused: bool) -> void:
_continue_stats_focused = focused
_update_continue_stats_visibility()
func _update_continue_stats_visibility() -> void:
if not is_node_ready():
return
var requested_visible: bool = (
_continue_stats_hovered
or (
_continue_stats_focused
and _navigation_focus_active
)
)
requested_visible = (
requested_visible
and not _continue_button.disabled
and _inspection != null
and _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED
and not _awaiting_start_input
and not _title_entry_transition_active
and not _title_settings_transition_active
and not _settings_panel.visible
and not _confirmation_panel.visible
and not _action_in_progress
and _presentation_center.visible
)
if requested_visible:
_feedback_label.text = _get_continue_stats_text()
_fade_continue_stats_to(1.0 if requested_visible else 0.0)
func _hide_continue_stats_context() -> void:
_continue_stats_hovered = false
_continue_stats_focused = false
_fade_continue_stats_to(0.0)
func _fade_continue_stats_to(target_opacity: float) -> void:
if not is_node_ready():
return
_cancel_continue_stats_fade()
if is_equal_approx(_feedback_label.modulate.a, target_opacity):
_feedback_label.modulate.a = target_opacity
return
_continue_stats_fade = create_tween()
_continue_stats_fade.tween_property(
_feedback_label,
"modulate:a",
target_opacity,
INTRO_PROMPT_FADE_DURATION
).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
func _cancel_continue_stats_fade() -> void:
if _continue_stats_fade != null:
_continue_stats_fade.kill()
_continue_stats_fade = null
func _get_continue_stats_text() -> String:
if (
_inspection == null
or _inspection.status != SaveInspectionType.Status.VALID_SUPPORTED
):
return ""
return (
"%d fish • $%d%d discovered"
% [
_inspection.catch_count,
_inspection.wallet_balance,
_inspection.discovered_species_count,
]
)
func _on_continue_pressed() -> void:
@ -461,6 +769,7 @@ func _on_continue_pressed() -> void:
or not _inspection.can_continue()
):
return
_hide_continue_stats_context()
_action_in_progress = true
if _save_manager.load_player_data():
_feedback_label.text = "save loaded."
@ -479,6 +788,7 @@ func _on_new_game_pressed() -> void:
or _inspection == null
):
return
_hide_continue_stats_context()
if _inspection.status == SaveInspectionType.Status.MISSING:
if _save_manager.initialize_new_game():
gameplay_requested.emit()
@ -508,6 +818,7 @@ func _on_delete_pressed() -> void:
or not _inspection.can_delete()
):
return
_hide_continue_stats_context()
_open_confirmation(
ConfirmationAction.DELETE_SAVE,
"delete your saved progression? this cannot be undone.",
@ -540,6 +851,7 @@ func _open_confirmation(
message: String,
confirm_text: String,
) -> void:
_hide_continue_stats_context()
_modal_restore_navigation_focus = _navigation_focus_active
_confirmation_action = action
_confirmation_text.text = message
@ -563,11 +875,18 @@ func _close_confirmation() -> void:
func _open_settings() -> void:
if _confirmation_panel.visible or _action_in_progress:
if (
_confirmation_panel.visible
or _action_in_progress
or _settings_panel.visible
or _title_settings_transition_active
):
return
_hide_continue_stats_context()
_modal_restore_navigation_focus = _navigation_focus_active
_feedback_label.text = ""
_settings_panel.open_panel(_settings_manager)
_feedback_visible_before_settings = _feedback_label.visible
_feedback_text_before_settings = _feedback_label.text
_begin_title_cluster_exit()
func _close_settings() -> void:
@ -575,12 +894,158 @@ func _close_settings() -> void:
func _on_settings_applied() -> void:
_feedback_label.text = "settings saved."
_restore_settings_focus()
_begin_title_cluster_return("settings saved.")
func _on_settings_closed() -> void:
_begin_title_cluster_return(_feedback_text_before_settings)
func _on_settings_opened() -> void:
_title_settings_transition_active = false
func _begin_title_cluster_exit() -> void:
_title_settings_transition_generation += 1
_cancel_title_settings_tween()
_title_settings_transition_active = true
_title_content_rest_position = _presentation_center.position
_title_bubble_rest_position = _bubble_field.position
_set_title_bubbles_interactive(false)
_release_primary_menu_focus()
var generation: int = _title_settings_transition_generation
var exit_distance: float = (
_main_content.global_position.y
+ _main_content.size.y
+ HOST_CLUSTER_SAFE_MARGIN
)
_title_settings_transition = create_tween()
_title_settings_transition.tween_property(
_presentation_center,
"position:y",
_title_content_rest_position.y - exit_distance,
HOST_PRESENTATION_OUT_DURATION
).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
)
func _finish_title_cluster_exit(generation: int) -> void:
if (
generation != _title_settings_transition_generation
or not _title_settings_transition_active
):
return
_title_settings_transition = null
_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:
_title_settings_transition_generation += 1
_cancel_title_settings_tween()
_title_settings_transition_active = true
if _feedback_label.text != feedback_text:
_feedback_label.text = feedback_text
_feedback_label.visible = _feedback_visible_before_settings
_hide_continue_stats_context()
_set_title_bubbles_interactive(false)
var generation: int = _title_settings_transition_generation
var rest_global_y: float = (
_presentation_center.global_position.y
- _presentation_center.position.y
+ _title_content_rest_position.y
)
_presentation_center.position = Vector2(
_title_content_rest_position.x,
_title_content_rest_position.y
+ size.y
+ HOST_CLUSTER_SAFE_MARGIN
- rest_global_y
)
_presentation_center.show()
_title_settings_transition = create_tween()
_title_settings_transition.tween_property(
_presentation_center,
"position",
_title_content_rest_position,
HOST_PRESENTATION_IN_DURATION
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
_title_settings_transition.finished.connect(
_finish_title_cluster_return.bind(generation),
CONNECT_ONE_SHOT
)
func _finish_title_cluster_return(generation: int) -> void:
if (
generation != _title_settings_transition_generation
or not _title_settings_transition_active
):
return
_title_settings_transition = null
_title_settings_transition_active = false
_presentation_center.position = _title_content_rest_position
_bubble_field.position = _title_bubble_rest_position
_set_title_bubbles_interactive(true)
_restore_settings_focus()
_update_continue_stats_visibility()
func _set_title_bubbles_interactive(interactive: bool) -> void:
for bubble: BubbleButton in _get_title_buttons():
bubble.focus_mode = (
Control.FOCUS_ALL if interactive else Control.FOCUS_NONE
)
bubble.mouse_filter = (
Control.MOUSE_FILTER_STOP
if interactive
else Control.MOUSE_FILTER_IGNORE
)
func _capture_title_bubble_rest_position() -> void:
if (
not is_node_ready()
or _title_settings_transition_active
or not _button_center.visible
):
return
_title_bubble_rest_position = _bubble_field.position
func _capture_title_content_rest_position() -> void:
if (
not is_node_ready()
or _title_settings_transition_active
or _title_entry_transition_active
or not _button_center.visible
):
return
_title_content_rest_position = _presentation_center.position
func _cancel_title_settings_transition() -> void:
_title_settings_transition_generation += 1
_cancel_title_settings_tween()
_title_settings_transition_active = false
if is_node_ready():
_presentation_center.position = _title_content_rest_position
_presentation_center.show()
_bubble_field.position = _title_bubble_rest_position
_set_title_bubbles_interactive(true)
func _cancel_title_settings_tween() -> void:
if _title_settings_transition != null:
_title_settings_transition.kill()
_title_settings_transition = null
func _restore_settings_focus() -> void:
@ -603,14 +1068,11 @@ func _refresh_save_inspection() -> void:
)
_feedback_label.text = _inspection.message
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
_feedback_label.text = (
"%d fish • $%d%d discovered"
% [
_inspection.catch_count,
_inspection.wallet_balance,
_inspection.discovered_species_count,
]
)
_feedback_label.text = _get_continue_stats_text()
if _continue_button.disabled:
_hide_continue_stats_context()
else:
_update_continue_stats_visibility()
func _focus_initial_button() -> void:
@ -626,6 +1088,7 @@ func _focus_initial_button() -> void:
else:
_new_game_button.grab_focus()
_navigation_focus_active = true
_update_continue_stats_visibility()
func _on_quit_pressed() -> void:
@ -634,6 +1097,7 @@ func _on_quit_pressed() -> void:
and not _confirmation_panel.visible
and not _settings_panel.visible
):
_hide_continue_stats_context()
quit_requested.emit()
@ -644,6 +1108,7 @@ func _on_title_visibility_changed() -> void:
set_process(true)
_start_decorative_presentation()
else:
_hide_continue_stats_context()
_stop_entry_prompt_animation()
_navigation_focus_active = false
_modal_restore_navigation_focus = false

View file

@ -101,6 +101,17 @@ text = "press any key to start"
horizontal_alignment = 1
vertical_alignment = 1
[node name="IntroBrandingAnchor" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 1
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="Center" type="CenterContainer" parent="."]
z_index = 10
layout_mode = 1
@ -115,7 +126,27 @@ layout_mode = 2
theme_override_constants/separation = 7
alignment = 1
[node name="TitleLogo" type="TextureRect" parent="Center/MainContent"]
[node name="BrandingSlot" type="Control" parent="Center/MainContent"]
custom_minimum_size = Vector2(640, 215)
layout_mode = 2
[node name="BrandingMotionRoot" type="Control" parent="Center/MainContent/BrandingSlot"]
unique_name_in_owner = true
layout_mode = 0
offset_right = 640.0
offset_bottom = 215.0
mouse_filter = 2
[node name="BrandingContent" type="VBoxContainer" parent="Center/MainContent/BrandingSlot/BrandingMotionRoot"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 7
[node name="TitleLogo" type="TextureRect" parent="Center/MainContent/BrandingSlot/BrandingMotionRoot/BrandingContent"]
unique_name_in_owner = true
custom_minimum_size = Vector2(640, 190)
material = SubResource("ShaderMaterial_title_logo")
@ -126,7 +157,8 @@ texture = ExtResource("5_logo")
expand_mode = 1
stretch_mode = 5
[node name="PlaytestLabel" type="Label" parent="Center/MainContent"]
[node name="PlaytestLabel" type="Label" parent="Center/MainContent/BrandingSlot/BrandingMotionRoot/BrandingContent"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
theme_override_font_sizes/font_size = 17
@ -142,14 +174,27 @@ unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="BubbleField" type="Control" parent="Center/MainContent/ButtonCenter"]
unique_name_in_owner = true
[node name="BubbleLayoutSlot" type="Control" parent="Center/MainContent/ButtonCenter"]
custom_minimum_size = Vector2(396, 318)
layout_mode = 2
[node name="BubbleMotionRoot" type="Control" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot"]
unique_name_in_owner = true
layout_mode = 0
offset_right = 396.0
offset_bottom = 318.0
mouse_filter = 2
[node name="BubbleField" type="Control" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot"]
unique_name_in_owner = true
custom_minimum_size = Vector2(396, 318)
layout_mode = 0
offset_right = 396.0
offset_bottom = 318.0
script = ExtResource("8_bubble_cluster")
profile = ExtResource("9_bubble_profile")
[node name="ContinueButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
[node name="ContinueButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 123.0
offset_top = 86.0
@ -169,7 +214,7 @@ motion_period = 5.8
deformation_amplitude = 0.01
deformation_period = 6.7
[node name="NewGameButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
[node name="NewGameButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 42.0
offset_top = 4.0
@ -191,7 +236,7 @@ motion_phase = 1.2
deformation_amplitude = 0.018
deformation_period = 5.9
[node name="NewGameLabel" type="Label" parent="Center/MainContent/ButtonCenter/BubbleField/NewGameButton"]
[node name="NewGameLabel" type="Label" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField/NewGameButton"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -206,7 +251,7 @@ text = "new game"
horizontal_alignment = 1
vertical_alignment = 1
[node name="SettingsButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
[node name="SettingsButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 264.0
offset_top = 17.0
@ -225,7 +270,7 @@ motion_period = 4.9
motion_phase = 2.5
deformation_period = 6.3
[node name="DeleteSaveButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
[node name="DeleteSaveButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = -11.0
offset_top = 122.0
@ -246,7 +291,7 @@ motion_phase = 3.7
deformation_amplitude = 0.018
deformation_period = 5.4
[node name="DeleteSaveLabel" type="Label" parent="Center/MainContent/ButtonCenter/BubbleField/DeleteSaveButton"]
[node name="DeleteSaveLabel" type="Label" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField/DeleteSaveButton"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
@ -261,7 +306,7 @@ text = "delete save"
horizontal_alignment = 1
vertical_alignment = 1
[node name="QuitButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleField"]
[node name="QuitButton" type="Button" parent="Center/MainContent/ButtonCenter/BubbleLayoutSlot/BubbleMotionRoot/BubbleField"]
unique_name_in_owner = true
offset_left = 275.0
offset_top = 217.0