Files
BABA_YAGA/Assets/Scripts/UI/SettingsController.cs

473 lines
26 KiB
C#
Raw Normal View History

2026-04-25 18:20:16 +07:00
using UnityEngine;
using UnityEngine.UIElements;
2026-04-30 20:58:59 +07:00
using UnityEngine.Audio;
2026-05-01 02:25:25 +07:00
using UnityEngine.InputSystem;
2026-04-29 01:04:28 +07:00
using System.Collections.Generic;
2026-04-30 20:58:59 +07:00
using System.Linq;
using System;
2026-04-28 00:07:42 +07:00
using System.Threading.Tasks;
2026-04-29 02:31:15 +07:00
using OnlyScove.Scripts;
2026-04-30 20:58:59 +07:00
using Hallucinate.Audio;
2026-04-30 21:46:37 +07:00
using PrimeTween;
2026-04-25 18:20:16 +07:00
2026-04-28 00:07:42 +07:00
namespace Hallucinate.UI
2026-04-25 18:20:16 +07:00
{
2026-04-28 00:07:42 +07:00
public class SettingsController : BaseUIController
2026-04-25 18:20:16 +07:00
{
2026-04-28 00:07:42 +07:00
private VisualElement _sidebar;
2026-05-01 16:51:08 +07:00
private VisualElement _tabsColumn;
2026-04-28 00:07:42 +07:00
private Label _tabTitle;
private ScrollView _content;
2026-04-29 01:04:28 +07:00
private Dictionary<string, Button> _tabButtons = new Dictionary<string, Button>();
private string _activeTab = "GENERAL";
2026-04-25 18:20:16 +07:00
2026-05-01 16:51:08 +07:00
private Tween _hoverTimer;
private bool _isExpanded;
2026-04-30 20:58:59 +07:00
// Advanced Mouse Metrics
private Label _mouseMetricsLabel;
// FPS State
private bool _fpsVisible;
// Hover Tracking for Arrow Key Slider Control
private Slider _hoveredSlider;
2026-04-30 21:46:37 +07:00
private Action<float> _hoveredOnChanged;
2026-04-30 20:58:59 +07:00
private float _sliderMin, _sliderMax;
// Osu-style Volume Overlay
2026-05-01 01:25:02 +07:00
private VisualElement _volumeContainer;
private VisualElement _masterRing;
2026-04-30 20:58:59 +07:00
private Label _masterVolLabel;
2026-05-01 01:25:02 +07:00
private Dictionary<string, (VisualElement ring, Label label)> _subRings = new Dictionary<string, (VisualElement, Label)>();
private string _hoveredSubVolume = null;
2026-04-30 20:58:59 +07:00
private float _masterVol = 80f;
2026-05-01 01:25:02 +07:00
private int _overlayActiveCount = 0;
2026-04-30 20:58:59 +07:00
2026-04-28 00:07:42 +07:00
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
2026-04-25 18:20:16 +07:00
{
2026-04-28 00:07:42 +07:00
base.Initialize(uxmlRoot, manager);
2026-04-25 18:20:16 +07:00
2026-04-28 00:07:42 +07:00
_sidebar = root.Q<VisualElement>("Sidebar");
2026-05-01 16:51:08 +07:00
_tabsColumn = root.Q<VisualElement>("TabsColumn");
2026-04-28 00:07:42 +07:00
_tabTitle = root.Q<Label>("TabTitle");
_content = root.Q<ScrollView>("SettingsContent");
2026-04-25 18:20:16 +07:00
2026-05-01 16:51:08 +07:00
// Smart Sidebar Hover Logic
_tabsColumn.RegisterCallback<PointerEnterEvent>(OnSidebarPointerEnter);
_tabsColumn.RegisterCallback<PointerLeaveEvent>(OnSidebarPointerLeave);
// Global Volume Catch
2026-05-01 01:25:02 +07:00
uiManager.Root.RegisterCallback<WheelEvent>(OnMouseWheel, TrickleDown.TrickleDown);
SetupHierarchicalVolumeOverlay();
2026-04-29 01:04:28 +07:00
2026-04-28 18:49:05 +07:00
root.RegisterCallback<PointerDownEvent>(evt => {
2026-04-30 20:58:59 +07:00
if (evt.target == root) uiManager.ToggleSettings();
2026-04-28 11:35:49 +07:00
});
2026-04-30 20:58:59 +07:00
root.RegisterCallback<KeyDownEvent>(OnKeyDown);
2026-04-29 01:04:28 +07:00
SetupTab("GeneralTab", "GENERAL");
SetupTab("VideoTab", "VIDEO");
SetupTab("SoundTab", "SOUND");
SetupTab("ControlTab", "CONTROL");
var closeBtn = root.Q<Button>("CloseSettingsBtn");
if (closeBtn != null) closeBtn.clicked += () => uiManager.ToggleSettings();
2026-04-30 20:58:59 +07:00
_masterVol = PlayerPrefs.GetFloat("MasterVolume", 80f);
2026-05-01 02:25:25 +07:00
ApplyVideoSettings();
2026-04-29 01:04:28 +07:00
SwitchTab("GENERAL");
}
2026-05-01 16:51:08 +07:00
private void OnSidebarPointerEnter(PointerEnterEvent evt)
{
_hoverTimer.Stop();
// Bung ra ngay lập tức với animation cực mượt
ExpandSidebar();
}
private void OnSidebarPointerLeave(PointerLeaveEvent evt)
{
_hoverTimer.Stop();
// Thu gọn ngay lập tức
CollapseSidebar();
}
private void ExpandSidebar()
{
if (_isExpanded) return;
_isExpanded = true;
_tabsColumn.AddToClassList("sidebar-expanded");
// Animation 0.5s OutQuart cho cảm giác cao cấp
Tween.Custom(_tabsColumn.resolvedStyle.width, 240f, duration: 0.5f, ease: Ease.OutQuart, onValueChange: val => _tabsColumn.style.width = val);
}
private void CollapseSidebar()
{
if (!_isExpanded) return;
_isExpanded = false;
_tabsColumn.RemoveFromClassList("sidebar-expanded");
Tween.Custom(_tabsColumn.resolvedStyle.width, 80f, duration: 0.45f, ease: Ease.OutQuart, onValueChange: val => _tabsColumn.style.width = val);
}
2026-05-01 02:25:25 +07:00
private void ApplyVideoSettings()
{
int frameLimitIdx = PlayerPrefs.GetInt("FrameLimiter", 2);
ApplyFrameLimit(frameLimitIdx);
_fpsVisible = PlayerPrefs.GetInt("ShowFPS", 0) == 1;
PerformanceOverlay.SetVisible(_fpsVisible);
float dim = PlayerPrefs.GetFloat("BackgroundDim", 50f);
ApplyBackgroundDim(dim);
bool isFull = PlayerPrefs.GetInt("Fullscreen", Screen.fullScreen ? 1 : 0) == 1;
Screen.fullScreen = isFull;
}
private void ApplyFrameLimit(int index)
{
switch (index)
{
case 0: QualitySettings.vSyncCount = 1; Application.targetFrameRate = -1; break;
case 1: QualitySettings.vSyncCount = 0; Application.targetFrameRate = 60; break;
case 2: QualitySettings.vSyncCount = 0; Application.targetFrameRate = 144; break;
case 3: QualitySettings.vSyncCount = 0; Application.targetFrameRate = 999; break;
}
PlayerPrefs.SetInt("FrameLimiter", index);
}
private void ApplyBackgroundDim(float value)
{
PlayerPrefs.SetFloat("BackgroundDim", value);
var dimOverlay = uiManager.Root.Q<VisualElement>("BackgroundDimOverlay");
2026-05-01 16:51:08 +07:00
if (dimOverlay != null) dimOverlay.style.backgroundColor = new Color(0, 0, 0, value / 100f);
2026-05-01 02:25:25 +07:00
}
2026-05-01 01:25:02 +07:00
private void SetupHierarchicalVolumeOverlay()
2026-04-30 20:58:59 +07:00
{
2026-05-01 16:51:08 +07:00
_volumeContainer = new VisualElement { name = "GlobalVolumeOverlay" };
2026-05-01 01:25:02 +07:00
_volumeContainer.style.position = Position.Absolute;
2026-05-01 16:51:08 +07:00
_volumeContainer.style.right = 50; _volumeContainer.style.bottom = 50;
_volumeContainer.style.width = 300; _volumeContainer.style.height = 300;
2026-05-01 01:25:02 +07:00
_volumeContainer.style.display = DisplayStyle.None;
_volumeContainer.pickingMode = PickingMode.Ignore;
uiManager.Root.Add(_volumeContainer);
2026-05-01 16:51:08 +07:00
_masterRing = CreateRing("Master", 120, cyan: true);
_masterRing.style.right = 0; _masterRing.style.bottom = 0;
2026-05-01 01:25:02 +07:00
_masterVolLabel = _masterRing.Q<Label>();
_volumeContainer.Add(_masterRing);
string[] subs = { "MusicVolume", "VFXVolume", "PlayerVolume", "UIVolume" };
string[] shortNames = { "MUS", "VFX", "PLY", "UI" };
for (int i = 0; i < subs.Length; i++)
{
var ring = CreateRing(shortNames[i], 70, false);
float angle = (i * 30f) * Mathf.Deg2Rad;
float radius = 140f;
ring.style.right = 25 + Mathf.Sin(angle) * radius;
ring.style.bottom = 25 + Mathf.Cos(angle) * radius;
string key = subs[i];
ring.RegisterCallback<PointerEnterEvent>(evt => _hoveredSubVolume = key);
ring.RegisterCallback<PointerLeaveEvent>(evt => { if (_hoveredSubVolume == key) _hoveredSubVolume = null; });
2026-05-01 16:51:08 +07:00
ring.pickingMode = PickingMode.Position;
2026-05-01 01:25:02 +07:00
_subRings[key] = (ring, ring.Q<Label>());
_volumeContainer.Add(ring);
}
}
private VisualElement CreateRing(string text, float size, bool cyan)
{
var ring = new VisualElement();
2026-05-01 16:51:08 +07:00
ring.style.width = size; ring.style.height = size;
2026-05-01 01:25:02 +07:00
ring.style.backgroundColor = new Color(0, 0, 0, 0.85f);
2026-05-01 16:51:08 +07:00
var radius = size / 2;
ring.style.borderTopLeftRadius = radius; ring.style.borderTopRightRadius = radius;
ring.style.borderBottomLeftRadius = radius; ring.style.borderBottomRightRadius = radius;
ring.style.borderTopWidth = 3; ring.style.borderBottomWidth = 3;
ring.style.borderLeftWidth = 3; ring.style.borderRightWidth = 3;
2026-05-01 01:25:02 +07:00
ring.style.borderTopColor = ring.style.borderBottomColor = ring.style.borderLeftColor = ring.style.borderRightColor = cyan ? Color.cyan : new Color(0.7f, 0.7f, 0.7f);
2026-05-01 16:51:08 +07:00
ring.style.justifyContent = Justify.Center; ring.style.alignItems = Align.Center;
2026-05-01 01:25:02 +07:00
ring.style.position = Position.Absolute;
var label = new Label("80%");
2026-05-01 16:51:08 +07:00
label.style.color = Color.white; label.style.fontSize = size * 0.25f;
2026-05-01 01:25:02 +07:00
label.style.unityFontStyleAndWeight = FontStyle.Bold;
ring.Add(label);
var title = new Label(text);
2026-05-01 16:51:08 +07:00
title.style.color = Color.gray; title.style.fontSize = size * 0.15f;
title.style.position = Position.Absolute; title.style.bottom = size * 0.15f;
2026-05-01 01:25:02 +07:00
ring.Add(title);
return ring;
2026-04-30 20:58:59 +07:00
}
private void OnMouseWheel(WheelEvent evt)
{
2026-05-01 01:25:02 +07:00
var mainMenuRoot = uiManager.Root.Q<VisualElement>("MainMenuRoot");
bool isMainMenuVisible = mainMenuRoot != null && mainMenuRoot.style.display == DisplayStyle.Flex;
2026-05-01 16:51:08 +07:00
if (!uiManager.IsSettingsOpen && isMainMenuVisible) return;
2026-05-01 02:25:25 +07:00
VisualElement target = evt.target as VisualElement;
bool isDirectUIInteraction = _hoveredSubVolume != null || (_hoveredSlider != null && _activeTab == "SOUND");
if (!isDirectUIInteraction && target != null)
{
2026-05-01 16:51:08 +07:00
if (target is ScrollView || target.GetFirstAncestorOfType<ScrollView>() != null) return;
2026-05-01 02:25:25 +07:00
}
2026-05-01 01:25:02 +07:00
_overlayActiveCount++;
ShowVolumeOverlay();
2026-05-01 16:51:08 +07:00
if (_hoveredSubVolume != null) UpdateSubVolume(_hoveredSubVolume, -evt.delta.y * 2f);
2026-05-01 01:25:02 +07:00
else if (_hoveredSlider != null && _activeTab == "SOUND")
{
2026-04-30 21:46:37 +07:00
float step = (_sliderMax - _sliderMin) / 100f;
2026-05-01 16:51:08 +07:00
float newVal = Mathf.Clamp(_hoveredSlider.value - (evt.delta.y * step * 5f), _sliderMin, _sliderMax);
2026-04-30 21:46:37 +07:00
_hoveredSlider.value = newVal;
_hoveredOnChanged?.Invoke(newVal);
}
2026-05-01 16:51:08 +07:00
else UpdateMasterVolume(-evt.delta.y * 2f);
2026-05-01 01:25:02 +07:00
evt.StopPropagation();
2026-04-30 20:58:59 +07:00
}
private void UpdateMasterVolume(float delta)
{
_masterVol = Mathf.Clamp(_masterVol + delta, 0f, 100f);
PlayerPrefs.SetFloat("MasterVolume", _masterVol);
2026-05-01 01:25:02 +07:00
AudioManager.Instance?.SetVolume("MasterVolume", _masterVol);
2026-04-30 20:58:59 +07:00
_masterVolLabel.text = $"{Mathf.RoundToInt(_masterVol)}%";
2026-05-01 01:25:02 +07:00
if (_activeTab == "SOUND") SwitchTab("SOUND");
}
private void UpdateSubVolume(string key, float delta)
{
2026-05-01 16:51:08 +07:00
float newVal = Mathf.Clamp(PlayerPrefs.GetFloat(key, 80f) + delta, 0f, 100f);
2026-05-01 01:25:02 +07:00
PlayerPrefs.SetFloat(key, newVal);
AudioManager.Instance?.SetVolume(key, newVal);
2026-05-01 16:51:08 +07:00
if (_subRings.TryGetValue(key, out var data)) data.label.text = $"{Mathf.RoundToInt(newVal)}%";
2026-05-01 01:25:02 +07:00
if (_activeTab == "SOUND") SwitchTab("SOUND");
2026-04-30 20:58:59 +07:00
}
private async void ShowVolumeOverlay()
{
2026-05-01 01:25:02 +07:00
_volumeContainer.BringToFront();
uiManager.Root.Q<VisualElement>("CursorLayer")?.BringToFront();
_volumeContainer.style.display = DisplayStyle.Flex;
_volumeContainer.style.opacity = 1f;
2026-05-01 16:51:08 +07:00
foreach (var kvp in _subRings) kvp.Value.label.text = $"{Mathf.RoundToInt(PlayerPrefs.GetFloat(kvp.Key, 80f))}%";
2026-05-01 01:25:02 +07:00
_masterVolLabel.text = $"{Mathf.RoundToInt(_masterVol)}%";
int currentId = _overlayActiveCount;
2026-05-01 16:51:08 +07:00
await Task.Delay(3000);
2026-05-01 01:25:02 +07:00
if (currentId == _overlayActiveCount && _hoveredSubVolume == null)
2026-04-30 20:58:59 +07:00
{
2026-05-01 01:25:02 +07:00
Tween.Custom(1f, 0f, duration: 0.5f, onValueChange: val => _volumeContainer.style.opacity = val)
2026-05-01 16:51:08 +07:00
.OnComplete(() => { if (_volumeContainer.style.opacity == 0f) _volumeContainer.style.display = DisplayStyle.None; });
2026-04-30 20:58:59 +07:00
}
}
2026-04-29 01:04:28 +07:00
private void SetupTab(string btnName, string tabId)
{
var btn = root.Q<Button>(btnName);
2026-05-01 16:51:08 +07:00
if (btn != null) { _tabButtons[tabId] = btn; btn.clicked += () => SwitchTab(tabId); }
2026-04-25 18:20:16 +07:00
}
2026-04-29 01:04:28 +07:00
private void SwitchTab(string tabId)
2026-04-25 18:20:16 +07:00
{
2026-05-01 16:51:08 +07:00
_activeTab = tabId; _tabTitle.text = tabId;
2026-04-29 01:04:28 +07:00
foreach (var kvp in _tabButtons)
{
if (kvp.Key == tabId) kvp.Value.AddToClassList("active-tab");
else kvp.Value.RemoveFromClassList("active-tab");
}
2026-05-01 16:51:08 +07:00
_content.Clear(); _hoveredSlider = null;
2026-04-29 01:04:28 +07:00
switch (tabId)
{
2026-04-30 20:58:59 +07:00
case "GENERAL": RenderGeneralTab(); break;
case "VIDEO": RenderVideoTab(); break;
case "SOUND": RenderSoundTab(); break;
case "CONTROL": RenderControlTab(); break;
2026-04-29 01:04:28 +07:00
}
}
2026-04-30 20:58:59 +07:00
private void RenderGeneralTab()
2026-04-29 01:04:28 +07:00
{
2026-04-30 20:58:59 +07:00
_content.Add(CreateSection("ACCOUNT"));
var userRow = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginBottom = 10 } };
2026-05-01 16:51:08 +07:00
var loggedInLabel = new Label("Logged in as: "); loggedInLabel.AddToClassList("text-body");
2026-04-30 21:46:37 +07:00
userRow.Add(loggedInLabel);
2026-05-01 16:51:08 +07:00
userRow.Add(new Label(PlayerPrefs.GetString("Username", "Guest")) { style = { color = Color.cyan, marginLeft = 5, unityFontStyleAndWeight = FontStyle.Bold } });
2026-04-30 20:58:59 +07:00
_content.Add(userRow);
_content.Add(CreateSection("LANGUAGE"));
2026-05-01 16:51:08 +07:00
var langDropdown = new DropdownField(new List<string> { "English", "Tiếng Việt" }, LocalizationManager.Instance?.CurrentLanguage == "vi" ? 1 : 0);
2026-04-30 20:58:59 +07:00
langDropdown.AddToClassList("custom-dropdown");
2026-04-29 02:31:15 +07:00
langDropdown.RegisterValueChangedCallback(evt => {
2026-05-01 16:51:08 +07:00
LocalizationManager.Instance?.LoadLanguage(evt.newValue == "Tiếng Việt" ? "vi" : "en"); SwitchTab("GENERAL");
2026-04-29 02:31:15 +07:00
});
2026-04-30 20:58:59 +07:00
_content.Add(langDropdown);
_content.Add(CreateSection("UPDATES"));
var versionBox = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
2026-05-01 16:51:08 +07:00
var versionLabel = new Label($"Version: {Application.version}"); versionLabel.AddToClassList("text-body");
2026-04-30 21:46:37 +07:00
versionBox.Add(versionLabel);
2026-05-01 16:51:08 +07:00
var checkBtn = new Button { text = "CHECK FOR UPDATES" }; checkBtn.AddToClassList("button-spring");
2026-04-30 20:58:59 +07:00
checkBtn.clicked += () => checkBtn.text = "UP TO DATE";
versionBox.Add(checkBtn);
_content.Add(versionBox);
_content.Add(CreateSection("CURSOR & MOUSE"));
2026-05-01 15:08:59 +07:00
_content.Add(CreateSliderWithInput("Cursor Size", 10, 150, PlayerPrefs.GetFloat("CursorSize", 40), val => uiManager.SetCursorSize(val)));
2026-04-30 20:58:59 +07:00
var trailToggle = new Toggle("Enable Cursor Trail") { value = PlayerPrefs.GetInt("CursorTrail", 1) == 1 };
2026-05-01 15:08:59 +07:00
trailToggle.RegisterValueChangedCallback(evt => uiManager.SetCursorTrail(evt.newValue));
2026-04-30 20:58:59 +07:00
_content.Add(trailToggle);
var rippleToggle = new Toggle("Enable Ripple Effects") { value = PlayerPrefs.GetInt("CursorRipples", 1) == 1 };
2026-05-01 15:08:59 +07:00
rippleToggle.RegisterValueChangedCallback(evt => uiManager.SetCursorRipples(evt.newValue));
2026-04-30 20:58:59 +07:00
_content.Add(rippleToggle);
2026-05-01 15:08:59 +07:00
_content.Add(CreateSliderWithInput("Sensitivity", 0.1f, 5.0f, PlayerPrefs.GetFloat("MouseSensitivity", 1.0f), val => uiManager.SetMouseSensitivity(val)));
2026-05-01 16:51:08 +07:00
_content.Add(new Toggle("Raw Input (Bypass Acceleration)") { value = true });
2026-04-30 20:58:59 +07:00
_mouseMetricsLabel = new Label("[(report: 0/sec latency: 0ms)]") { style = { fontSize = 11, color = Color.gray, marginTop = 5 } };
_content.Add(_mouseMetricsLabel);
}
2026-04-29 02:31:15 +07:00
2026-04-30 20:58:59 +07:00
private void RenderVideoTab()
{
_content.Add(CreateSection("RENDERER"));
2026-05-01 16:51:08 +07:00
var frameLimit = new DropdownField("Frame Limiter", new List<string> { "VSync", "Power Saving", "Optimal", "Unlimited" }, PlayerPrefs.GetInt("FrameLimiter", 2));
frameLimit.RegisterValueChangedCallback(evt => ApplyFrameLimit(frameLimit.index));
2026-04-30 20:58:59 +07:00
_content.Add(frameLimit);
var fpsToggle = new Toggle("Show FPS Counter") { value = _fpsVisible };
2026-05-01 16:51:08 +07:00
fpsToggle.RegisterValueChangedCallback(evt => { _fpsVisible = evt.newValue; PlayerPrefs.SetInt("ShowFPS", _fpsVisible ? 1 : 0); PerformanceOverlay.SetVisible(_fpsVisible); });
2026-04-30 20:58:59 +07:00
_content.Add(fpsToggle);
_content.Add(CreateSection("LAYOUT"));
Resolution native = Screen.currentResolution;
var resList = Screen.resolutions.Select(r => $"{r.width}x{r.height}").Distinct().Select(s => s == $"{native.width}x{native.height}" ? s + " (native)" : s).ToList();
2026-05-01 02:25:25 +07:00
string currentResStr = $"{Screen.width}x{Screen.height}";
int currentResIdx = resList.FindIndex(s => s.StartsWith(currentResStr));
if (currentResIdx == -1) currentResIdx = resList.FindIndex(s => s.Contains("native"));
var resDropdown = new DropdownField("Resolution", resList, currentResIdx);
2026-04-30 20:58:59 +07:00
resDropdown.RegisterValueChangedCallback(evt => {
string[] parts = evt.newValue.Split(' ')[0].Split('x');
2026-05-01 16:51:08 +07:00
int w = int.Parse(parts[0]), h = int.Parse(parts[1]);
2026-05-01 02:25:25 +07:00
Screen.SetResolution(w, h, Screen.fullScreen);
2026-05-01 16:51:08 +07:00
PlayerPrefs.SetInt("ScreenWidth", w); PlayerPrefs.SetInt("ScreenHeight", h);
2026-04-29 02:31:15 +07:00
});
2026-04-30 20:58:59 +07:00
_content.Add(resDropdown);
var fullToggle = new Toggle("Fullscreen Mode") { value = Screen.fullScreen };
2026-05-01 16:51:08 +07:00
fullToggle.RegisterValueChangedCallback(evt => { Screen.fullScreen = evt.newValue; PlayerPrefs.SetInt("Fullscreen", evt.newValue ? 1 : 0); });
2026-04-30 20:58:59 +07:00
_content.Add(fullToggle);
2026-05-01 02:25:25 +07:00
_content.Add(CreateSliderWithInput("Background Dim", 0, 100, PlayerPrefs.GetFloat("BackgroundDim", 50), val => ApplyBackgroundDim(val)));
2026-04-30 20:58:59 +07:00
_content.Add(CreateSliderWithInput("UI Scale", 0.5f, 2.0f, PlayerPrefs.GetFloat("UIScale", 1.0f), val => uiManager.SetUIScale(val)));
2026-04-29 02:31:15 +07:00
}
2026-04-30 20:58:59 +07:00
private void RenderSoundTab()
2026-04-29 01:04:28 +07:00
{
2026-04-30 20:58:59 +07:00
_content.Add(CreateSection("AUDIO VOLUMES"));
_content.Add(CreateAudioSlider("Master", "MasterVolume"));
_content.Add(CreateAudioSlider("Music", "MusicVolume"));
_content.Add(CreateAudioSlider("VFX", "VFXVolume"));
_content.Add(CreateAudioSlider("Player", "PlayerVolume"));
_content.Add(CreateAudioSlider("UI", "UIVolume"));
_content.Add(new Label("Use Scroll Wheel to control volume.") { style = { marginTop = 20, color = Color.gray, fontSize = 12 } });
}
2026-04-29 01:04:28 +07:00
2026-04-30 20:58:59 +07:00
private VisualElement CreateAudioSlider(string label, string prefKey)
{
2026-05-01 01:25:02 +07:00
var sliderRow = CreateSliderWithInput(label, 0, 100, PlayerPrefs.GetFloat(prefKey, 80), val => {
2026-05-01 16:51:08 +07:00
PlayerPrefs.SetFloat(prefKey, val); AudioManager.Instance?.SetVolume(prefKey, val);
2026-05-01 01:25:02 +07:00
});
2026-04-30 20:58:59 +07:00
sliderRow.RegisterCallback<WheelEvent>(evt => {
2026-05-01 16:51:08 +07:00
float newVal = Mathf.Clamp(PlayerPrefs.GetFloat(prefKey, 80f) - (evt.delta.y * 2f), 0f, 100f);
PlayerPrefs.SetFloat(prefKey, newVal); AudioManager.Instance?.SetVolume(prefKey, newVal);
var slider = sliderRow.Q<Slider>(); if (slider != null) slider.value = newVal;
2026-04-30 20:58:59 +07:00
});
return sliderRow;
2026-04-29 01:04:28 +07:00
}
2026-04-30 20:58:59 +07:00
private void RenderControlTab()
2026-04-29 01:04:28 +07:00
{
2026-04-30 20:58:59 +07:00
_content.Add(CreateSection("KEY BINDINGS"));
2026-05-01 16:51:08 +07:00
if (uiManager.InputReader?.InputActions == null) { _content.Add(new Label("Input Actions not found.") { style = { color = Color.white } }); return; }
2026-05-01 02:25:25 +07:00
foreach (var map in uiManager.InputReader.InputActions.actionMaps)
{
2026-05-01 16:51:08 +07:00
var mapHeader = new Label(map.name.ToUpper()) { style = { fontSize = 14, unityFontStyleAndWeight = FontStyle.Bold, color = Color.cyan, marginTop = 15, marginBottom = 5 } };
2026-05-01 02:25:25 +07:00
_content.Add(mapHeader);
foreach (var action in map.actions)
{
2026-05-01 16:51:08 +07:00
if (action.name == "Look" || action.name == "Scroll" || action.name == "Navigate" || action.name == "Point" || action.name == "Click") continue;
2026-05-01 02:25:25 +07:00
if (action.bindings.Any(b => b.isComposite))
{
for (int i = 0; i < action.bindings.Count; i++)
2026-05-01 16:51:08 +07:00
if (action.bindings[i].isPartOfComposite && action.bindings[i].groups.Contains("Keyboard&Mouse"))
_content.Add(CreateRebindRow(action, i, $"{action.name} {action.bindings[i].name}".ToUpper()));
2026-05-01 02:25:25 +07:00
}
else
{
2026-05-01 16:51:08 +07:00
int idx = action.bindings.ToList().FindIndex(b => b.groups.Contains("Keyboard&Mouse"));
if (idx != -1) _content.Add(CreateRebindRow(action, idx, action.name.ToUpper()));
2026-05-01 02:25:25 +07:00
}
}
}
2026-05-01 16:51:08 +07:00
var resetBtn = new Button { text = "RESET ALL TO DEFAULT" }; resetBtn.AddToClassList("button-spring"); resetBtn.style.marginTop = 30; resetBtn.style.alignSelf = Align.Center;
resetBtn.clicked += () => { uiManager.InputReader.ResetBindings(); SwitchTab("CONTROL"); };
2026-05-01 02:25:25 +07:00
_content.Add(resetBtn);
}
2026-05-01 16:51:08 +07:00
2026-05-01 02:25:25 +07:00
private VisualElement CreateRebindRow(UnityEngine.InputSystem.InputAction action, int bindingIndex, string labelText)
{
2026-05-01 16:51:08 +07:00
var row = new VisualElement(); row.AddToClassList("rebind-row"); row.style.flexDirection = FlexDirection.Row; row.style.justifyContent = Justify.SpaceBetween; row.style.alignItems = Align.Center; row.style.paddingTop = row.style.paddingBottom = 10; row.style.borderBottomWidth = 1; row.style.borderBottomColor = new Color(1, 1, 1, 0.1f);
var label = new Label(labelText); label.AddToClassList("rebind-label"); label.style.color = Color.white; row.Add(label);
var btn = new Button { text = action.GetBindingDisplayString(bindingIndex).ToUpper() }; btn.AddToClassList("rebind-button"); btn.style.width = 150;
btn.clicked += () => StartRebinding(action, bindingIndex, btn); row.Add(btn);
2026-05-01 02:25:25 +07:00
return row;
}
private void StartRebinding(UnityEngine.InputSystem.InputAction action, int bindingIndex, Button btn)
{
2026-05-01 16:51:08 +07:00
btn.text = "> <"; btn.style.color = Color.yellow; action.actionMap.Disable();
var op = action.PerformInteractiveRebinding(bindingIndex).WithControlsExcluding("<Mouse>/position").WithControlsExcluding("<Mouse>/delta").WithControlsExcluding("<Keyboard>/escape").OnMatchWaitForAnother(0.1f)
.OnComplete(operation => { btn.text = action.GetBindingDisplayString(bindingIndex).ToUpper(); btn.style.color = Color.white; operation.Dispose(); action.actionMap.Enable(); uiManager.InputReader.SaveBindings(); })
.OnCancel(operation => { btn.text = action.GetBindingDisplayString(bindingIndex).ToUpper(); btn.style.color = Color.white; operation.Dispose(); action.actionMap.Enable(); });
op.Start();
2026-04-29 01:04:28 +07:00
}
2026-05-01 16:51:08 +07:00
private VisualElement CreateSection(string title) { var label = new Label(title); label.AddToClassList("setting-section-header"); label.style.marginTop = 20; return label; }
2026-04-29 01:04:28 +07:00
2026-04-30 20:58:59 +07:00
private VisualElement CreateSliderWithInput(string labelText, float min, float max, float startVal, Action<float> OnValueChanged)
2026-04-29 01:04:28 +07:00
{
2026-04-30 20:58:59 +07:00
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginTop = 5, marginBottom = 5 } };
2026-05-01 16:51:08 +07:00
var label = new Label(labelText) { style = { width = Length.Percent(35) } }; label.AddToClassList("text-body");
2026-04-30 20:58:59 +07:00
var slider = new Slider(min, max) { value = startVal, style = { flexGrow = 1 } };
2026-05-01 16:51:08 +07:00
var input = new TextField { value = startVal.ToString("F1"), style = { width = 50, marginLeft = 10 } }; input.AddToClassList("input-field");
slider.RegisterCallback<PointerEnterEvent>(evt => { _hoveredSlider = slider; _hoveredOnChanged = OnValueChanged; _sliderMin = min; _sliderMax = max; });
slider.RegisterCallback<PointerLeaveEvent>(evt => { if (_hoveredSlider == slider) { _hoveredSlider = null; _hoveredOnChanged = null; } });
slider.RegisterValueChangedCallback(evt => { float val = Mathf.Round(evt.newValue * 10f) / 10f; if (input.panel?.focusController?.focusedElement != input.ElementAt(0)) input.value = val.ToString("F1"); OnValueChanged?.Invoke(val); });
input.RegisterValueChangedCallback(evt => { if (float.TryParse(evt.newValue, out float val)) { slider.value = Mathf.Clamp(val, min, max); OnValueChanged?.Invoke(slider.value); } });
row.Add(label); row.Add(slider); row.Add(input); return row;
2026-04-29 01:04:28 +07:00
}
2026-04-30 20:58:59 +07:00
private void OnKeyDown(KeyDownEvent evt)
2026-04-29 01:04:28 +07:00
{
2026-04-30 20:58:59 +07:00
if (_hoveredSlider == null) return;
float step = (_sliderMax - _sliderMin) / 100f;
if (evt.keyCode == KeyCode.LeftArrow) _hoveredSlider.value -= step;
if (evt.keyCode == KeyCode.RightArrow) _hoveredSlider.value += step;
2026-04-29 01:04:28 +07:00
}
2026-04-30 20:58:59 +07:00
public override void Update()
2026-04-29 01:04:28 +07:00
{
2026-04-30 20:58:59 +07:00
if (_activeTab == "GENERAL" && _mouseMetricsLabel != null)
{
var (polling, latency) = MouseMetricsHelper.GetMetrics();
_mouseMetricsLabel.text = $"[(report: {polling}/sec latency: {latency:F0}ms)]";
}
2026-04-28 00:07:42 +07:00
}
2026-04-26 05:02:49 +07:00
2026-04-28 00:07:42 +07:00
public override async Task PlayTransitionIn()
{
2026-04-30 20:58:59 +07:00
root.style.display = DisplayStyle.Flex;
2026-04-28 00:07:42 +07:00
_sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(-100), 0));
2026-04-30 21:46:37 +07:00
await Tween.Custom(-100f, 0f, duration: 0.4f, ease: Ease.OutQuad, onValueChange: val => _sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(val), 0)));
2026-04-28 00:07:42 +07:00
}
2026-04-26 05:02:49 +07:00
2026-04-28 00:07:42 +07:00
public override async Task PlayTransitionOut()
{
2026-04-30 21:46:37 +07:00
await Tween.Custom(0f, -100f, duration: 0.3f, ease: Ease.InQuad, onValueChange: val => _sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(val), 0)));
2026-04-28 00:07:42 +07:00
Hide();
2026-04-25 18:20:16 +07:00
}
}
}