Skip to content

Instantly share code, notes, and snippets.

@nomnomab
Last active October 31, 2022 19:28
Show Gist options
  • Save nomnomab/d2d5b1841bcb3992cb88ea0c1b5133cb to your computer and use it in GitHub Desktop.
Save nomnomab/d2d5b1841bcb3992cb88ea0c1b5133cb to your computer and use it in GitHub Desktop.
A visual representation of two fill objects in a single container.
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
[ExecuteAlways]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public class PercentageFill : LayoutGroup {
[SerializeField] private RectTransform _leftBar;
[SerializeField] private RectTransform _rightBar;
[SerializeField] private Slider.Direction _direction = Slider.Direction.LeftToRight;
[SerializeField] private Vector2 _range = new Vector2(0, 100f);
[SerializeField] private float _value = 20;
public UnityEvent<float> onValueChanged;
#if UNITY_EDITOR
protected override void OnValidate() {
MapValue();
if (!Application.isPlaying) {
SetDirty();
return;
}
SetDirtyEmit();
}
#endif
private void SetDirtyEmit() {
SetDirty();
onValueChanged?.Invoke(_value);
}
public void SetValue(float newValue) {
_value = newValue;
MapValue();
SetDirtyEmit();
}
public void SetValueWithoutNotify(float newValue) {
_value = newValue;
MapValue();
SetDirty();
}
public void SetNormalizedValue(float percent) {
_value = Mathf.Lerp(_range.x, _range.y, percent);
MapValue();
SetDirtyEmit();
}
public void SetNormalizedValueWithoutNotify(float percent) {
_value = Mathf.Lerp(_range.x, _range.y, percent);
MapValue();
SetDirty();
}
private void MapValue() {
_value = Mathf.Clamp(_value, _range.x, _range.y);
}
public float GetValue(Side side) {
float left = _value;
float right = _range.y - _value;
return side switch {
Side.Left => _direction == Slider.Direction.LeftToRight ? left : right,
Side.Right => _direction == Slider.Direction.TopToBottom ? left : right,
_ => throw new ArgumentOutOfRangeException(nameof(side), side, null)
};
}
public float GetNormalizedValue(Side side) {
float value = GetValue(side);
return CalculatePercentage(value);
}
public Slider.Direction GetDirection() {
return _direction;
}
public override void CalculateLayoutInputHorizontal() {
base.CalculateLayoutInputHorizontal();
if (!_leftBar || !_rightBar) {
return;
}
if (!_direction.HasFlag(Slider.Direction.LeftToRight) && !_direction.HasFlag(Slider.Direction.RightToLeft)) {
return;
}
(float leftWidth, float rightWidth) = CalculateWidth(0);
RectTransform leftChild = _leftBar;
RectTransform rightChild = _rightBar;
if (_direction == Slider.Direction.RightToLeft) {
(leftChild, rightChild) = (rightChild, leftChild);
}
SetChildAlongAxis(leftChild, 0, 0, leftWidth);
SetChildAlongAxis(rightChild, 0, leftWidth, rightWidth);
SetChildAlongAxis(leftChild, 1, 0, rectTransform!.rect.height);
SetChildAlongAxis(rightChild, 1, 0, rectTransform!.rect.height);
}
public override void CalculateLayoutInputVertical() {
if (!_leftBar || !_rightBar) {
return;
}
if (!_direction.HasFlag(Slider.Direction.TopToBottom) && !_direction.HasFlag(Slider.Direction.BottomToTop)) {
return;
}
(float leftWidth, float rightWidth) = CalculateWidth(1);
RectTransform leftChild = _leftBar;
RectTransform rightChild = _rightBar;
if (_direction == Slider.Direction.BottomToTop) {
(leftChild, rightChild) = (rightChild, leftChild);
}
SetChildAlongAxis(leftChild, 1, 0, leftWidth);
SetChildAlongAxis(rightChild, 1, leftWidth, rightWidth);
SetChildAlongAxis(leftChild, 0, 0, rectTransform!.rect.width);
SetChildAlongAxis(rightChild, 0, 0, rectTransform!.rect.width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculatePercentage(float value) {
return (value - _range.x) / (_range.y - _range.x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (float left, float right) CalculateWidth(int axis) {
float width = axis switch {
0 => rectTransform!.rect.width,
1 => rectTransform!.rect.height,
_ => throw new NotImplementedException($"Axis {axis} is not available")
};
float percentage = Mathf.Clamp01(CalculatePercentage(_value));
return (width * percentage, width * (1f - percentage));
}
public override void SetLayoutHorizontal() { }
public override void SetLayoutVertical() { }
public enum Side {
Left,
Right
}
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(PercentageFill))]
public class PercentageFillEditor : UnityEditor.Editor {
public override void OnInspectorGUI() {
UnityEditor.SerializedProperty iter = serializedObject.FindProperty("_leftBar");
UnityEditor.SerializedProperty rangeProperty = serializedObject.FindProperty("_range");
using (var changeScope = new UnityEditor.EditorGUI.ChangeCheckScope()) {
// first field
UnityEditor.EditorGUILayout.PropertyField(iter, true);
// rest
while (iter.Next(false)) {
if (iter.name == "_value") {
Vector2 range = rangeProperty.vector2Value;
iter.floatValue = UnityEditor.EditorGUILayout.Slider(iter.displayName, iter.floatValue, range.x, range.y);
continue;
}
UnityEditor.EditorGUILayout.PropertyField(iter, true);
}
if (!changeScope.changed) {
return;
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
@nomnomab
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment