/*
* 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 {
///
/// A custom UI slider factory class with one handle. Does not include a text field to set
/// the value.
///
public class PSliderSingle : IUIComponent {
///
/// Sets the current value of a realized slider.
///
/// The realized slider.
/// The value to set.
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);
}
///
/// If true, the default Klei track and fill will be skipped; only the handle will be
/// shown.
///
public bool CustomTrack { get; set; }
///
/// 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.
///
public Slider.Direction Direction { get; set; }
///
/// The flexible size bounds of this component.
///
public Vector2 FlexSize { get; set; }
///
/// The slider's handle color.
///
public ColorStyleSetting HandleColor { get; set; }
///
/// The size of the slider handle.
///
public float HandleSize { get; set; }
///
/// The initial slider value.
///
public float InitialValue { get; set; }
///
/// true to make the slider snap to integers, or false to allow any representable
/// floating point number in the range.
///
public bool IntegersOnly { get; set; }
///
/// 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.
///
public float MaxValue { get; set; }
///
/// The minimum value that can be set by this slider.
///
public float MinValue { get; set; }
public string Name { get; }
///
/// The action to trigger during slider dragging.
///
public PUIDelegates.OnSliderDrag OnDrag { get; set; }
///
/// The preferred length of the scrollbar. If vertical, this is the height, otherwise
/// it is the width.
///
public float PreferredLength { get; set; }
///
/// The action to trigger after the slider is changed. It is passed the realized source
/// object and new value.
///
public PUIDelegates.OnSliderChanged OnValueChanged { get; set; }
public event PUIDelegates.OnRealize OnRealize;
///
/// The tool tip text. If {0} is present, it will be formatted with the slider's
/// current value.
///
public string ToolTip { get; set; }
///
/// The size of the slider track.
///
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;
}
///
/// Adds a handler when this slider is realized.
///
/// The handler to invoke on realization.
/// This slider for call chaining.
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();
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();
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();
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.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;
}
///
/// Creates the handle component.
///
/// The parent component.
/// The sliding handle object.
private GameObject CreateHandle(GameObject slider) {
// Handle
var handle = PUIElements.CreateUI(slider, "Handle", true, PUIAnchoring.Center,
PUIAnchoring.Center);
var handleImg = handle.AddComponent();
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;
}
///
/// Sets the default Klei pink button style as this slider's foreground color style.
///
/// This button for call chaining.
public PSliderSingle SetKleiPinkStyle() {
HandleColor = PUITuning.Colors.ButtonPinkStyle;
return this;
}
///
/// 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.
///
/// This button for call chaining.
public PSliderSingle SetKleiBlueStyle() {
HandleColor = PUITuning.Colors.ButtonBlueStyle;
return this;
}
public override string ToString() {
return string.Format("PSliderSingle[Name={0}]", Name);
}
}
}