mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
638 lines
25 KiB
C#
638 lines
25 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Numerics;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Extensions.Color4Extensions;
|
|
using osu.Framework.Extensions.ObjectExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Colour;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Graphics.Shapes;
|
|
using osu.Framework.Graphics.UserInterface;
|
|
using osu.Framework.Input;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Framework.Localisation;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Graphics.Sprites;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Localisation;
|
|
using osu.Game.Overlays;
|
|
using osuTK.Graphics;
|
|
using Vector2 = osuTK.Vector2;
|
|
|
|
namespace osu.Game.Graphics.UserInterfaceV2
|
|
{
|
|
public partial class FormSliderBar<T> : CompositeDrawable, IHasCurrentValue<T>, IFormControl
|
|
where T : struct, INumber<T>, IMinMaxValue<T>
|
|
{
|
|
public Bindable<T> Current
|
|
{
|
|
get => current.Current;
|
|
set
|
|
{
|
|
current.Current = value;
|
|
|
|
// the above `Current` set could have disabled the instantaneous bindable too,
|
|
// but we still need to copy out `Default` manually,
|
|
// so lift that disable for a second and then restore it
|
|
currentNumberInstantaneous.Disabled = false;
|
|
currentNumberInstantaneous.Default = current.Default;
|
|
currentNumberInstantaneous.Disabled = current.Disabled;
|
|
}
|
|
}
|
|
|
|
private readonly BindableNumberWithCurrent<T> current = new BindableNumberWithCurrent<T>();
|
|
|
|
private readonly BindableNumber<T> currentNumberInstantaneous = new BindableNumber<T>();
|
|
private readonly InnerSlider slider;
|
|
|
|
/// <summary>
|
|
/// Whether changes to the value should instantaneously transfer to outside bindables.
|
|
/// If <see langword="false"/>, the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider commit.
|
|
/// </summary>
|
|
public bool TransferValueOnCommit { get; set; }
|
|
|
|
private CompositeDrawable? tabbableContentContainer;
|
|
|
|
public CompositeDrawable? TabbableContentContainer
|
|
{
|
|
set
|
|
{
|
|
tabbableContentContainer = value;
|
|
|
|
if (textBox.IsNotNull())
|
|
textBox.TabbableContentContainer = tabbableContentContainer;
|
|
}
|
|
}
|
|
|
|
private LocalisableString caption;
|
|
|
|
/// <summary>
|
|
/// Caption describing this slider bar, displayed on top of the controls.
|
|
/// </summary>
|
|
public LocalisableString Caption
|
|
{
|
|
get => caption;
|
|
set
|
|
{
|
|
caption = value;
|
|
|
|
if (IsLoaded)
|
|
captionText.Caption = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
|
/// </summary>
|
|
public LocalisableString HintText { get; init; }
|
|
|
|
/// <summary>
|
|
/// A custom step value for each key press which actuates a change on this control.
|
|
/// </summary>
|
|
public float KeyboardStep
|
|
{
|
|
get => slider.KeyboardStep;
|
|
set => slider.KeyboardStep = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether to format the tooltip as a percentage or the actual value.
|
|
/// </summary>
|
|
public bool DisplayAsPercentage { get; init; }
|
|
|
|
/// <summary>
|
|
/// Whether sound effects should play when adjusting this slider.
|
|
/// </summary>
|
|
public bool PlaySamplesOnAdjust { get; init; } = true;
|
|
|
|
/// <summary>
|
|
/// The string formatting function to use for the value label.
|
|
/// </summary>
|
|
public Func<T, LocalisableString> LabelFormat { get; init; }
|
|
|
|
/// <summary>
|
|
/// The string formatting function to use for the slider's tooltip text.
|
|
/// If not provided, <see cref="LabelFormat"/> is used.
|
|
/// </summary>
|
|
public Func<T, LocalisableString> TooltipFormat { get; init; }
|
|
|
|
private FormControlBackground background = null!;
|
|
private Box flashLayer = null!;
|
|
private FormTextBox.InnerTextBox textBox = null!;
|
|
private OsuSpriteText valueLabel = null!;
|
|
private FormFieldCaption captionText = null!;
|
|
private IFocusManager focusManager = null!;
|
|
|
|
[Resolved]
|
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
|
|
|
private readonly Bindable<Language> currentLanguage = new Bindable<Language>();
|
|
|
|
public bool TakeFocus() => GetContainingFocusManager()?.ChangeFocus(textBox) == true;
|
|
|
|
public FormSliderBar()
|
|
{
|
|
LabelFormat ??= defaultLabelFormat;
|
|
TooltipFormat ??= v => LabelFormat(v);
|
|
|
|
// the reason why this slider is created in constructor rather than in BDL like the rest of drawable hierarchy is as follows:
|
|
// `SliderBar<T>` (the base framework class for all sliders) also does its `Current` initialisation in its ctor.
|
|
// if that precedent is not followed, it is possible to run into a crippling issue
|
|
// when a `FormSliderBar` instance is on a screen and said screen is exited before said instance's `LoadComplete()` is invoked.
|
|
// in that case, the screen exit will unbind the `InnerSlider`'s internal bindings & value change callbacks:
|
|
// https://github.com/ppy/osu-framework/blob/23ac694fa2c342ce39f563c8a1b975119249d5e9/osu.Framework/Screens/ScreenStack.cs#L353
|
|
// the callbacks are supposed to propagate `{Min,Max}Value` from `Current` to its internal `currentNumberInstantaneous` bindable:
|
|
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L62-L63
|
|
// thus, the callbacks getting unbound by the screen exit prevents `{Min,Max}Value` from ever correctly propagating, which finally causes a crash at
|
|
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L112 ->
|
|
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L88-L92.
|
|
// moving the slider creation & binding to constructor does little to fix the issue other than to make it less likely to be hit.
|
|
slider = new InnerSlider
|
|
{
|
|
Current = currentNumberInstantaneous,
|
|
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
|
TooltipFormat = s => TooltipFormat(s),
|
|
DisplayAsPercentage = DisplayAsPercentage,
|
|
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
|
ResetToDefault = () =>
|
|
{
|
|
if (!IsDisabled)
|
|
SetDefault();
|
|
}
|
|
};
|
|
|
|
current.ValueChanged += e =>
|
|
{
|
|
currentNumberInstantaneous.Value = e.NewValue;
|
|
ValueChanged?.Invoke();
|
|
};
|
|
|
|
current.MinValueChanged += v => currentNumberInstantaneous.MinValue = v;
|
|
current.MaxValueChanged += v => currentNumberInstantaneous.MaxValue = v;
|
|
current.PrecisionChanged += v => currentNumberInstantaneous.Precision = v;
|
|
current.DisabledChanged += disabled =>
|
|
{
|
|
if (disabled)
|
|
{
|
|
// revert any changes before disabling to make sure we are in a consistent state.
|
|
currentNumberInstantaneous.Value = current.Value;
|
|
}
|
|
|
|
currentNumberInstantaneous.Disabled = disabled;
|
|
if (IsLoaded)
|
|
updateState();
|
|
};
|
|
|
|
current.CopyTo(currentNumberInstantaneous);
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load(OsuColour colours, OsuGame? game)
|
|
{
|
|
RelativeSizeAxes = Axes.X;
|
|
AutoSizeAxes = Axes.Y;
|
|
|
|
Masking = true;
|
|
CornerRadius = 5;
|
|
CornerExponent = 2.5f;
|
|
|
|
InternalChildren = new Drawable[]
|
|
{
|
|
background = new FormControlBackground(),
|
|
flashLayer = new Box
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Colour = Colour4.Transparent,
|
|
},
|
|
new Container
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Padding = new MarginPadding
|
|
{
|
|
Vertical = 5,
|
|
Left = 9,
|
|
Right = 5,
|
|
},
|
|
Children = new Drawable[]
|
|
{
|
|
new FillFlowContainer
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Direction = FillDirection.Vertical,
|
|
Spacing = new Vector2(0f, 4f),
|
|
Width = 0.5f,
|
|
Padding = new MarginPadding
|
|
{
|
|
Right = 10,
|
|
Vertical = 4,
|
|
},
|
|
Children = new Drawable[]
|
|
{
|
|
captionText = new FormFieldCaption
|
|
{
|
|
TooltipText = HintText,
|
|
},
|
|
new Container
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Children = new Drawable[]
|
|
{
|
|
textBox = new FormNumberBox.InnerNumberBox(allowDecimals: true)
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
// the textbox is hidden when the control is unfocused,
|
|
// but clicking on the label should reach the textbox,
|
|
// therefore make it always present.
|
|
AlwaysPresent = true,
|
|
CommitOnFocusLost = true,
|
|
SelectAllOnFocus = true,
|
|
OnInputError = () =>
|
|
{
|
|
flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3);
|
|
flashLayer.FadeOutFromOne(200, Easing.OutQuint);
|
|
},
|
|
TabbableContentContainer = tabbableContentContainer,
|
|
},
|
|
valueLabel = new TruncatingSpriteText
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
Padding = new MarginPadding { Right = 5 },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
slider.With(s =>
|
|
{
|
|
s.Anchor = Anchor.CentreRight;
|
|
s.Origin = Anchor.CentreRight;
|
|
s.RelativeSizeAxes = Axes.X;
|
|
s.Width = 0.5f;
|
|
})
|
|
},
|
|
},
|
|
};
|
|
|
|
if (game != null)
|
|
currentLanguage.BindTo(game.CurrentLanguage);
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
captionText.Caption = caption;
|
|
|
|
focusManager = GetContainingFocusManager()!;
|
|
|
|
textBox.Focused.BindValueChanged(_ => updateState());
|
|
textBox.OnCommit += textCommitted;
|
|
textBox.Current.BindValueChanged(textChanged);
|
|
|
|
slider.IsDragging.BindValueChanged(_ => updateState());
|
|
slider.Focused.BindValueChanged(_ => updateState());
|
|
|
|
currentLanguage.BindValueChanged(_ => Schedule(updateValueDisplay));
|
|
currentNumberInstantaneous.BindDisabledChanged(_ => updateState());
|
|
currentNumberInstantaneous.BindValueChanged(e =>
|
|
{
|
|
if (!TransferValueOnCommit)
|
|
current.Value = e.NewValue;
|
|
|
|
updateState();
|
|
updateValueDisplay();
|
|
}, true);
|
|
}
|
|
|
|
private bool updatingFromTextBox;
|
|
|
|
private void textChanged(ValueChangedEvent<string> change)
|
|
{
|
|
tryUpdateSliderFromTextBox();
|
|
}
|
|
|
|
private void textCommitted(TextBox t, bool isNew)
|
|
{
|
|
tryUpdateSliderFromTextBox();
|
|
// If the attempted update above failed, restore text box to match the slider.
|
|
currentNumberInstantaneous.TriggerChange();
|
|
current.Value = currentNumberInstantaneous.Value;
|
|
|
|
background.Flash();
|
|
}
|
|
|
|
private void tryUpdateSliderFromTextBox()
|
|
{
|
|
updatingFromTextBox = true;
|
|
|
|
try
|
|
{
|
|
switch (currentNumberInstantaneous)
|
|
{
|
|
case Bindable<int> bindableInt:
|
|
bindableInt.Value = int.Parse(textBox.Current.Value);
|
|
break;
|
|
|
|
case Bindable<double> bindableDouble:
|
|
bindableDouble.Value = double.Parse(textBox.Current.Value) / (DisplayAsPercentage ? 100 : 1);
|
|
break;
|
|
|
|
case Bindable<float> bindableFloat:
|
|
bindableFloat.Value = float.Parse(textBox.Current.Value) / (DisplayAsPercentage ? 100 : 1);
|
|
break;
|
|
|
|
default:
|
|
currentNumberInstantaneous.Parse(textBox.Current.Value, CultureInfo.CurrentCulture);
|
|
break;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore parsing failures.
|
|
// sane state will eventually be restored by a commit (either explicit, or implicit via focus loss).
|
|
}
|
|
|
|
updatingFromTextBox = false;
|
|
}
|
|
|
|
protected override bool OnHover(HoverEvent e)
|
|
{
|
|
updateState();
|
|
return true;
|
|
}
|
|
|
|
protected override void OnHoverLost(HoverLostEvent e)
|
|
{
|
|
base.OnHoverLost(e);
|
|
updateState();
|
|
}
|
|
|
|
protected override bool OnClick(ClickEvent e)
|
|
{
|
|
if (!Current.Disabled)
|
|
focusManager.ChangeFocus(textBox);
|
|
return true;
|
|
}
|
|
|
|
private void updateState()
|
|
{
|
|
bool childHasFocus = slider.Focused.Value || textBox.Focused.Value;
|
|
|
|
textBox.ReadOnly = currentNumberInstantaneous.Disabled;
|
|
textBox.Alpha = textBox.Focused.Value ? 1 : 0;
|
|
valueLabel.Alpha = textBox.Focused.Value ? 0 : 1;
|
|
|
|
captionText.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content2;
|
|
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
|
valueLabel.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
|
|
|
if (Current.Disabled)
|
|
background.VisualStyle = VisualStyle.Disabled;
|
|
else if (childHasFocus)
|
|
background.VisualStyle = VisualStyle.Focused;
|
|
else if (IsHovered || slider.IsDragging.Value)
|
|
background.VisualStyle = VisualStyle.Hovered;
|
|
else
|
|
background.VisualStyle = VisualStyle.Normal;
|
|
}
|
|
|
|
private void updateValueDisplay()
|
|
{
|
|
if (updatingFromTextBox) return;
|
|
|
|
if (DisplayAsPercentage)
|
|
{
|
|
double floatValue = double.CreateTruncating(currentNumberInstantaneous.Value);
|
|
|
|
// if `DisplayAsPercentage` is true and `T` is not `int`, then `Current` / `currentNumberInstantaneous` are in the range of [0,1].
|
|
// in the text box, we want to show the percentage in the range of [0,100], but without the percentage sign.
|
|
// the reason we don't want a percentage sign is that `TextBox`es with numerical `TextInputType`s
|
|
// have framework-side limitations on which characters they accept and they won't accept a percentage sign.
|
|
//
|
|
// therefore, the instantaneous value needs to be multiplied by 100 if it's not `int`, so that `ToStandardFormattedString()`,
|
|
// which is called *intentionally* without `asPercentage: true` specified as to not emit the percentage sign, spits out the correct number.
|
|
//
|
|
// additionally note that `ToStandardFormattedString()`, when called with `asPercentage: true` specified, does the *inverse* of this,
|
|
// which is that it brings the formatted number *into* the [0,1] range,
|
|
// because .NET number formatting *automatically* multiplies the formatted number by 100 when it is told to stringify a number as percentage
|
|
// (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings#the--custom-specifier-3).
|
|
// it's all very confusing.
|
|
if (currentNumberInstantaneous.Value is not int)
|
|
floatValue *= 100;
|
|
|
|
textBox.Text = floatValue.ToStandardFormattedString(Math.Max(0, OsuSliderBar<T>.MAX_DECIMAL_DIGITS - 2));
|
|
}
|
|
else
|
|
textBox.Text = currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS);
|
|
|
|
valueLabel.Text = LabelFormat(currentNumberInstantaneous.Value);
|
|
}
|
|
|
|
private LocalisableString defaultLabelFormat(T value) => currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS, DisplayAsPercentage);
|
|
|
|
public partial class InnerSlider : OsuSliderBar<T>
|
|
{
|
|
public BindableBool Focused { get; } = new BindableBool();
|
|
|
|
public BindableBool IsDragging { get; } = new BindableBool();
|
|
|
|
public Action? ResetToDefault { get; init; }
|
|
|
|
public Action? OnCommit { get; init; }
|
|
|
|
public sealed override LocalisableString TooltipText => base.TooltipText;
|
|
|
|
public required Func<T, LocalisableString> TooltipFormat { get; init; }
|
|
|
|
private Box leftBox = null!;
|
|
private Box rightBox = null!;
|
|
private InnerSliderNub nub = null!;
|
|
public const float NUB_WIDTH = 10;
|
|
|
|
[Resolved]
|
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
Height = 40;
|
|
RelativeSizeAxes = Axes.X;
|
|
RangePadding = NUB_WIDTH / 2;
|
|
|
|
Children = new Drawable[]
|
|
{
|
|
new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Masking = true,
|
|
CornerRadius = 5,
|
|
Children = new Drawable[]
|
|
{
|
|
leftBox = new Box
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Anchor = Anchor.CentreLeft,
|
|
Origin = Anchor.CentreLeft,
|
|
},
|
|
rightBox = new Box
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Anchor = Anchor.CentreRight,
|
|
Origin = Anchor.CentreRight,
|
|
},
|
|
},
|
|
},
|
|
new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Padding = new MarginPadding { Horizontal = RangePadding, },
|
|
Child = nub = new InnerSliderNub
|
|
{
|
|
ResetToDefault = ResetToDefault,
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
Current.BindDisabledChanged(_ => updateState(), true);
|
|
FinishTransforms(true);
|
|
}
|
|
|
|
protected override void UpdateAfterChildren()
|
|
{
|
|
base.UpdateAfterChildren();
|
|
leftBox.Width = Math.Clamp(RangePadding + nub.DrawPosition.X, 0, Math.Max(0, DrawWidth)) / DrawWidth;
|
|
rightBox.Width = Math.Clamp(DrawWidth - nub.DrawPosition.X - RangePadding, 0, Math.Max(0, DrawWidth)) / DrawWidth;
|
|
}
|
|
|
|
protected override bool OnDragStart(DragStartEvent e)
|
|
{
|
|
bool dragging = base.OnDragStart(e);
|
|
IsDragging.Value = dragging;
|
|
updateState();
|
|
return dragging;
|
|
}
|
|
|
|
protected override void OnDragEnd(DragEndEvent e)
|
|
{
|
|
base.OnDragEnd(e);
|
|
IsDragging.Value = false;
|
|
updateState();
|
|
}
|
|
|
|
protected override bool OnHover(HoverEvent e)
|
|
{
|
|
updateState();
|
|
return base.OnHover(e);
|
|
}
|
|
|
|
protected override void OnHoverLost(HoverLostEvent e)
|
|
{
|
|
updateState();
|
|
base.OnHoverLost(e);
|
|
}
|
|
|
|
protected override void OnFocus(FocusEvent e)
|
|
{
|
|
updateState();
|
|
Focused.Value = true;
|
|
base.OnFocus(e);
|
|
}
|
|
|
|
protected override void OnFocusLost(FocusLostEvent e)
|
|
{
|
|
updateState();
|
|
Focused.Value = false;
|
|
base.OnFocusLost(e);
|
|
}
|
|
|
|
private void updateState()
|
|
{
|
|
rightBox.Colour = colourProvider.Background5;
|
|
|
|
Color4 leftColour = colourProvider.Light4;
|
|
Color4 nubColour;
|
|
|
|
if (IsHovered || HasFocus || IsDragged)
|
|
nubColour = colourProvider.Highlight1;
|
|
else
|
|
nubColour = colourProvider.Highlight1.Darken(0.1f);
|
|
|
|
if (Current.Disabled)
|
|
{
|
|
nubColour = nubColour.Darken(0.4f);
|
|
leftColour = leftColour.Darken(0.4f);
|
|
}
|
|
|
|
leftBox.FadeColour(leftColour, 250, Easing.OutQuint);
|
|
nub.FadeColour(nubColour, 250, Easing.OutQuint);
|
|
}
|
|
|
|
protected override void UpdateValue(float value)
|
|
{
|
|
nub.MoveToX(value, 250, Easing.OutElasticQuarter);
|
|
}
|
|
|
|
protected override bool Commit()
|
|
{
|
|
bool result = base.Commit();
|
|
|
|
if (result)
|
|
OnCommit?.Invoke();
|
|
|
|
return result;
|
|
}
|
|
|
|
protected sealed override LocalisableString GetTooltipText(T value) => TooltipFormat(value);
|
|
}
|
|
|
|
public partial class InnerSliderNub : Circle
|
|
{
|
|
public Action? ResetToDefault { get; set; }
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
CornerExponent = 2.5f;
|
|
Width = InnerSlider.NUB_WIDTH;
|
|
RelativeSizeAxes = Axes.Y;
|
|
RelativePositionAxes = Axes.X;
|
|
Origin = Anchor.TopCentre;
|
|
}
|
|
|
|
protected override bool OnClick(ClickEvent e) => true; // must be handled for double click handler to ever fire
|
|
|
|
protected override bool OnDoubleClick(DoubleClickEvent e)
|
|
{
|
|
ResetToDefault?.Invoke();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<LocalisableString> FilterTerms => new[] { Caption, HintText };
|
|
|
|
public event Action? ValueChanged;
|
|
|
|
public bool IsDefault => Current.IsDefault;
|
|
|
|
public void SetDefault() => Current.SetDefault();
|
|
|
|
public bool IsDisabled => Current.Disabled;
|
|
|
|
public float MainDrawHeight => DrawHeight;
|
|
}
|
|
}
|