netfishing/ui/settings_panel.gd

578 lines
16 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name SettingsPanel
extends Control
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)
signal navigation_transition_started
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 _display_name_edit: LineEdit = %DisplayNameEdit
@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 _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 _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
var _controller_sensitivity: float = 2.5
var _invert_camera_y: bool = false
var _network_profile: NetworkProfilePreferences
var _network_session: NetworkSession
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)
%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)
_display_name_edit.text_submitted.connect(
func(_value: String) -> void: _apply_display_name()
)
_display_name_edit.focus_exited.connect(_apply_display_name)
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 setup_network_profile(
profile: NetworkProfilePreferences,
session: NetworkSession,
) -> void:
_network_profile = profile
_network_session = session
if _network_profile != null:
_display_name_edit.text = _network_profile.display_name
func _apply_display_name() -> void:
if _network_profile == null:
return
var value := _display_name_edit.text.strip_edges()
var accepted := (
_network_session.update_local_display_name(value)
if _network_session != null
else _network_profile.set_display_name(value)
)
if accepted:
_display_name_edit.text = _network_profile.display_name
_feedback.text = "display name saved."
else:
_display_name_edit.text = _network_profile.display_name
_feedback.text = "display name must be 124 plain-text characters."
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
_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()
if _network_profile != null:
_display_name_edit.text = _network_profile.display_name
_feedback.text = ""
show()
_page_stack.clear()
_page_stack.append(PAGE_ROOT)
panel_visibility_changed.emit(true)
if (
presentation_mode == PresentationMode.TITLE_EMBEDDED
or _animate_gameplay_host_transitions
):
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_animated_open.bind(generation),
TITLE_HOST_INCOMING_DURATION
)
else:
_show_active_page(true)
opened.emit()
func close_panel(immediate: bool = false) -> void:
if not visible:
return
if immediate:
_finish_panel_close(false)
return
if (
_presentation_mode == PresentationMode.TITLE_EMBEDDED
or _animate_gameplay_host_transitions
):
if _page_transition_active:
return
_begin_animated_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
navigation_transition_started.emit()
var generation: int = _page_transition_generation
outgoing_page.transition_out(
_finish_outgoing_page.bind(
generation,
page_id,
push_page,
incoming_page
)
)
func _finish_outgoing_page(
generation: int,
page_id: StringName,
push_page: bool,
incoming_page: SettingsBubblePage,
) -> void:
if generation != _page_transition_generation or not _page_transition_active:
return
if push_page:
_page_stack.append(page_id)
else:
_page_stack.pop_back()
for page: SettingsBubblePage in _pages.values():
if page != incoming_page:
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_animated_open(generation: int) -> void:
if generation != _page_transition_generation or not _page_transition_active:
return
_page_transition_active = false
opened.emit()
func _begin_animated_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
navigation_transition_started.emit()
var generation: int = _page_transition_generation
active_page.transition_out(
_finish_animated_close.bind(generation, applied_result),
TITLE_HOST_OUTGOING_DURATION
)
func _finish_animated_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_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):
if (
_presentation_mode == PresentationMode.TITLE_EMBEDDED
or _animate_gameplay_host_transitions
):
_begin_animated_close(true)
else:
_finish_panel_close(true)
else:
_feedback.text = "failed to save settings."
func _load_controls() -> void:
if _settings_manager == null or not is_node_ready():
return
var settings: PlayerSettings = _settings_manager.current_settings
_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() -> 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_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 _set_world_pixelation(pixel_size: int) -> void:
if _settings_manager == null:
return
if not _settings_manager.apply_pixelation(
pixel_size,
_settings_manager.current_settings.ui_pixel_size
):
_feedback.text = "failed to save pixelation settings."
return
_refresh_value_labels()
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:
_effective_ui_pixel_size = clampi(
pixel_size,
PlayerSettings.MIN_UI_PIXEL_SIZE,
PlayerSettings.MAX_UI_PIXEL_SIZE
)
if is_node_ready():
_refresh_value_labels()
func focus_back_button() -> void:
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:
if visible:
_load_controls()
func _refresh_panel_size() -> void:
if not is_node_ready():
return
var parent_control := get_parent() as Control
if parent_control == null:
return
var available_size: Vector2 = parent_control.size - Vector2.ONE * (
PANEL_EDGE_MARGIN * 2.0
)
var target_size := Vector2(
minf(MAX_PANEL_SIZE.x, maxf(320.0, available_size.x)),
minf(MAX_PANEL_SIZE.y, maxf(240.0, available_size.y))
)
custom_minimum_size = target_size
if parent_control is CenterContainer:
return
set_anchors_preset(Control.PRESET_CENTER)
position = (parent_control.size - target_size) * 0.5
size = target_size