329 lines
9.5 KiB
GDScript
329 lines
9.5 KiB
GDScript
extends "res://src/ui/panel_window.gd"
|
||
## Unified settings panel — replaces the GTK control board.
|
||
## Sidebar navigation: General, Backend, Personality, Sounds, Animations, Camera.
|
||
|
||
const SidebarNavScript = preload("res://addons/godot-ui/core/sidebar_nav.gd")
|
||
const GeneralPageScript = preload("res://src/ui/settings_page_general.gd")
|
||
const BackendPageScript = preload("res://src/ui/settings_page_backend.gd")
|
||
const TtsPageScript = preload("res://src/ui/settings_page_tts.gd")
|
||
const SoundsPageScript = preload("res://src/ui/settings_page_sounds.gd")
|
||
const CameraPageScript = preload("res://src/ui/settings_page_camera.gd")
|
||
const GazeDemoPageScript = preload("res://src/ui/settings_page_gaze_demo.gd")
|
||
const PersonalityPageScript = preload("res://src/ui/settings_page_personality.gd")
|
||
const AnimationsPageScript = preload("res://src/ui/settings_page_animations.gd")
|
||
|
||
const _DEFAULT_SIZE := Vector2i(660, 520)
|
||
const _DEFAULT_MIN := Vector2i(620, 480)
|
||
const _CAMERA_SIZE := Vector2i(900, 580)
|
||
const _CAMERA_MIN := Vector2i(860, 480)
|
||
## Gaze Demo needs room for the 640×360 preview + status + diagram + halo toggle.
|
||
const _GAZE_DEMO_SIZE := Vector2i(900, 940)
|
||
const _GAZE_DEMO_MIN := Vector2i(860, 860)
|
||
|
||
var _companion: Node
|
||
var _sound_config: Node
|
||
var _sound_engine: Node
|
||
var _general_page: RefCounted
|
||
var _backend_page: RefCounted
|
||
var _tts_page: RefCounted
|
||
var _camera_page: RefCounted
|
||
var _gaze_demo_page: RefCounted
|
||
var _sidebar_nav: PanelContainer
|
||
|
||
var _pages: Dictionary = {}
|
||
var _active_tab: String = "general"
|
||
|
||
|
||
func setup(
|
||
companion: Node,
|
||
sound_config: Node,
|
||
sound_engine: Node,
|
||
) -> void:
|
||
_companion = companion
|
||
_sound_config = sound_config
|
||
_sound_engine = sound_engine
|
||
|
||
|
||
func _ready() -> void:
|
||
min_size = _DEFAULT_MIN
|
||
size = _DEFAULT_SIZE
|
||
super._ready()
|
||
_update_panel_size()
|
||
|
||
|
||
func _on_theme_changed() -> void:
|
||
if _camera_page != null:
|
||
_camera_page.cleanup()
|
||
if _gaze_demo_page != null:
|
||
_gaze_demo_page.cleanup()
|
||
super._on_theme_changed()
|
||
|
||
|
||
func _build_ui() -> void:
|
||
var bg := PanelContainer.new()
|
||
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
var bg_style := StyleBoxFlat.new()
|
||
bg_style.bg_color = UiTheme.bg_dark
|
||
bg_style.set_border_width_all(1)
|
||
bg_style.border_color = UiTheme.border
|
||
bg_style.set_corner_radius_all(10)
|
||
bg.add_theme_stylebox_override("panel", bg_style)
|
||
add_child(bg)
|
||
|
||
var root := VBoxContainer.new()
|
||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
root.add_theme_constant_override("separation", 0)
|
||
bg.add_child(root)
|
||
|
||
root.add_child(_build_title_bar("SETTINGS"))
|
||
root.add_child(_build_divider())
|
||
|
||
var body := HBoxContainer.new()
|
||
body.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
body.add_theme_constant_override("separation", 0)
|
||
root.add_child(body)
|
||
|
||
_sidebar_nav = SidebarNavScript.new()
|
||
var tabs: Array[Dictionary] = [
|
||
{"key": "general", "label": "General"},
|
||
{"key": "backend", "label": "Backend"},
|
||
{"key": "tts", "label": "Voice"},
|
||
{"key": "personality", "label": "Personality"},
|
||
{"key": "sounds", "label": "Sounds"},
|
||
{"key": "animations", "label": "Animations"},
|
||
{"key": "camera", "label": "Camera"},
|
||
{"key": "gaze_demo", "label": "Gaze Demo"},
|
||
]
|
||
_sidebar_nav.setup(tabs)
|
||
_sidebar_nav.tab_selected.connect(_switch_tab)
|
||
_sidebar_nav.set_active(_active_tab)
|
||
body.add_child(_sidebar_nav)
|
||
body.add_child(_sidebar_line())
|
||
body.add_child(_build_page_area())
|
||
|
||
|
||
func show_tab(tab_key: String) -> void:
|
||
_switch_tab(tab_key)
|
||
|
||
|
||
# -- Pages --------------------------------------------------------------------
|
||
|
||
|
||
func _build_page_area() -> Control:
|
||
var container := Control.new()
|
||
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
|
||
_pages["general"] = _build_general_page()
|
||
_pages["backend"] = _build_backend_page()
|
||
_pages["tts"] = _build_tts_page()
|
||
_pages["personality"] = _build_personality_page()
|
||
_pages["sounds"] = _build_sounds_page()
|
||
_pages["animations"] = _build_animations_page()
|
||
_pages["camera"] = _build_camera_page()
|
||
_pages["gaze_demo"] = _build_gaze_demo_page()
|
||
|
||
for key: String in _pages:
|
||
var page: Control = _pages[key]
|
||
page.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
page.visible = (key == _active_tab)
|
||
container.add_child(page)
|
||
|
||
return container
|
||
|
||
|
||
func _build_general_page() -> Control:
|
||
_general_page = GeneralPageScript.new()
|
||
_general_page.setup(_companion)
|
||
var ctrl: Control = _general_page.build()
|
||
|
||
_general_page.stt_toggle.toggled.connect(_on_stt)
|
||
_general_page.tts_toggle.toggled.connect(_on_tts)
|
||
_general_page.halo_toggle.toggled.connect(_on_halo_toggled)
|
||
_general_page.snap_toggle.toggled.connect(_on_snap_toggled)
|
||
_general_page.zoom_slider.value_changed.connect(_on_zoom_changed)
|
||
_general_page.reset_btn.pressed.connect(_on_reset_pressed)
|
||
return ctrl
|
||
|
||
|
||
func _build_backend_page() -> Control:
|
||
_backend_page = BackendPageScript.new()
|
||
var ctrl: Control = _backend_page.build()
|
||
|
||
_backend_page.speech_url_input.text_submitted.connect(_on_speech_url)
|
||
_backend_page.speech_url_input.input_focus_exited.connect(
|
||
func() -> void: _on_speech_url(_backend_page.speech_url_input.get_text())
|
||
)
|
||
_backend_page.llm_url_input.text_submitted.connect(_on_llm_url)
|
||
_backend_page.llm_url_input.input_focus_exited.connect(
|
||
func() -> void: _on_llm_url(_backend_page.llm_url_input.get_text())
|
||
)
|
||
_backend_page.llm_model_input.text_submitted.connect(_on_llm_model)
|
||
_backend_page.llm_model_input.input_focus_exited.connect(
|
||
func() -> void: _on_llm_model(_backend_page.llm_model_input.get_text())
|
||
)
|
||
_backend_page.temperature_spin.value_changed.connect(_on_temperature)
|
||
_backend_page.max_tokens_spin.value_changed.connect(_on_max_tokens)
|
||
_backend_page.auto_resume_toggle.toggled.connect(_on_auto_resume)
|
||
_backend_page.retry_btn.pressed.connect(_on_retry_services)
|
||
return ctrl
|
||
|
||
|
||
func _build_tts_page() -> Control:
|
||
_tts_page = TtsPageScript.new()
|
||
var ctrl: Control = _tts_page.build()
|
||
_tts_page.exaggeration_slider.value_changed.connect(_on_tts_exaggeration)
|
||
_tts_page.cfg_weight_slider.value_changed.connect(_on_tts_cfg_weight)
|
||
_tts_page.emotion_params_toggle.toggled.connect(_on_tts_emotion_params)
|
||
return ctrl
|
||
|
||
|
||
func _build_personality_page() -> Control:
|
||
var page := PersonalityPageScript.new()
|
||
return page.build()
|
||
|
||
|
||
func _build_sounds_page() -> Control:
|
||
var page := SoundsPageScript.new()
|
||
page.setup(_sound_config, _sound_engine)
|
||
return page.build()
|
||
|
||
|
||
func _build_animations_page() -> Control:
|
||
var page := AnimationsPageScript.new()
|
||
page.setup(_companion, _sound_config)
|
||
return page.build()
|
||
|
||
|
||
func _build_camera_page() -> Control:
|
||
_camera_page = CameraPageScript.new()
|
||
_camera_page.setup(_companion)
|
||
return _camera_page.build()
|
||
|
||
|
||
func _build_gaze_demo_page() -> Control:
|
||
_gaze_demo_page = GazeDemoPageScript.new()
|
||
_gaze_demo_page.setup(_companion)
|
||
return _gaze_demo_page.build()
|
||
|
||
|
||
# -- Tab switching ------------------------------------------------------------
|
||
|
||
|
||
func _switch_tab(tab_key: String) -> void:
|
||
if tab_key == _active_tab:
|
||
return
|
||
_active_tab = tab_key
|
||
_sidebar_nav.set_active(tab_key)
|
||
_update_panel_size()
|
||
for key: String in _pages:
|
||
_pages[key].visible = (key == tab_key)
|
||
|
||
|
||
func _update_panel_size() -> void:
|
||
if _active_tab == "gaze_demo":
|
||
min_size = _GAZE_DEMO_MIN
|
||
size = _GAZE_DEMO_SIZE
|
||
elif _active_tab == "camera":
|
||
min_size = _CAMERA_MIN
|
||
size = _CAMERA_SIZE
|
||
else:
|
||
min_size = _DEFAULT_MIN
|
||
size = _DEFAULT_SIZE
|
||
|
||
|
||
func _sidebar_line() -> Control:
|
||
var line := ColorRect.new()
|
||
line.color = UiTheme.border
|
||
line.custom_minimum_size.x = 1
|
||
line.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
return line
|
||
|
||
|
||
# -- Accessors ----------------------------------------------------------------
|
||
|
||
# -- Callbacks ----------------------------------------------------------------
|
||
|
||
|
||
func _on_stt(on: bool) -> void:
|
||
CompanionConfig.stt_enabled = on
|
||
|
||
|
||
func _on_tts(on: bool) -> void:
|
||
CompanionConfig.tts_enabled = on
|
||
|
||
|
||
func _on_halo_toggled(on: bool) -> void:
|
||
AppState.set_companion("gaze_halo", on)
|
||
EventBus.gaze_halo_toggled.emit(on)
|
||
|
||
|
||
func _on_snap_toggled(on: bool) -> void:
|
||
AppState.set_snap_enabled(on)
|
||
if _companion != null:
|
||
var snap: Node = _companion.get_node_or_null("EdgeSnap")
|
||
if snap != null:
|
||
snap.set("enabled", on)
|
||
|
||
|
||
func _on_zoom_changed(value: float) -> void:
|
||
if _companion != null:
|
||
var zoom := _companion.get_node_or_null("WindowZoom")
|
||
if zoom != null:
|
||
zoom.set_zoom_level(value)
|
||
if _general_page != null and _general_page.zoom_label != null:
|
||
_general_page.zoom_label.text = "%d%%" % int(value * 100)
|
||
|
||
|
||
func _on_reset_pressed() -> void:
|
||
if _companion == null:
|
||
return
|
||
if _companion.has_method("_set_default_position"):
|
||
_companion._set_default_position()
|
||
|
||
|
||
func _on_speech_url(url: String) -> void:
|
||
var trimmed := url.strip_edges()
|
||
if not trimmed.is_empty():
|
||
CompanionConfig.speech_url = trimmed
|
||
|
||
|
||
func _on_llm_url(url: String) -> void:
|
||
var trimmed := url.strip_edges()
|
||
if not trimmed.is_empty():
|
||
CompanionConfig.llm_url = trimmed
|
||
|
||
|
||
func _on_llm_model(model: String) -> void:
|
||
var trimmed := model.strip_edges()
|
||
if not trimmed.is_empty():
|
||
CompanionConfig.llm_model = trimmed
|
||
|
||
|
||
func _on_temperature(val: float) -> void:
|
||
CompanionConfig.llm_temperature = val
|
||
|
||
|
||
func _on_max_tokens(val: float) -> void:
|
||
CompanionConfig.llm_max_tokens = int(val)
|
||
|
||
|
||
func _on_auto_resume(on: bool) -> void:
|
||
CompanionConfig.auto_resume_conversation = on
|
||
|
||
|
||
func _on_tts_exaggeration(value: float) -> void:
|
||
CompanionConfig.tts_exaggeration = value
|
||
|
||
|
||
func _on_tts_cfg_weight(value: float) -> void:
|
||
CompanionConfig.tts_cfg_weight = value
|
||
|
||
|
||
func _on_tts_emotion_params(on: bool) -> void:
|
||
CompanionConfig.tts_use_emotion_params = on
|
||
|
||
|
||
func _on_retry_services() -> void:
|
||
if _companion != null:
|
||
_companion.retry_services()
|