using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
namespace UnityEditor.Rendering
{
///
/// Formats the provided descriptor into a linear slider with contextual slider markers, tooltips, and icons.
///
public class LightUnitSlider
{
///
/// The that contains a
///
protected SerializedObject m_SerializedObject;
static class SliderConfig
{
public const float k_IconSeparator = 0;
public const float k_MarkerWidth = 2;
public const float k_MarkerHeight = 2;
public const float k_MarkerTooltipScale = 4;
public const float k_ThumbTooltipSize = 10;
public const float k_KnobSize = 10;
}
///
/// The styles to be used on sliders
///
protected static class SliderStyles
{
/// A with "IconButton"
public static GUIStyle k_IconButton = new GUIStyle("IconButton");
/// A with "ColorPickerSliderBackground"
public static GUIStyle k_TemperatureBorder = new GUIStyle("ColorPickerSliderBackground");
/// A with "ColorPickerHorizThumb"
public static GUIStyle k_TemperatureThumb = new GUIStyle("ColorPickerHorizThumb");
}
///
/// The
///
protected readonly LightUnitSliderUIDescriptor m_Descriptor;
///
/// Constructor with a
///
/// The
public LightUnitSlider(LightUnitSliderUIDescriptor descriptor)
{
m_Descriptor = descriptor;
}
///
/// Modifies the for this Light slider
///
/// The SerializedObject representing the serialized data to be used by this slider.
public void SetSerializedObject(SerializedObject serialized)
{
m_SerializedObject = serialized;
}
///
/// Draws the slider in a given
///
/// The to draw the slider into
/// The with the property serialized
/// The float value modified by the slider GUI
public virtual void Draw(Rect rect, SerializedProperty value, ref float floatValue)
{
BuildRects(rect, out var sliderRect, out var iconRect);
if (m_Descriptor.clampValue)
ClampValue(ref floatValue, m_Descriptor.sliderRange);
var level = CurrentRange(floatValue);
DoSlider(sliderRect, ref floatValue, m_Descriptor.sliderRange, level.value);
if (m_Descriptor.hasMarkers)
{
foreach (var r in m_Descriptor.valueRanges)
{
var markerValue = r.value.y;
var markerPosition = GetPositionOnSlider(markerValue, r.value);
var markerTooltip = r.content.tooltip;
DoSliderMarker(sliderRect, markerPosition, markerValue, markerTooltip);
}
}
var levelIconContent = level.content;
var levelRange = level.value;
DoIcon(iconRect, levelIconContent, value, floatValue, levelRange.y);
var thumbValue = floatValue;
var thumbPosition = GetPositionOnSlider(thumbValue, level.value);
var thumbTooltip = levelIconContent.tooltip;
DoThumbTooltip(sliderRect, thumbPosition, thumbValue, thumbTooltip);
}
LightUnitSliderUIRange CurrentRange(float value)
{
foreach (var l in m_Descriptor.valueRanges)
{
if (value >= l.value.x && value <= l.value.y)
{
return l;
}
}
var cautionValue = value < m_Descriptor.sliderRange.x ? m_Descriptor.sliderRange.x : m_Descriptor.sliderRange.y;
var cautionTooltip = value < m_Descriptor.sliderRange.x ? m_Descriptor.belowRangeTooltip : m_Descriptor.aboveRangeTooltip;
return LightUnitSliderUIRange.CautionRange(cautionTooltip, cautionValue);
}
void BuildRects(Rect baseRect, out Rect sliderRect, out Rect iconRect)
{
sliderRect = baseRect;
sliderRect.width -= EditorGUIUtility.singleLineHeight + SliderConfig.k_IconSeparator;
iconRect = baseRect;
iconRect.x += sliderRect.width + SliderConfig.k_IconSeparator;
iconRect.width = EditorGUIUtility.singleLineHeight;
}
void ClampValue(ref float value, Vector2 range) =>
value = Mathf.Clamp(value, range.x, range.y);
internal float ClampValue(float value) => Mathf.Clamp(value, m_Descriptor.sliderRange.x, m_Descriptor.sliderRange.y);
private static Color k_DarkThemeColor = new Color32(153, 153, 153, 255);
private static Color k_LiteThemeColor = new Color32(97, 97, 97, 255);
static Color GetMarkerColor() => EditorGUIUtility.isProSkin ? k_DarkThemeColor : k_LiteThemeColor;
void DoSliderMarker(Rect rect, float position, float value, string tooltip)
{
const float width = SliderConfig.k_MarkerWidth;
const float height = SliderConfig.k_MarkerHeight;
var markerRect = rect;
markerRect.width = width;
markerRect.height = height;
// Vertically align with slider.
markerRect.y += (EditorGUIUtility.singleLineHeight / 2f) - 1;
// Horizontally place on slider. We need to take into account the "knob" size when doing this, because position 0 and 1 starts
// at the center of the knob when it's placed at the left and right corner respectively. We don't do this adjustment when placing
// the marker at the corners (to avoid havind the slider slightly extend past the marker)
float knobSize = (position > 0f && position < 1f) ? SliderConfig.k_KnobSize : 0f;
float start = rect.x + knobSize / 2f;
float range = rect.width - knobSize;
markerRect.x = start + range * position;
// Center the marker on value.
const float halfWidth = width * 0.5f;
markerRect.x -= halfWidth;
// Clamp to the slider edges.
float min = rect.x;
float max = (rect.x + rect.width) - width;
markerRect.x = Mathf.Clamp(markerRect.x, min, max);
// Draw marker by manually drawing the rect, and an empty label with the tooltip.
EditorGUI.DrawRect(markerRect, GetMarkerColor());
// Scale the marker tooltip for easier discovery
const float markerTooltipRectScale = SliderConfig.k_MarkerTooltipScale;
var markerTooltipRect = markerRect;
markerTooltipRect.width *= markerTooltipRectScale;
markerTooltipRect.height *= markerTooltipRectScale;
markerTooltipRect.x -= (markerTooltipRect.width * 0.5f) - 1;
markerTooltipRect.y -= (markerTooltipRect.height * 0.5f) - 1;
EditorGUI.LabelField(markerTooltipRect, GetLightUnitTooltip(tooltip, value, m_Descriptor.unitName));
}
void DoIcon(Rect rect, GUIContent icon, SerializedProperty value, float floatValue, float range)
{
// Draw the context menu feedback before the icon
GUI.Box(rect, GUIContent.none, SliderStyles.k_IconButton);
var oldColor = GUI.color;
GUI.color = Color.clear;
EditorGUI.DrawTextureTransparent(rect, icon.image);
GUI.color = oldColor;
EditorGUI.LabelField(rect, GetLightUnitTooltip(icon.tooltip, range, m_Descriptor.unitName));
// Handle events for context menu
var e = Event.current;
if (e.type == EventType.MouseDown && e.button == 0)
{
if (rect.Contains(e.mousePosition))
{
var menuPosition = rect.position + rect.size;
DoContextMenu(menuPosition, value, floatValue);
e.Use();
}
}
}
void DoContextMenu(Vector2 pos, SerializedProperty value, float floatValue)
{
var menu = new GenericMenu();
foreach (var preset in m_Descriptor.valueRanges)
{
// Indicate a checkmark if the value is within this preset range.
var isInPreset = CurrentRange(floatValue).value == preset.value;
menu.AddItem(EditorGUIUtility.TrTextContent(preset.content.tooltip), isInPreset, () => SetValueToPreset(value, preset));
}
menu.DropDown(new Rect(pos, Vector2.zero));
}
void DoThumbTooltip(Rect rect, float position, float value, string tooltip)
{
const float size = SliderConfig.k_ThumbTooltipSize;
const float halfSize = SliderConfig.k_ThumbTooltipSize * 0.5f;
var thumbMarkerRect = rect;
thumbMarkerRect.width = size;
thumbMarkerRect.height = size;
// Vertically align with slider
thumbMarkerRect.y += halfSize - 1f;
// Horizontally place tooltip on the wheel,
thumbMarkerRect.x = rect.x + (rect.width - size) * position;
EditorGUI.LabelField(thumbMarkerRect, GetLightUnitTooltip(tooltip, value, m_Descriptor.unitName));
}
///
/// The serialized property for color temperature is stored in the build-in light editor, and we need to use this object to apply the update.
///
/// The value to update
/// The preset range
protected virtual void SetValueToPreset(SerializedProperty value, LightUnitSliderUIRange preset)
{
m_SerializedObject?.Update();
// Set the value to the average of the preset range.
value.floatValue = preset.presetValue;
m_SerializedObject?.ApplyModifiedProperties();
}
///
/// Gets the tooltip
///
/// The base tooltip
/// The value
/// The units
/// A well formed tooltip on a
protected virtual GUIContent GetLightUnitTooltip(string baseTooltip, float value, string unit)
{
var formatValue = value < 100 ? $"{value:n}" : $"{value:n0}";
var tooltip = $"{baseTooltip} | {formatValue} {unit}";
return new GUIContent(string.Empty, tooltip);
}
///
/// Draws the slider
///
/// The to draw the slider.
/// The current value, and also returns the modified value.
/// The ranges of the slider.
/// Not used
protected virtual void DoSlider(Rect rect, ref float value, Vector2 sliderRange, Vector2 _)
{
DoSlider(rect, ref value, sliderRange);
}
///
/// Draws a linear slider mapped to the min/max value range. Override this for different slider behavior (texture background, power).
///
/// The to draw the slider.
/// The current value, and also returns the modified value.
/// The ranges of the slider.
protected virtual void DoSlider(Rect rect, ref float value, Vector2 sliderRange)
{
value = GUI.HorizontalSlider(rect, value, sliderRange.x, sliderRange.y);
}
// Remaps value in the domain { Min0, Max0 } to { Min1, Max1 } (by default, normalizes it to (0, 1).
static float Remap(float v, float x0, float y0, float x1 = 0f, float y1 = 1f) => x1 + (v - x0) * (y1 - x1) / (y0 - x0);
///
/// Maps a light unit value onto the slider. Keeps in sync placement of markers and tooltips with the slider power.
/// Override this in case of non-linear slider.
///
/// The value to get the position at
/// The ranges of the values
/// The position
protected virtual float GetPositionOnSlider(float value, Vector2 valueRange)
{
return GetPositionOnSlider(value);
}
///
/// Maps a light unit value onto the slider. Keeps in sync placement of markers and tooltips with the slider power.
/// Override this in case of non-linear slider.
///
/// The value to get the position
/// The position on the slider
protected virtual float GetPositionOnSlider(float value)
{
return Remap(value, m_Descriptor.sliderRange.x, m_Descriptor.sliderRange.y);
}
}
}