445 lines
20 KiB
C#
445 lines
20 KiB
C#
using System;
|
||||
|
|
using System.Collections.Generic;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.InputSystem;
|
|||
|
|
using UnityEngine.UIElements;
|
|||
|
|
|
|||
|
|
namespace Ironspore
|
|||
|
|
{
|
|||
|
|
// Owns a settings draft and its UI. It does not write files or mutate gameplay state.
|
|||
|
|
public sealed class SettingsScreen
|
|||
|
|
{
|
|||
|
|
readonly SettingsService settings;
|
|||
|
|
readonly Func<bool> backPressed;
|
|||
|
|
readonly List<VisualElement> focusOrder = new List<VisualElement>();
|
|||
|
|
readonly Dictionary<Button, Action> buttonActions = new Dictionary<Button, Action>();
|
|||
|
|
VisualElement parent, screen;
|
|||
|
|
ScrollView scroll;
|
|||
|
|
Label countdown;
|
|||
|
|
Action closed;
|
|||
|
|
GameSettingsData draft;
|
|||
|
|
BindingSlot capture;
|
|||
|
|
string messageKey;
|
|||
|
|
object[] messageArgs = Array.Empty<object>();
|
|||
|
|
int tab, lastKeyFrame = -1;
|
|||
|
|
bool open;
|
|||
|
|
static string L(string key, params object[] args) => GameLocalization.Text(key, args);
|
|||
|
|
|
|||
|
|
public SettingsScreen(SettingsService settings, Func<bool> backPressed)
|
|||
|
|
{
|
|||
|
|
this.settings = settings;
|
|||
|
|
this.backPressed = backPressed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Open(VisualElement parent, Action closed)
|
|||
|
|
{
|
|||
|
|
this.parent = parent;
|
|||
|
|
this.closed = closed;
|
|||
|
|
draft = settings.Current;
|
|||
|
|
messageKey = null;
|
|||
|
|
open = true;
|
|||
|
|
Draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Close()
|
|||
|
|
{
|
|||
|
|
if (!open)
|
|||
|
|
return;
|
|||
|
|
open = false;
|
|||
|
|
capture = null;
|
|||
|
|
settings.DiscardDraft();
|
|||
|
|
screen?.RemoveFromHierarchy();
|
|||
|
|
screen = null;
|
|||
|
|
scroll = null;
|
|||
|
|
closed = null;
|
|||
|
|
focusOrder.Clear();
|
|||
|
|
buttonActions.Clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void RefreshLocale()
|
|||
|
|
{
|
|||
|
|
if (!open) return;
|
|||
|
|
string focusedName = (screen.focusController.focusedElement as VisualElement)?.name;
|
|||
|
|
Draw();
|
|||
|
|
if (!string.IsNullOrEmpty(focusedName)) FocusAfterDispatch(screen.Q(focusedName));
|
|||
|
|
}
|
|||
|
|
public void CancelInput() => settings.Bindings.Cancel();
|
|||
|
|
|
|||
|
|
void Draw()
|
|||
|
|
{
|
|||
|
|
Vector2 offset = scroll?.scrollOffset ?? Vector2.zero;
|
|||
|
|
screen?.RemoveFromHierarchy();
|
|||
|
|
focusOrder.Clear();
|
|||
|
|
buttonActions.Clear();
|
|||
|
|
screen = Element(parent, "settings-screen", "settings-screen");
|
|||
|
|
screen.RegisterCallback<KeyDownEvent>(evt =>
|
|||
|
|
{
|
|||
|
|
if (HandleKey(evt.keyCode, evt.shiftKey))
|
|||
|
|
evt.StopImmediatePropagation();
|
|||
|
|
}, TrickleDown.TrickleDown);
|
|||
|
|
screen.RegisterCallback<PointerDownEvent>(CapturePointer, TrickleDown.TrickleDown);
|
|||
|
|
var panel = Element(screen, "settings-panel");
|
|||
|
|
var header = Element(panel, "settings-header");
|
|||
|
|
var title = Element(header, "settings-title-group");
|
|||
|
|
Text(title, L("settings.title"), "heading");
|
|||
|
|
Text(title, L("settings.help"), "settings-help");
|
|||
|
|
var locales = GameLocalization.Catalog.locales;
|
|||
|
|
var names = new List<string>();
|
|||
|
|
foreach (var locale in locales)
|
|||
|
|
names.Add(locale.nativeName);
|
|||
|
|
int languageIndex = Array.FindIndex(locales, x => x.code == GameLocalization.CurrentCode);
|
|||
|
|
var language = Dropdown(header, "settings.language", names, languageIndex, index => GameLocalization.SelectLocale(locales[index].code));
|
|||
|
|
language.name = "settings-language";
|
|||
|
|
language.AddToClassList("settings-language");
|
|||
|
|
var body = Element(panel, "settings-body");
|
|||
|
|
var navigation = Element(body, "settings-navigation");
|
|||
|
|
string[] tabs = { "settings.audio", "settings.display", "settings.controls" };
|
|||
|
|
for (int i = 0; i < tabs.Length; i++)
|
|||
|
|
{
|
|||
|
|
int choice = i;
|
|||
|
|
var button = AddButton(navigation, L(tabs[i]), () => { tab = choice; scroll = null; Draw(); }, "settings-tab");
|
|||
|
|
button.name = "settings-tab-" + i;
|
|||
|
|
if (tab == i)
|
|||
|
|
button.AddToClassList("selected");
|
|||
|
|
}
|
|||
|
|
scroll = new ScrollView(ScrollViewMode.Vertical) { horizontalScrollerVisibility = ScrollerVisibility.Hidden };
|
|||
|
|
scroll.AddToClassList("settings-scroll");
|
|||
|
|
body.Add(scroll);
|
|||
|
|
if (tab == 0)
|
|||
|
|
Audio(scroll);
|
|||
|
|
else if (tab == 1)
|
|||
|
|
Display(scroll);
|
|||
|
|
else
|
|||
|
|
Controls(scroll);
|
|||
|
|
var renderedScroll = scroll;
|
|||
|
|
renderedScroll.schedule.Execute(() => { if (renderedScroll.panel != null) renderedScroll.scrollOffset = offset; });
|
|||
|
|
var status = Text(panel, string.IsNullOrEmpty(messageKey) ? "" : L(messageKey, messageArgs), "settings-status");
|
|||
|
|
status.name = "settings-status";
|
|||
|
|
var footer = Element(panel, "settings-footer");
|
|||
|
|
var reset = AddButton(footer, L("settings.reset"), Reset, "button");
|
|||
|
|
reset.name = "settings-reset";
|
|||
|
|
var footerActions = Element(footer, "settings-footer-actions");
|
|||
|
|
AddButton(footerActions, L("settings.back"), Back, "button").name = "settings-back";
|
|||
|
|
AddButton(footerActions, L("settings.apply"), Apply, "primary").name = "settings-apply";
|
|||
|
|
if (settings.ConfirmingDisplay)
|
|||
|
|
{
|
|||
|
|
header.SetEnabled(false);
|
|||
|
|
body.SetEnabled(false);
|
|||
|
|
footer.SetEnabled(false);
|
|||
|
|
var dialog = Dialog("settings.display.confirm");
|
|||
|
|
countdown = Text(dialog, L("settings.display.countdown", settings.ConfirmationSeconds), "body");
|
|||
|
|
FocusAfterDispatch(AddButton(dialog, L("settings.display.keep"), ConfirmDisplay, "primary"));
|
|||
|
|
AddButton(dialog, L("settings.display.revert"), RevertDisplay, "button");
|
|||
|
|
}
|
|||
|
|
else if (capture != null)
|
|||
|
|
{
|
|||
|
|
header.SetEnabled(false);
|
|||
|
|
body.SetEnabled(false);
|
|||
|
|
footer.SetEnabled(false);
|
|||
|
|
var dialog = Dialog("settings.bind.waiting");
|
|||
|
|
Text(dialog, L("settings.bind.prompt", L(capture.LabelKey)), "body");
|
|||
|
|
var cancel = AddButton(dialog, L("settings.bind.cancel"), settings.Bindings.Cancel, "button");
|
|||
|
|
cancel.name = "settings-capture-cancel";
|
|||
|
|
FocusAfterDispatch(cancel);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
FocusAfterDispatch(navigation.Q<Button>("settings-tab-" + tab));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void FocusAfterDispatch(VisualElement element)
|
|||
|
|
{
|
|||
|
|
// Popup and button dispatch may restore focus to the old, detached control afterwards.
|
|||
|
|
if (element != null)
|
|||
|
|
element.schedule.Execute(() => { if (element.panel != null && element.enabledInHierarchy) element.Focus(); });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Audio(VisualElement content)
|
|||
|
|
{
|
|||
|
|
Text(content, L("settings.audio"), "section");
|
|||
|
|
Slider(content, "settings.volume", draft.masterVolume, 0, 1, .01f,
|
|||
|
|
value => draft.masterVolume = value, value => L("settings.percent", Mathf.RoundToInt(value * 100)));
|
|||
|
|
Text(content, L("settings.volumeHelp"), "hint");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Display(VisualElement content)
|
|||
|
|
{
|
|||
|
|
Text(content, L("settings.display"), "section");
|
|||
|
|
string[] tiers = QualitySettings.names;
|
|||
|
|
var qualityNames = new List<string>();
|
|||
|
|
foreach (string tier in tiers)
|
|||
|
|
{
|
|||
|
|
switch (tier)
|
|||
|
|
{
|
|||
|
|
case "Very Low": qualityNames.Add(L("settings.quality.veryLow")); break;
|
|||
|
|
case "Low": qualityNames.Add(L("settings.quality.low")); break;
|
|||
|
|
case "Medium": qualityNames.Add(L("settings.quality.medium")); break;
|
|||
|
|
case "High": qualityNames.Add(L("settings.quality.high")); break;
|
|||
|
|
case "Very High": qualityNames.Add(L("settings.quality.veryHigh")); break;
|
|||
|
|
case "Ultra": qualityNames.Add(L("settings.quality.ultra")); break;
|
|||
|
|
default: qualityNames.Add(tier); break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Dropdown(content, "settings.quality", qualityNames, draft.qualityLevel, index => draft.qualityLevel = index).name = "settings-quality";
|
|||
|
|
var resolutions = settings.Resolutions();
|
|||
|
|
var choices = new List<string>();
|
|||
|
|
int selected = 0;
|
|||
|
|
for (int i = 0; i < resolutions.Count; i++)
|
|||
|
|
{
|
|||
|
|
var size = resolutions[i];
|
|||
|
|
choices.Add(size.x + " × " + size.y);
|
|||
|
|
if (size.x == draft.resolutionWidth && size.y == draft.resolutionHeight)
|
|||
|
|
selected = i;
|
|||
|
|
}
|
|||
|
|
Dropdown(content, "settings.resolution", choices, selected, index =>
|
|||
|
|
{
|
|||
|
|
draft.resolutionWidth = resolutions[index].x;
|
|||
|
|
draft.resolutionHeight = resolutions[index].y;
|
|||
|
|
}).name = "settings-resolution";
|
|||
|
|
Dropdown(content, "settings.windowMode", new List<string> { L("settings.windowed"), L("settings.fullscreen") }, draft.fullscreen ? 1 : 0,
|
|||
|
|
index => draft.fullscreen = index == 1).name = "settings-window-mode";
|
|||
|
|
Text(content, L("settings.displayHelp"), "hint");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Controls(VisualElement content)
|
|||
|
|
{
|
|||
|
|
Text(content, L("settings.mouse"), "section");
|
|||
|
|
Slider(content, "settings.sensitivity", draft.mouseSensitivity, .1f, 5, .05f,
|
|||
|
|
value => draft.mouseSensitivity = value, value => L("settings.multiplier", value.ToString("0.00", GameLocalization.Culture)));
|
|||
|
|
Slider(content, "settings.aimSensitivity", draft.aimSensitivity, .1f, 2, .05f,
|
|||
|
|
value => draft.aimSensitivity = value, value => L("settings.multiplier", value.ToString("0.00", GameLocalization.Culture)));
|
|||
|
|
Text(content, L("settings.bindings"), "section");
|
|||
|
|
Text(content, L("settings.bind.help"), "hint");
|
|||
|
|
foreach (var slot in settings.Bindings.Slots)
|
|||
|
|
{
|
|||
|
|
var row = Element(content, "settings-binding-row");
|
|||
|
|
Text(row, L(slot.LabelKey), "settings-binding-label");
|
|||
|
|
var button = AddButton(row, settings.Bindings.Display(slot), () => BeginRebind(slot), "settings-binding");
|
|||
|
|
button.name = "binding-" + slot.Action.name.ToLowerInvariant() + "-" + slot.Index;
|
|||
|
|
button.tooltip = L("settings.bind.prompt", L(slot.LabelKey));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void BeginRebind(BindingSlot slot)
|
|||
|
|
{
|
|||
|
|
capture = slot;
|
|||
|
|
settings.Bindings.Begin(slot, (result, conflict) =>
|
|||
|
|
{
|
|||
|
|
if (!open)
|
|||
|
|
return;
|
|||
|
|
capture = null;
|
|||
|
|
draft.bindingOverrides = settings.Bindings.Save();
|
|||
|
|
string key = result == BindingChange.Success ? "settings.bind.changed"
|
|||
|
|
: result == BindingChange.Cancelled ? "settings.bind.cancelled"
|
|||
|
|
: result == BindingChange.Conflict ? "settings.bind.conflict"
|
|||
|
|
: result == BindingChange.Reserved ? "settings.bind.reserved" : "settings.bind.unsupported";
|
|||
|
|
Message(key, result == BindingChange.Conflict ? L(conflict.LabelKey) : L(slot.LabelKey));
|
|||
|
|
Draw();
|
|||
|
|
});
|
|||
|
|
Draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Apply()
|
|||
|
|
{
|
|||
|
|
var result = settings.Apply(draft);
|
|||
|
|
if (result == SettingsApplyResult.Saved)
|
|||
|
|
{
|
|||
|
|
draft = settings.Current;
|
|||
|
|
Message("settings.saved");
|
|||
|
|
}
|
|||
|
|
else if (result == SettingsApplyResult.Failed)
|
|||
|
|
Message("settings.saveFailed");
|
|||
|
|
Draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void ConfirmDisplay()
|
|||
|
|
{
|
|||
|
|
bool saved = settings.ConfirmDisplay();
|
|||
|
|
draft = settings.Current;
|
|||
|
|
Message(saved ? "settings.saved" : "settings.saveFailed");
|
|||
|
|
Draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void RevertDisplay()
|
|||
|
|
{
|
|||
|
|
settings.CancelDisplay();
|
|||
|
|
var saved = settings.Current;
|
|||
|
|
draft.resolutionWidth = saved.resolutionWidth;
|
|||
|
|
draft.resolutionHeight = saved.resolutionHeight;
|
|||
|
|
draft.fullscreen = saved.fullscreen;
|
|||
|
|
Message("settings.display.reverted");
|
|||
|
|
Draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Reset()
|
|||
|
|
{
|
|||
|
|
draft = settings.Defaults;
|
|||
|
|
settings.Bindings.Reset();
|
|||
|
|
Message("settings.resetHint");
|
|||
|
|
Draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Back()
|
|||
|
|
{
|
|||
|
|
if (settings.Bindings.IsRebinding)
|
|||
|
|
{
|
|||
|
|
settings.Bindings.Cancel();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (settings.ConfirmingDisplay)
|
|||
|
|
{
|
|||
|
|
RevertDisplay();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
var callback = closed;
|
|||
|
|
Close();
|
|||
|
|
callback?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Tick()
|
|||
|
|
{
|
|||
|
|
if (!open)
|
|||
|
|
return;
|
|||
|
|
if (settings.Tick())
|
|||
|
|
RevertDisplay();
|
|||
|
|
if (settings.ConfirmingDisplay && countdown != null)
|
|||
|
|
countdown.text = L("settings.display.countdown", settings.ConfirmationSeconds);
|
|||
|
|
if (settings.Bindings.SuppressMenuInput)
|
|||
|
|
return;
|
|||
|
|
var keyboard = Keyboard.current;
|
|||
|
|
if (keyboard != null)
|
|||
|
|
{
|
|||
|
|
if (keyboard.escapeKey.wasPressedThisFrame) HandleKey(KeyCode.Escape, false);
|
|||
|
|
else if (keyboard.tabKey.wasPressedThisFrame) HandleKey(KeyCode.Tab, keyboard.shiftKey.isPressed);
|
|||
|
|
else if (keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame) HandleKey(KeyCode.Return, false);
|
|||
|
|
else if (keyboard.spaceKey.wasPressedThisFrame) HandleKey(KeyCode.Space, false);
|
|||
|
|
else if (keyboard.leftArrowKey.wasPressedThisFrame) HandleKey(KeyCode.LeftArrow, false);
|
|||
|
|
else if (keyboard.rightArrowKey.wasPressedThisFrame) HandleKey(KeyCode.RightArrow, false);
|
|||
|
|
else if (keyboard.upArrowKey.wasPressedThisFrame) HandleKey(KeyCode.UpArrow, false);
|
|||
|
|
else if (keyboard.downArrowKey.wasPressedThisFrame) HandleKey(KeyCode.DownArrow, false);
|
|||
|
|
}
|
|||
|
|
if (backPressed() && lastKeyFrame != Time.frameCount)
|
|||
|
|
HandleKey(KeyCode.Escape, false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool HandleKey(KeyCode key, bool shift)
|
|||
|
|
{
|
|||
|
|
if (settings.Bindings.IsRebinding)
|
|||
|
|
{
|
|||
|
|
if (key == KeyCode.Escape) settings.Bindings.Cancel();
|
|||
|
|
else if (key != KeyCode.None) settings.Bindings.CapturePath(SettingsInputCapture.KeyboardPath(key));
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
if (settings.Bindings.SuppressMenuInput)
|
|||
|
|
return true;
|
|||
|
|
if (lastKeyFrame == Time.frameCount)
|
|||
|
|
return true;
|
|||
|
|
var focused = screen.focusController.focusedElement as VisualElement;
|
|||
|
|
int index = focusOrder.FindIndex(x => x == focused || (focused != null && x.Contains(focused)));
|
|||
|
|
if (key == KeyCode.Escape)
|
|||
|
|
Back();
|
|||
|
|
else if (key == KeyCode.Tab)
|
|||
|
|
{
|
|||
|
|
int direction = shift ? -1 : 1;
|
|||
|
|
for (int i = 0; i < focusOrder.Count; i++)
|
|||
|
|
{
|
|||
|
|
index = (index + direction + focusOrder.Count) % focusOrder.Count;
|
|||
|
|
if (focusOrder[index].enabledInHierarchy)
|
|||
|
|
{
|
|||
|
|
focusOrder[index].Focus();
|
|||
|
|
if (scroll.Contains(focusOrder[index])) scroll.ScrollTo(focusOrder[index]);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if ((key == KeyCode.Return || key == KeyCode.KeypadEnter || key == KeyCode.Space) && focused is Button button && buttonActions.TryGetValue(button, out var action))
|
|||
|
|
action();
|
|||
|
|
else if (index >= 0 && (key == KeyCode.LeftArrow || key == KeyCode.RightArrow || key == KeyCode.UpArrow || key == KeyCode.DownArrow))
|
|||
|
|
{
|
|||
|
|
int direction = key == KeyCode.LeftArrow || key == KeyCode.DownArrow ? -1 : 1;
|
|||
|
|
if (focusOrder[index] is Slider slider)
|
|||
|
|
slider.value += direction * (float)slider.userData;
|
|||
|
|
else if (focusOrder[index] is DropdownField dropdown)
|
|||
|
|
dropdown.index = (dropdown.index + direction + dropdown.choices.Count) % dropdown.choices.Count;
|
|||
|
|
else
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
return false;
|
|||
|
|
lastKeyFrame = Time.frameCount;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void CapturePointer(PointerDownEvent evt)
|
|||
|
|
{
|
|||
|
|
if (!settings.Bindings.IsRebinding)
|
|||
|
|
return;
|
|||
|
|
var cancel = screen.Q<Button>("settings-capture-cancel");
|
|||
|
|
var target = evt.target as VisualElement;
|
|||
|
|
if (cancel != null && target != null && (target == cancel || cancel.Contains(target)))
|
|||
|
|
settings.Bindings.Cancel();
|
|||
|
|
else
|
|||
|
|
settings.Bindings.CapturePath(SettingsInputCapture.MousePath(evt.button));
|
|||
|
|
evt.StopImmediatePropagation();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Message(string key, params object[] args) { messageKey = key; messageArgs = args; }
|
|||
|
|
|
|||
|
|
VisualElement Dialog(string titleKey)
|
|||
|
|
{
|
|||
|
|
var shade = Element(screen, "settings-dialog-shade");
|
|||
|
|
var dialog = Element(shade, "settings-dialog");
|
|||
|
|
Text(dialog, L(titleKey), "heading");
|
|||
|
|
return dialog;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static VisualElement Element(VisualElement parent, string css, string name = null)
|
|||
|
|
{
|
|||
|
|
var element = new VisualElement { name = name };
|
|||
|
|
element.AddToClassList(css);
|
|||
|
|
parent.Add(element);
|
|||
|
|
return element;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static Label Text(VisualElement parent, string value, string css)
|
|||
|
|
{
|
|||
|
|
var label = new Label(value);
|
|||
|
|
label.AddToClassList(css);
|
|||
|
|
parent.Add(label);
|
|||
|
|
return label;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Button AddButton(VisualElement parent, string label, Action action, string css)
|
|||
|
|
{
|
|||
|
|
var button = new Button(action) { text = label };
|
|||
|
|
button.AddToClassList(css);
|
|||
|
|
parent.Add(button);
|
|||
|
|
focusOrder.Add(button);
|
|||
|
|
buttonActions.Add(button, action);
|
|||
|
|
return button;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DropdownField Dropdown(VisualElement parent, string key, List<string> choices, int index, Action<int> changed)
|
|||
|
|
{
|
|||
|
|
var dropdown = new DropdownField(L(key), choices, Mathf.Clamp(index, 0, choices.Count - 1));
|
|||
|
|
dropdown.AddToClassList("settings-dropdown");
|
|||
|
|
dropdown.RegisterValueChangedCallback(evt => changed(dropdown.index));
|
|||
|
|
parent.Add(dropdown);
|
|||
|
|
focusOrder.Add(dropdown);
|
|||
|
|
return dropdown;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void Slider(VisualElement parent, string key, float value, float min, float max, float step, Action<float> changed, Func<float, string> format)
|
|||
|
|
{
|
|||
|
|
var row = Element(parent, "settings-slider-row");
|
|||
|
|
var heading = Element(row, "settings-slider-heading");
|
|||
|
|
Text(heading, L(key), "settings-label");
|
|||
|
|
var readout = Text(heading, format(value), "settings-value");
|
|||
|
|
var slider = new Slider(min, max) { value = value, name = key, userData = step };
|
|||
|
|
slider.AddToClassList("settings-slider");
|
|||
|
|
slider.RegisterValueChangedCallback(evt => { changed(evt.newValue); readout.text = format(evt.newValue); });
|
|||
|
|
row.Add(slider);
|
|||
|
|
focusOrder.Add(slider);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|