Update Setting
This commit is contained in:
@@ -24,6 +24,10 @@ namespace Hallucinate.UI
|
||||
private Tween _hoverTimer;
|
||||
private bool _isExpanded;
|
||||
|
||||
// Osu Style Scroll Tracking
|
||||
private readonly Dictionary<string, VisualElement> _sectionHeaders = new Dictionary<string, VisualElement>();
|
||||
private bool _isManualScrolling;
|
||||
|
||||
// Advanced Mouse Metrics
|
||||
private Label _mouseMetricsLabel;
|
||||
|
||||
@@ -57,6 +61,15 @@ namespace Hallucinate.UI
|
||||
_tabsColumn.RegisterCallback<PointerEnterEvent>(OnSidebarPointerEnter);
|
||||
_tabsColumn.RegisterCallback<PointerLeaveEvent>(OnSidebarPointerLeave);
|
||||
|
||||
// Scroll Tracking cho Osu Style
|
||||
_content.verticalScroller.valueChanged += OnScrollValueChanged;
|
||||
|
||||
// Đăng ký sự kiện đổi ngôn ngữ
|
||||
if (LocalizationManager.Instance != null)
|
||||
{
|
||||
LocalizationManager.Instance.OnLanguageChanged += OnLanguageChanged;
|
||||
}
|
||||
|
||||
// Global Volume Catch
|
||||
uiManager.Root.RegisterCallback<WheelEvent>(OnMouseWheel, TrickleDown.TrickleDown);
|
||||
SetupHierarchicalVolumeOverlay();
|
||||
@@ -76,21 +89,48 @@ namespace Hallucinate.UI
|
||||
if (closeBtn != null) closeBtn.clicked += () => uiManager.ToggleSettings();
|
||||
|
||||
_masterVol = PlayerPrefs.GetFloat("MasterVolume", 80f);
|
||||
|
||||
// Render ban đầu
|
||||
RefreshUI();
|
||||
ApplyVideoSettings();
|
||||
SwitchTab("GENERAL");
|
||||
}
|
||||
|
||||
private void OnLanguageChanged()
|
||||
{
|
||||
// Lưu lại vị trí cuộn hiện tại
|
||||
float currentScroll = _content.scrollOffset.y;
|
||||
RefreshUI();
|
||||
// Khôi phục vị trí cuộn sau một frame để layout kịp cập nhật
|
||||
_content.schedule.Execute(() => _content.scrollOffset = new Vector2(0, currentScroll)).StartingIn(10);
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
RenderAllSettings();
|
||||
UpdateTabLabels();
|
||||
HighlightTab(_activeTab);
|
||||
}
|
||||
|
||||
private void UpdateTabLabels()
|
||||
{
|
||||
foreach (var kvp in _tabButtons)
|
||||
{
|
||||
var label = kvp.Value.Q<Label>(className: "tab-label");
|
||||
if (label != null) label.text = GetT(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetT(string key) => LocalizationManager.Instance != null ? LocalizationManager.Instance.GetLocalizedString(key) : key;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -99,7 +139,6 @@ namespace Hallucinate.UI
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -111,6 +150,201 @@ namespace Hallucinate.UI
|
||||
Tween.Custom(_tabsColumn.resolvedStyle.width, 80f, duration: 0.45f, ease: Ease.OutQuart, onValueChange: val => _tabsColumn.style.width = val);
|
||||
}
|
||||
|
||||
private void RenderAllSettings()
|
||||
{
|
||||
_content.Clear();
|
||||
_sectionHeaders.Clear();
|
||||
|
||||
RenderGeneralTab();
|
||||
RenderVideoTab();
|
||||
RenderSoundTab();
|
||||
RenderControlTab();
|
||||
|
||||
// Thêm khoảng trống cuối để cuộn thoải mái
|
||||
var spacer = new VisualElement { style = { height = 200 } };
|
||||
_content.Add(spacer);
|
||||
}
|
||||
|
||||
private void SetupTab(string btnName, string tabId)
|
||||
{
|
||||
var btn = root.Q<Button>(btnName);
|
||||
if (btn != null)
|
||||
{
|
||||
_tabButtons[tabId] = btn;
|
||||
btn.clicked += () => ScrollToSection(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToSection(string tabId)
|
||||
{
|
||||
if (!_sectionHeaders.TryGetValue(tabId, out var header)) return;
|
||||
|
||||
_isManualScrolling = true;
|
||||
HighlightTab(tabId);
|
||||
|
||||
float targetY = header.layout.y;
|
||||
Tween.Custom(_content.scrollOffset.y, targetY, duration: 0.5f, ease: Ease.OutQuart, onValueChange: val => {
|
||||
_content.scrollOffset = new Vector2(0, val);
|
||||
}).OnComplete(() => _isManualScrolling = false);
|
||||
}
|
||||
|
||||
private void OnScrollValueChanged(float val)
|
||||
{
|
||||
if (_isManualScrolling) return;
|
||||
|
||||
string currentActive = "GENERAL";
|
||||
float minDistance = float.MaxValue;
|
||||
|
||||
foreach (var kvp in _sectionHeaders)
|
||||
{
|
||||
float dist = Math.Abs(kvp.Value.worldBound.y - _content.worldBound.y);
|
||||
if (dist < minDistance)
|
||||
{
|
||||
minDistance = dist;
|
||||
currentActive = kvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
if (_activeTab != currentActive) HighlightTab(currentActive);
|
||||
}
|
||||
|
||||
private void HighlightTab(string tabId)
|
||||
{
|
||||
_activeTab = tabId;
|
||||
_tabTitle.text = GetT(tabId);
|
||||
foreach (var kvp in _tabButtons)
|
||||
{
|
||||
if (kvp.Key == tabId) kvp.Value.AddToClassList("active-tab");
|
||||
else kvp.Value.RemoveFromClassList("active-tab");
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderGeneralTab()
|
||||
{
|
||||
var header = CreateSection("GENERAL");
|
||||
_sectionHeaders["GENERAL"] = header;
|
||||
_content.Add(header);
|
||||
|
||||
_content.Add(CreateSubSection("ACCOUNT"));
|
||||
var userRow = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginBottom = 10 } };
|
||||
var loggedInLabel = new Label(GetT("LOGGED_IN_AS")); loggedInLabel.AddToClassList("text-body");
|
||||
userRow.Add(loggedInLabel);
|
||||
userRow.Add(new Label(PlayerPrefs.GetString("Username", "Guest")) { style = { color = Color.cyan, marginLeft = 5, unityFontStyleAndWeight = FontStyle.Bold } });
|
||||
_content.Add(userRow);
|
||||
|
||||
_content.Add(CreateSubSection("LANGUAGE"));
|
||||
var langDropdown = new DropdownField(new List<string> { "English", "Tiếng Việt" }, LocalizationManager.Instance?.CurrentLanguage == "vi" ? 1 : 0);
|
||||
langDropdown.AddToClassList("custom-dropdown");
|
||||
langDropdown.RegisterValueChangedCallback(evt => {
|
||||
LocalizationManager.Instance?.LoadLanguage(evt.newValue == "Tiếng Việt" ? "vi" : "en");
|
||||
});
|
||||
_content.Add(langDropdown);
|
||||
|
||||
_content.Add(CreateSubSection("UPDATES"));
|
||||
var versionBox = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
|
||||
var versionLabel = new Label($"{GetT("VERSION")} {Application.version}"); versionLabel.AddToClassList("text-body");
|
||||
versionBox.Add(versionLabel);
|
||||
var checkBtn = new Button { text = GetT("CHECK_FOR_UPDATES") }; checkBtn.AddToClassList("button-spring");
|
||||
checkBtn.clicked += () => checkBtn.text = GetT("UP_TO_DATE");
|
||||
versionBox.Add(checkBtn);
|
||||
_content.Add(versionBox);
|
||||
|
||||
_content.Add(CreateSubSection("CURSOR_MOUSE"));
|
||||
_content.Add(CreateSliderWithInput(GetT("CURSOR_SIZE"), 10, 150, PlayerPrefs.GetFloat("CursorSize", 40), val => uiManager.SetCursorSize(val)));
|
||||
var trailToggle = new Toggle(GetT("ENABLE_TRAIL")) { value = PlayerPrefs.GetInt("CursorTrail", 1) == 1 };
|
||||
trailToggle.RegisterValueChangedCallback(evt => uiManager.SetCursorTrail(evt.newValue));
|
||||
_content.Add(trailToggle);
|
||||
var rippleToggle = new Toggle(GetT("ENABLE_RIPPLES")) { value = PlayerPrefs.GetInt("CursorRipples", 1) == 1 };
|
||||
rippleToggle.RegisterValueChangedCallback(evt => uiManager.SetCursorRipples(evt.newValue));
|
||||
_content.Add(rippleToggle);
|
||||
_content.Add(CreateSliderWithInput(GetT("SENSITIVITY"), 0.1f, 5.0f, PlayerPrefs.GetFloat("MouseSensitivity", 1.0f), val => uiManager.SetMouseSensitivity(val)));
|
||||
_mouseMetricsLabel = new Label($"{GetT("MOUSE_LATENCY")} report: 0/sec latency: 0ms") { style = { fontSize = 11, color = Color.gray, marginTop = 5 } };
|
||||
_content.Add(_mouseMetricsLabel);
|
||||
}
|
||||
|
||||
private void RenderVideoTab()
|
||||
{
|
||||
var header = CreateSection("VIDEO");
|
||||
_sectionHeaders["VIDEO"] = header;
|
||||
_content.Add(header);
|
||||
|
||||
_content.Add(CreateSubSection("RENDERER"));
|
||||
var frameLimit = new DropdownField(GetT("FRAME_LIMITER"), new List<string> { "VSync", "Power Saving", "Optimal", "Unlimited" }, PlayerPrefs.GetInt("FrameLimiter", 2));
|
||||
frameLimit.RegisterValueChangedCallback(evt => ApplyFrameLimit(frameLimit.index));
|
||||
_content.Add(frameLimit);
|
||||
var fpsToggle = new Toggle(GetT("SHOW_FPS")) { value = _fpsVisible };
|
||||
fpsToggle.RegisterValueChangedCallback(evt => { _fpsVisible = evt.newValue; PlayerPrefs.SetInt("ShowFPS", _fpsVisible ? 1 : 0); PerformanceOverlay.SetVisible(_fpsVisible); });
|
||||
_content.Add(fpsToggle);
|
||||
|
||||
_content.Add(CreateSubSection("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();
|
||||
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(GetT("RESOLUTION"), resList, currentResIdx);
|
||||
resDropdown.RegisterValueChangedCallback(evt => {
|
||||
string[] parts = evt.newValue.Split(' ')[0].Split('x');
|
||||
int w = int.Parse(parts[0]), h = int.Parse(parts[1]);
|
||||
Screen.SetResolution(w, h, Screen.fullScreen);
|
||||
PlayerPrefs.SetInt("ScreenWidth", w); PlayerPrefs.SetInt("ScreenHeight", h);
|
||||
});
|
||||
_content.Add(resDropdown);
|
||||
var fullToggle = new Toggle(GetT("FULLSCREEN")) { value = Screen.fullScreen };
|
||||
fullToggle.RegisterValueChangedCallback(evt => { Screen.fullScreen = evt.newValue; PlayerPrefs.SetInt("Fullscreen", evt.newValue ? 1 : 0); });
|
||||
_content.Add(fullToggle);
|
||||
_content.Add(CreateSliderWithInput(GetT("BACKGROUND_DIM"), 0, 100, PlayerPrefs.GetFloat("BackgroundDim", 50), val => ApplyBackgroundDim(val)));
|
||||
_content.Add(CreateSliderWithInput(GetT("UI_SCALE"), 0.5f, 2.0f, PlayerPrefs.GetFloat("UIScale", 1.0f), val => uiManager.SetUIScale(val)));
|
||||
}
|
||||
|
||||
private void RenderSoundTab()
|
||||
{
|
||||
var header = CreateSection("SOUND");
|
||||
_sectionHeaders["SOUND"] = header;
|
||||
_content.Add(header);
|
||||
|
||||
_content.Add(CreateSubSection("AUDIO_VOLUMES"));
|
||||
_content.Add(CreateAudioSlider(GetT("MASTER"), "MasterVolume"));
|
||||
_content.Add(CreateAudioSlider(GetT("MUSIC"), "MusicVolume"));
|
||||
_content.Add(CreateAudioSlider(GetT("VFX"), "VFXVolume"));
|
||||
_content.Add(CreateAudioSlider(GetT("PLAYER"), "PlayerVolume"));
|
||||
_content.Add(CreateAudioSlider(GetT("UI"), "UIVolume"));
|
||||
_content.Add(new Label(GetT("SCROLL_HINT")) { style = { marginTop = 20, color = Color.gray, fontSize = 12 } });
|
||||
}
|
||||
|
||||
private void RenderControlTab()
|
||||
{
|
||||
var header = CreateSection("CONTROL");
|
||||
_sectionHeaders["CONTROL"] = header;
|
||||
_content.Add(header);
|
||||
|
||||
_content.Add(CreateSubSection("KEY_BINDINGS"));
|
||||
if (uiManager.InputReader?.InputActions == null) return;
|
||||
foreach (var map in uiManager.InputReader.InputActions.actionMaps)
|
||||
{
|
||||
var mapHeader = new Label(map.name.ToUpper()) { style = { fontSize = 14, unityFontStyleAndWeight = FontStyle.Bold, color = Color.cyan, marginTop = 15, marginBottom = 5 } };
|
||||
_content.Add(mapHeader);
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
if (action.name == "Look" || action.name == "Scroll" || action.name == "Navigate" || action.name == "Point" || action.name == "Click") continue;
|
||||
if (action.bindings.Any(b => b.isComposite))
|
||||
{
|
||||
for (int i = 0; i < action.bindings.Count; i++)
|
||||
if (action.bindings[i].isPartOfComposite && action.bindings[i].groups.Contains("Keyboard&Mouse"))
|
||||
_content.Add(CreateRebindRow(action, i, $"{action.name} {action.bindings[i].name}".ToUpper()));
|
||||
}
|
||||
else
|
||||
{
|
||||
int idx = action.bindings.ToList().FindIndex(b => b.groups.Contains("Keyboard&Mouse"));
|
||||
if (idx != -1) _content.Add(CreateRebindRow(action, idx, action.name.ToUpper()));
|
||||
}
|
||||
}
|
||||
}
|
||||
var resetBtn = new Button { text = GetT("RESET_ALL") }; resetBtn.AddToClassList("button-spring"); resetBtn.style.marginTop = 30; resetBtn.style.alignSelf = Align.Center;
|
||||
resetBtn.clicked += () => { uiManager.InputReader.ResetBindings(); RefreshUI(); };
|
||||
_content.Add(resetBtn);
|
||||
}
|
||||
|
||||
private void ApplyVideoSettings()
|
||||
{
|
||||
int frameLimitIdx = PlayerPrefs.GetInt("FrameLimiter", 2);
|
||||
@@ -119,8 +353,6 @@ namespace Hallucinate.UI
|
||||
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)
|
||||
@@ -230,7 +462,6 @@ namespace Hallucinate.UI
|
||||
PlayerPrefs.SetFloat("MasterVolume", _masterVol);
|
||||
AudioManager.Instance?.SetVolume("MasterVolume", _masterVol);
|
||||
_masterVolLabel.text = $"{Mathf.RoundToInt(_masterVol)}%";
|
||||
if (_activeTab == "SOUND") SwitchTab("SOUND");
|
||||
}
|
||||
|
||||
private void UpdateSubVolume(string key, float delta)
|
||||
@@ -239,7 +470,6 @@ namespace Hallucinate.UI
|
||||
PlayerPrefs.SetFloat(key, newVal);
|
||||
AudioManager.Instance?.SetVolume(key, newVal);
|
||||
if (_subRings.TryGetValue(key, out var data)) data.label.text = $"{Mathf.RoundToInt(newVal)}%";
|
||||
if (_activeTab == "SOUND") SwitchTab("SOUND");
|
||||
}
|
||||
|
||||
private async void ShowVolumeOverlay()
|
||||
@@ -259,109 +489,36 @@ namespace Hallucinate.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTab(string btnName, string tabId)
|
||||
{
|
||||
var btn = root.Q<Button>(btnName);
|
||||
if (btn != null) { _tabButtons[tabId] = btn; btn.clicked += () => SwitchTab(tabId); }
|
||||
private VisualElement CreateSection(string title)
|
||||
{
|
||||
var label = new Label(GetT(title));
|
||||
label.AddToClassList("text-heading");
|
||||
label.style.marginTop = 60;
|
||||
label.style.borderBottomWidth = 2;
|
||||
label.style.borderBottomColor = Color.cyan;
|
||||
label.style.paddingBottom = 10;
|
||||
return label;
|
||||
}
|
||||
|
||||
private void SwitchTab(string tabId)
|
||||
{
|
||||
_activeTab = tabId; _tabTitle.text = tabId;
|
||||
foreach (var kvp in _tabButtons)
|
||||
{
|
||||
if (kvp.Key == tabId) kvp.Value.AddToClassList("active-tab");
|
||||
else kvp.Value.RemoveFromClassList("active-tab");
|
||||
}
|
||||
_content.Clear(); _hoveredSlider = null;
|
||||
switch (tabId)
|
||||
{
|
||||
case "GENERAL": RenderGeneralTab(); break;
|
||||
case "VIDEO": RenderVideoTab(); break;
|
||||
case "SOUND": RenderSoundTab(); break;
|
||||
case "CONTROL": RenderControlTab(); break;
|
||||
}
|
||||
private VisualElement CreateSubSection(string title)
|
||||
{
|
||||
var label = new Label(GetT(title));
|
||||
label.AddToClassList("setting-section-header");
|
||||
label.style.marginTop = 20;
|
||||
return label;
|
||||
}
|
||||
|
||||
private void RenderGeneralTab()
|
||||
private VisualElement CreateSliderWithInput(string labelText, float min, float max, float startVal, Action<float> OnValueChanged)
|
||||
{
|
||||
_content.Add(CreateSection("ACCOUNT"));
|
||||
var userRow = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginBottom = 10 } };
|
||||
var loggedInLabel = new Label("Logged in as: "); loggedInLabel.AddToClassList("text-body");
|
||||
userRow.Add(loggedInLabel);
|
||||
userRow.Add(new Label(PlayerPrefs.GetString("Username", "Guest")) { style = { color = Color.cyan, marginLeft = 5, unityFontStyleAndWeight = FontStyle.Bold } });
|
||||
_content.Add(userRow);
|
||||
|
||||
_content.Add(CreateSection("LANGUAGE"));
|
||||
var langDropdown = new DropdownField(new List<string> { "English", "Tiếng Việt" }, LocalizationManager.Instance?.CurrentLanguage == "vi" ? 1 : 0);
|
||||
langDropdown.AddToClassList("custom-dropdown");
|
||||
langDropdown.RegisterValueChangedCallback(evt => {
|
||||
LocalizationManager.Instance?.LoadLanguage(evt.newValue == "Tiếng Việt" ? "vi" : "en"); SwitchTab("GENERAL");
|
||||
});
|
||||
_content.Add(langDropdown);
|
||||
|
||||
_content.Add(CreateSection("UPDATES"));
|
||||
var versionBox = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } };
|
||||
var versionLabel = new Label($"Version: {Application.version}"); versionLabel.AddToClassList("text-body");
|
||||
versionBox.Add(versionLabel);
|
||||
var checkBtn = new Button { text = "CHECK FOR UPDATES" }; checkBtn.AddToClassList("button-spring");
|
||||
checkBtn.clicked += () => checkBtn.text = "UP TO DATE";
|
||||
versionBox.Add(checkBtn);
|
||||
_content.Add(versionBox);
|
||||
|
||||
_content.Add(CreateSection("CURSOR & MOUSE"));
|
||||
_content.Add(CreateSliderWithInput("Cursor Size", 10, 150, PlayerPrefs.GetFloat("CursorSize", 40), val => uiManager.SetCursorSize(val)));
|
||||
var trailToggle = new Toggle("Enable Cursor Trail") { value = PlayerPrefs.GetInt("CursorTrail", 1) == 1 };
|
||||
trailToggle.RegisterValueChangedCallback(evt => uiManager.SetCursorTrail(evt.newValue));
|
||||
_content.Add(trailToggle);
|
||||
var rippleToggle = new Toggle("Enable Ripple Effects") { value = PlayerPrefs.GetInt("CursorRipples", 1) == 1 };
|
||||
rippleToggle.RegisterValueChangedCallback(evt => uiManager.SetCursorRipples(evt.newValue));
|
||||
_content.Add(rippleToggle);
|
||||
_content.Add(CreateSliderWithInput("Sensitivity", 0.1f, 5.0f, PlayerPrefs.GetFloat("MouseSensitivity", 1.0f), val => uiManager.SetMouseSensitivity(val)));
|
||||
_content.Add(new Toggle("Raw Input (Bypass Acceleration)") { value = true });
|
||||
_mouseMetricsLabel = new Label("[(report: 0/sec latency: 0ms)]") { style = { fontSize = 11, color = Color.gray, marginTop = 5 } };
|
||||
_content.Add(_mouseMetricsLabel);
|
||||
}
|
||||
|
||||
private void RenderVideoTab()
|
||||
{
|
||||
_content.Add(CreateSection("RENDERER"));
|
||||
var frameLimit = new DropdownField("Frame Limiter", new List<string> { "VSync", "Power Saving", "Optimal", "Unlimited" }, PlayerPrefs.GetInt("FrameLimiter", 2));
|
||||
frameLimit.RegisterValueChangedCallback(evt => ApplyFrameLimit(frameLimit.index));
|
||||
_content.Add(frameLimit);
|
||||
var fpsToggle = new Toggle("Show FPS Counter") { value = _fpsVisible };
|
||||
fpsToggle.RegisterValueChangedCallback(evt => { _fpsVisible = evt.newValue; PlayerPrefs.SetInt("ShowFPS", _fpsVisible ? 1 : 0); PerformanceOverlay.SetVisible(_fpsVisible); });
|
||||
_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();
|
||||
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);
|
||||
resDropdown.RegisterValueChangedCallback(evt => {
|
||||
string[] parts = evt.newValue.Split(' ')[0].Split('x');
|
||||
int w = int.Parse(parts[0]), h = int.Parse(parts[1]);
|
||||
Screen.SetResolution(w, h, Screen.fullScreen);
|
||||
PlayerPrefs.SetInt("ScreenWidth", w); PlayerPrefs.SetInt("ScreenHeight", h);
|
||||
});
|
||||
_content.Add(resDropdown);
|
||||
var fullToggle = new Toggle("Fullscreen Mode") { value = Screen.fullScreen };
|
||||
fullToggle.RegisterValueChangedCallback(evt => { Screen.fullScreen = evt.newValue; PlayerPrefs.SetInt("Fullscreen", evt.newValue ? 1 : 0); });
|
||||
_content.Add(fullToggle);
|
||||
_content.Add(CreateSliderWithInput("Background Dim", 0, 100, PlayerPrefs.GetFloat("BackgroundDim", 50), val => ApplyBackgroundDim(val)));
|
||||
_content.Add(CreateSliderWithInput("UI Scale", 0.5f, 2.0f, PlayerPrefs.GetFloat("UIScale", 1.0f), val => uiManager.SetUIScale(val)));
|
||||
}
|
||||
|
||||
private void RenderSoundTab()
|
||||
{
|
||||
_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 } });
|
||||
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginTop = 5, marginBottom = 5 } };
|
||||
var label = new Label(labelText) { style = { width = Length.Percent(35) } }; label.AddToClassList("text-body");
|
||||
var slider = new Slider(min, max) { value = startVal, style = { flexGrow = 1 } };
|
||||
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;
|
||||
}
|
||||
|
||||
private VisualElement CreateAudioSlider(string label, string prefKey)
|
||||
@@ -377,35 +534,6 @@ namespace Hallucinate.UI
|
||||
return sliderRow;
|
||||
}
|
||||
|
||||
private void RenderControlTab()
|
||||
{
|
||||
_content.Add(CreateSection("KEY BINDINGS"));
|
||||
if (uiManager.InputReader?.InputActions == null) { _content.Add(new Label("Input Actions not found.") { style = { color = Color.white } }); return; }
|
||||
foreach (var map in uiManager.InputReader.InputActions.actionMaps)
|
||||
{
|
||||
var mapHeader = new Label(map.name.ToUpper()) { style = { fontSize = 14, unityFontStyleAndWeight = FontStyle.Bold, color = Color.cyan, marginTop = 15, marginBottom = 5 } };
|
||||
_content.Add(mapHeader);
|
||||
foreach (var action in map.actions)
|
||||
{
|
||||
if (action.name == "Look" || action.name == "Scroll" || action.name == "Navigate" || action.name == "Point" || action.name == "Click") continue;
|
||||
if (action.bindings.Any(b => b.isComposite))
|
||||
{
|
||||
for (int i = 0; i < action.bindings.Count; i++)
|
||||
if (action.bindings[i].isPartOfComposite && action.bindings[i].groups.Contains("Keyboard&Mouse"))
|
||||
_content.Add(CreateRebindRow(action, i, $"{action.name} {action.bindings[i].name}".ToUpper()));
|
||||
}
|
||||
else
|
||||
{
|
||||
int idx = action.bindings.ToList().FindIndex(b => b.groups.Contains("Keyboard&Mouse"));
|
||||
if (idx != -1) _content.Add(CreateRebindRow(action, idx, action.name.ToUpper()));
|
||||
}
|
||||
}
|
||||
}
|
||||
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"); };
|
||||
_content.Add(resetBtn);
|
||||
}
|
||||
|
||||
private VisualElement CreateRebindRow(UnityEngine.InputSystem.InputAction action, int bindingIndex, string labelText)
|
||||
{
|
||||
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);
|
||||
@@ -424,21 +552,6 @@ namespace Hallucinate.UI
|
||||
op.Start();
|
||||
}
|
||||
|
||||
private VisualElement CreateSection(string title) { var label = new Label(title); label.AddToClassList("setting-section-header"); label.style.marginTop = 20; return label; }
|
||||
|
||||
private VisualElement CreateSliderWithInput(string labelText, float min, float max, float startVal, Action<float> OnValueChanged)
|
||||
{
|
||||
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginTop = 5, marginBottom = 5 } };
|
||||
var label = new Label(labelText) { style = { width = Length.Percent(35) } }; label.AddToClassList("text-body");
|
||||
var slider = new Slider(min, max) { value = startVal, style = { flexGrow = 1 } };
|
||||
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;
|
||||
}
|
||||
|
||||
private void OnKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
if (_hoveredSlider == null) return;
|
||||
@@ -449,10 +562,10 @@ namespace Hallucinate.UI
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (_activeTab == "GENERAL" && _mouseMetricsLabel != null)
|
||||
if (_mouseMetricsLabel != null && _mouseMetricsLabel.panel != null)
|
||||
{
|
||||
var (polling, latency) = MouseMetricsHelper.GetMetrics();
|
||||
_mouseMetricsLabel.text = $"[(report: {polling}/sec latency: {latency:F0}ms)]";
|
||||
_mouseMetricsLabel.text = $"{GetT("MOUSE_LATENCY")} report: {polling}/sec latency: {latency:F0}ms";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,5 +581,13 @@ namespace Hallucinate.UI
|
||||
await Tween.Custom(0f, -100f, duration: 0.3f, ease: Ease.InQuad, onValueChange: val => _sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(val), 0)));
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (LocalizationManager.Instance != null)
|
||||
{
|
||||
LocalizationManager.Instance.OnLanguageChanged -= OnLanguageChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user