oni-priority-ux/mod/PLibUI/PSlider.cs

269 lines
8.9 KiB
C#

/*
* Copyright 2022 Peter Han
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using PeterHan.PLib.Core;
using System;
using UnityEngine;
using UnityEngine.UI;
namespace PeterHan.PLib.UI {
/// <summary>
/// A custom UI slider factory class with one handle. Does not include a text field to set
/// the value.
/// </summary>
public class PSliderSingle : IUIComponent {
/// <summary>
/// Sets the current value of a realized slider.
/// </summary>
/// <param name="realized">The realized slider.</param>
/// <param name="value">The value to set.</param>
public static void SetCurrentValue(GameObject realized, float value) {
if (realized != null && realized.TryGetComponent(out KSlider slider) && !value.
IsNaNOrInfinity())
slider.value = value.InRange(slider.minValue, slider.maxValue);
}
/// <summary>
/// If true, the default Klei track and fill will be skipped; only the handle will be
/// shown.
/// </summary>
public bool CustomTrack { get; set; }
/// <summary>
/// The direction of the slider. The slider goes from minimum to maximum value in the
/// direction indicated, i.e. LeftToRight is minimum left, maximum right.
/// </summary>
public Slider.Direction Direction { get; set; }
/// <summary>
/// The flexible size bounds of this component.
/// </summary>
public Vector2 FlexSize { get; set; }
/// <summary>
/// The slider's handle color.
/// </summary>
public ColorStyleSetting HandleColor { get; set; }
/// <summary>
/// The size of the slider handle.
/// </summary>
public float HandleSize { get; set; }
/// <summary>
/// The initial slider value.
/// </summary>
public float InitialValue { get; set; }
/// <summary>
/// true to make the slider snap to integers, or false to allow any representable
/// floating point number in the range.
/// </summary>
public bool IntegersOnly { get; set; }
/// <summary>
/// The maximum value that can be set by this slider. The slider is a linear scale, but
/// can be post-scaled by the user to nonlinear if necessary.
/// </summary>
public float MaxValue { get; set; }
/// <summary>
/// The minimum value that can be set by this slider.
/// </summary>
public float MinValue { get; set; }
public string Name { get; }
/// <summary>
/// The action to trigger during slider dragging.
/// </summary>
public PUIDelegates.OnSliderDrag OnDrag { get; set; }
/// <summary>
/// The preferred length of the scrollbar. If vertical, this is the height, otherwise
/// it is the width.
/// </summary>
public float PreferredLength { get; set; }
/// <summary>
/// The action to trigger after the slider is changed. It is passed the realized source
/// object and new value.
/// </summary>
public PUIDelegates.OnSliderChanged OnValueChanged { get; set; }
public event PUIDelegates.OnRealize OnRealize;
/// <summary>
/// The tool tip text. If {0} is present, it will be formatted with the slider's
/// current value.
/// </summary>
public string ToolTip { get; set; }
/// <summary>
/// The size of the slider track.
/// </summary>
public float TrackSize { get; set; }
public PSliderSingle() : this("SliderSingle") { }
public PSliderSingle(string name) {
CustomTrack = false;
Direction = Slider.Direction.LeftToRight;
HandleColor = PUITuning.Colors.ButtonPinkStyle;
HandleSize = 16.0f;
InitialValue = 0.5f;
IntegersOnly = false;
MaxValue = 1.0f;
MinValue = 0.0f;
Name = name;
PreferredLength = 100.0f;
TrackSize = 12.0f;
}
/// <summary>
/// Adds a handler when this slider is realized.
/// </summary>
/// <param name="onRealize">The handler to invoke on realization.</param>
/// <returns>This slider for call chaining.</returns>
public PSliderSingle AddOnRealize(PUIDelegates.OnRealize onRealize) {
OnRealize += onRealize;
return this;
}
public GameObject Build() {
// Bounds must be valid
if (MaxValue.IsNaNOrInfinity())
throw new ArgumentException(nameof(MaxValue));
if (MinValue.IsNaNOrInfinity())
throw new ArgumentException(nameof(MinValue));
// max > min
if (MaxValue <= MinValue)
throw new ArgumentOutOfRangeException(nameof(MaxValue));
// Initial value must be in range
var slider = PUIElements.CreateUI(null, Name);
bool isVertical = Direction == Slider.Direction.BottomToTop || Direction ==
Slider.Direction.TopToBottom;
var trueColor = HandleColor ?? PUITuning.Colors.ButtonBlueStyle;
slider.SetActive(false);
// Track (visual)
if (!CustomTrack) {
var trackImg = slider.AddComponent<Image>();
trackImg.sprite = isVertical ? PUITuning.Images.ScrollBorderVertical :
PUITuning.Images.ScrollBorderHorizontal;
trackImg.type = Image.Type.Sliced;
}
// Fill
var fill = PUIElements.CreateUI(slider, "Fill", true);
if (!CustomTrack) {
var fillImg = fill.AddComponent<Image>();
fillImg.sprite = isVertical ? PUITuning.Images.ScrollHandleVertical :
PUITuning.Images.ScrollHandleHorizontal;
fillImg.color = trueColor.inactiveColor;
fillImg.type = Image.Type.Sliced;
}
PUIElements.SetAnchorOffsets(fill, 1.0f, 1.0f, 1.0f, 1.0f);
// Slider component itself
var ks = slider.AddComponent<KSlider>();
ks.maxValue = MaxValue;
ks.minValue = MinValue;
ks.value = InitialValue.IsNaNOrInfinity() ? MinValue : InitialValue.InRange(
MinValue, MaxValue);
ks.wholeNumbers = IntegersOnly;
ks.handleRect = CreateHandle(slider).rectTransform();
ks.fillRect = fill.rectTransform();
ks.SetDirection(Direction, true);
if (OnValueChanged != null)
ks.onValueChanged.AddListener((value) => OnValueChanged(slider, value));
if (OnDrag != null)
ks.onDrag += () => OnDrag(slider, ks.value);
// Manually add tooltip with slider link
string tt = ToolTip;
if (!string.IsNullOrEmpty(tt)) {
var toolTip = slider.AddComponent<ToolTip>();
toolTip.OnToolTip = () => string.Format(tt, ks.value);
// Tooltip can be dynamically updated
toolTip.refreshWhileHovering = true;
}
slider.SetActive(true);
// Static layout!
slider.SetMinUISize(isVertical ? new Vector2(TrackSize, PreferredLength) :
new Vector2(PreferredLength, TrackSize));
slider.SetFlexUISize(FlexSize);
OnRealize?.Invoke(slider);
return slider;
}
/// <summary>
/// Creates the handle component.
/// </summary>
/// <param name="slider">The parent component.</param>
/// <returns>The sliding handle object.</returns>
private GameObject CreateHandle(GameObject slider) {
// Handle
var handle = PUIElements.CreateUI(slider, "Handle", true, PUIAnchoring.Center,
PUIAnchoring.Center);
var handleImg = handle.AddComponent<Image>();
handleImg.sprite = PUITuning.Images.SliderHandle;
handleImg.preserveAspect = true;
handle.SetUISize(new Vector2(HandleSize, HandleSize));
// Rotate the handle if needed (CCW)
float rot = 0.0f;
switch (Direction) {
case Slider.Direction.TopToBottom:
rot = 90.0f;
break;
case Slider.Direction.RightToLeft:
rot = 180.0f;
break;
case Slider.Direction.BottomToTop:
rot = 270.0f;
break;
default:
break;
}
if (rot != 0.0f)
handle.transform.Rotate(new Vector3(0.0f, 0.0f, rot));
return handle;
}
/// <summary>
/// Sets the default Klei pink button style as this slider's foreground color style.
/// </summary>
/// <returns>This button for call chaining.</returns>
public PSliderSingle SetKleiPinkStyle() {
HandleColor = PUITuning.Colors.ButtonPinkStyle;
return this;
}
/// <summary>
/// Sets the default Klei blue button style as this slider's foreground color style.
///
/// Note that the default slider handle has a hard coded pink color.
/// </summary>
/// <returns>This button for call chaining.</returns>
public PSliderSingle SetKleiBlueStyle() {
HandleColor = PUITuning.Colors.ButtonBlueStyle;
return this;
}
public override string ToString() {
return string.Format("PSliderSingle[Name={0}]", Name);
}
}
}