313 lines
13 KiB
C#
313 lines
13 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
|
|
namespace UnityEditor.Rendering
|
|
{
|
|
/// <summary>
|
|
/// Formats the provided descriptor into a linear slider with contextual slider markers, tooltips, and icons.
|
|
/// </summary>
|
|
public class LightUnitSlider
|
|
{
|
|
/// <summary>
|
|
/// The <see cref="SerializedObject"/> that contains a <see cref="Light"/>
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The styles to be used on sliders
|
|
/// </summary>
|
|
protected static class SliderStyles
|
|
{
|
|
/// <summary> A <see cref="GUIStyle"/> with "IconButton" </summary>
|
|
public static GUIStyle k_IconButton = new GUIStyle("IconButton");
|
|
/// <summary> A <see cref="GUIStyle"/> with "ColorPickerSliderBackground" </summary>
|
|
public static GUIStyle k_TemperatureBorder = new GUIStyle("ColorPickerSliderBackground");
|
|
/// <summary> A <see cref="GUIStyle"/> with "ColorPickerHorizThumb" </summary>
|
|
public static GUIStyle k_TemperatureThumb = new GUIStyle("ColorPickerHorizThumb");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="LightUnitSliderUIDescriptor"/>
|
|
/// </summary>
|
|
protected readonly LightUnitSliderUIDescriptor m_Descriptor;
|
|
|
|
/// <summary>
|
|
/// Constructor with a <see cref="LightUnitSliderUIDescriptor"/>
|
|
/// </summary>
|
|
/// <param name="descriptor">The <see cref="LightUnitSliderUIDescriptor"/></param>
|
|
public LightUnitSlider(LightUnitSliderUIDescriptor descriptor)
|
|
{
|
|
m_Descriptor = descriptor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Modifies the <see cref="SerializedObject"/> for this Light slider
|
|
/// </summary>
|
|
/// <param name="serialized"></param>
|
|
public void SetSerializedObject(SerializedObject serialized)
|
|
{
|
|
m_SerializedObject = serialized;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws the slider in a given <see cref="Rect"/>
|
|
/// </summary>
|
|
/// <param name="rect">The <see cref="Rect"/> to draw the slider into</param>
|
|
/// <param name="value">The <see cref="SerializedProperty"/> with the property serialized</param>
|
|
/// <param name="floatValue">The float value modified by the slider GUI</param>
|
|
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);
|
|
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value">The value to update</param>
|
|
/// <param name="preset">The preset range</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the tooltip
|
|
/// </summary>
|
|
/// <param name="baseTooltip">The base tooltip</param>
|
|
/// <param name="value">The value</param>
|
|
/// <param name="unit">The units</param>
|
|
/// <returns>A well formed tooltip on a <see cref="GUIContent"/></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws the slider
|
|
/// </summary>
|
|
/// <param name="rect">The <see cref="Rect"/> to draw the slider.</param>
|
|
/// <param name="value">The current value, and also returns the modified value.</param>
|
|
/// <param name="sliderRange">The ranges of the slider.</param>
|
|
/// <param name="_">Not used</param>
|
|
protected virtual void DoSlider(Rect rect, ref float value, Vector2 sliderRange, Vector2 _)
|
|
{
|
|
DoSlider(rect, ref value, sliderRange);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a linear slider mapped to the min/max value range. Override this for different slider behavior (texture background, power).
|
|
/// </summary>
|
|
/// <param name="rect">The <see cref="Rect"/> to draw the slider.</param>
|
|
/// <param name="value">The current value, and also returns the modified value.</param>
|
|
/// <param name="sliderRange">The ranges of the slider.</param>
|
|
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);
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value">The value to get the position at</param>
|
|
/// <param name="valueRange">The ranges of the values</param>
|
|
/// <returns>The position</returns>
|
|
protected virtual float GetPositionOnSlider(float value, Vector2 valueRange)
|
|
{
|
|
return GetPositionOnSlider(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value">The value to get the position</param>
|
|
/// <returns>The position on the slider</returns>
|
|
protected virtual float GetPositionOnSlider(float value)
|
|
{
|
|
return Remap(value, m_Descriptor.sliderRange.x, m_Descriptor.sliderRange.y);
|
|
}
|
|
}
|
|
}
|