/************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Fields /************************************************************************************************************************/ /// <summary> /// Draw a <see cref="EditorGUI.FloatField(Rect, GUIContent, float)"/> with an alternate cached string when it /// is not selected (for example, "1" might become "1s" to indicate "seconds"). /// </summary> public static float DoSpecialFloatField(Rect area, GUIContent label, float value, ConversionCache <float, string> toString) { // Treat most events normally, but when repainting show a text field with the cached string. if (label != null) { if (Event.current.type != EventType.Repaint) { return(EditorGUI.FloatField(area, label, value)); } var dragArea = new Rect(area.x, area.y, EditorGUIUtility.labelWidth, area.height); EditorGUIUtility.AddCursorRect(dragArea, MouseCursor.SlideArrow); EditorGUI.TextField(area, label, toString.Convert(value)); } else { var indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; if (Event.current.type != EventType.Repaint) { value = EditorGUI.FloatField(area, value); } else { EditorGUI.TextField(area, toString.Convert(value)); } EditorGUI.indentLevel = indentLevel; } return(value); }
/************************************************************************************************************************/ /// <summary>Creates a new <see cref="CompactUnitConversionCache"/>.</summary> public CompactUnitConversionCache(string suffix) { Suffix = suffix; ApproximateSuffix = "~" + suffix; ConvertedZero = "0" + Suffix; ConvertedSmallPositive = "0" + ApproximateSuffix; ConvertedSmallNegative = "-0" + ApproximateSuffix; SuffixWidth = WidthCache.Convert(suffix); }
private void DoLoopCounterGUI(ref Rect area, float length) { if (_LoopCounterCache == null) { _LoopCounterCache = new ConversionCache <int, string>((x) => "x" + x); } string label; var normalizedTime = Target.Time / length; if (float.IsNaN(normalizedTime)) { label = "NaN"; } else { var loops = (int)Math.Abs(Target.Time / length); label = _LoopCounterCache.Convert(loops); } var width = AnimancerGUI.CalculateLabelWidth(label); var labelArea = AnimancerGUI.StealFromRight(ref area, width); GUI.Label(labelArea, label); }
/// <summary>[Animancer Extension] /// Calls <see cref="Gather(ICollection{AnimationClip}, AnimationClip)"/> for each clip in the `asset`. /// </summary> public static void GatherFromAsset(this ICollection <AnimationClip> clips, PlayableAsset asset) { if (asset == null) { return; } // We want to get the tracks out of a TimelineAsset without actually referencing that class directly // because it comes from an optional package and Animancer does not need to depend on that package. if (_TypeToGetRootTracks == null) { _TypeToGetRootTracks = new Editor.ConversionCache <Type, MethodInfo>((type) => { var method = type.GetMethod("GetRootTracks"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) { return(method); } else { return(null); } }); } var getRootTracks = _TypeToGetRootTracks.Convert(asset.GetType()); if (getRootTracks != null) { var rootTracks = getRootTracks.Invoke(asset, null); GatherAnimationClips(rootTracks as IEnumerable, clips); } }
/// <summary> /// Draws a label showing the `weight` aligned to the right side of the `area` and reduces its /// <see cref="Rect.width"/> to remove that label from its area. /// </summary> public static void DoWeightLabel(ref Rect area, float weight) { string label; if (weight < 0) { label = "-?"; } else { if (_F1Cache == null) { _F1Cache = new ConversionCache <float, string>((value) => value.ToString("F1")); } label = _F1Cache.Convert(weight); } var style = ObjectPool.GetCachedResult(() => new GUIStyle(GUI.skin.label)); if (_WeightValueWidth < 0) { _WeightValueWidth = style.CalculateWidth("0.0"); } style.normal.textColor = Color.Lerp(Color.grey, TextColor, weight); style.fontStyle = Mathf.Approximately(weight * 10, (int)(weight * 10)) ? FontStyle.Normal : FontStyle.Italic; var weightArea = StealFromRight(ref area, _WeightValueWidth); GUI.Label(weightArea, label, style); }
/************************************************************************************************************************/ /// <summary>Calculate the index of the cache to use for the given parameters.</summary> private int CalculateCacheIndex(float value, float width) { //if (value > LargeExponentialThreshold || // value < -LargeExponentialThreshold) // return 0; var valueString = value.ToStringCached(); // It the approximated string wouldn't be shorter than the original, don't approximate. if (valueString.Length < 2 + ApproximateSuffix.Length) { return(0); } if (_SuffixWidth == 0) { if (_WidthCache == null) { _WidthCache = AnimancerGUI.CreateWidthCache(EditorStyles.numberField); _FieldPadding = EditorStyles.numberField.padding.horizontal; _ApproximateSymbolWidth = _WidthCache.Convert("~") - _FieldPadding; } _SuffixWidth = _WidthCache.Convert(Suffix); } // If the field is wide enough to fit the full value, don't approximate. width -= _FieldPadding + _ApproximateSymbolWidth * 0.75f; var valueWidth = _WidthCache.Convert(valueString) + _SuffixWidth; if (valueWidth <= width) { return(0); } // If the number of allowed characters would include the full value, don't approximate. var suffixedLength = valueString.Length + Suffix.Length; var allowedCharacters = (int)(suffixedLength * width / valueWidth); if (allowedCharacters + 2 >= suffixedLength) { return(0); } return(allowedCharacters); }
/// <summary> /// Calls <see cref="GUIStyle.CalcMinMaxWidth"/> using <see cref="GUISkin.label"/> and returns the max /// width. The result is cached for efficient reuse. /// <para></para> /// This method uses the <see cref="TempContent(string, string, bool)"/>. /// </summary> public static float CalculateLabelWidth(string text) { if (_LabelWidthCache == null) { _LabelWidthCache = CreateWidthCache(GUI.skin.label); } return(_LabelWidthCache.Convert(text)); }
private static void DrawSpriteFrames(AnimationClip clip) { var keyframes = GetSpriteReferences(clip); if (keyframes == null) { return; } for (int i = 0; i < keyframes.Length; i++) { var keyframe = keyframes[i]; var sprite = keyframe.value as Sprite; if (sprite != null) { if (_FrameCache == null) { _FrameCache = new ConversionCache <int, string>( (value) => $"Frame: {value}"); _TimeCache = new ConversionCache <float, string>( (value) => $"Time: {value}s"); } var texture = sprite.texture; var area = GUILayoutUtility.GetRect(0, AnimancerGUI.LineHeight * 4); var width = area.width; var rect = sprite.rect; area.width = area.height * rect.width / rect.height; rect.x /= texture.width; rect.y /= texture.height; rect.width /= texture.width; rect.height /= texture.height; GUI.DrawTextureWithTexCoords(area, texture, rect); var offset = area.width + AnimancerGUI.StandardSpacing; area.x += offset; area.width = width - offset; area.height = AnimancerGUI.LineHeight; area.y += Mathf.Round(area.height * 0.5f); GUI.Label(area, _FrameCache.Convert(i)); AnimancerGUI.NextVerticalArea(ref area); GUI.Label(area, _TimeCache.Convert(keyframe.time)); AnimancerGUI.NextVerticalArea(ref area); GUI.Label(area, sprite.name); } } }
private void DoRulerLabelGUI(ref Rect previousArea, float time) { if (_RulerLabelStyle == null) { _RulerLabelStyle = new GUIStyle(GUI.skin.label) { padding = new RectOffset(), contentOffset = new Vector2(0, -2), alignment = TextAnchor.UpperLeft, fontSize = Mathf.CeilToInt(AnimancerGUI.LineHeight * 0.6f), } } ; var text = G2Cache.Convert(time); if (_TimeLabelWidthCache == null) { _TimeLabelWidthCache = AnimancerGUI.CreateWidthCache(_RulerLabelStyle); } var area = new Rect( SecondsToPixels(time), _Area.y, _TimeLabelWidthCache.Convert(text), _Area.height); if (area.x > _Area.x) { var tickY = _Area.yMax - TickHeight; EditorGUI.DrawRect(new Rect(area.x, tickY, 1, TickHeight), AnimancerGUI.TextColor); } if (area.xMax > _Area.xMax) { area.x = _Area.xMax - area.width; } if (area.x < 0) { area.x = 0; } if (area.x > previousArea.xMax + 2) { GUI.Label(area, text, _RulerLabelStyle); previousArea = area; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ }
/// <summary> /// Returns the `text` without any spaces if <see cref="EditorGUIUtility.wideMode"/> is false. /// Otherwise simply returns the `text` without any changes. /// </summary> public static string GetNarrowText(string text) { if (EditorGUIUtility.wideMode || string.IsNullOrEmpty(text)) { return(text); } if (_NarrowTextCache == null) { _NarrowTextCache = new ConversionCache <string, string>((str) => str.Replace(" ", "")); } return(_NarrowTextCache.Convert(text)); }
/// <summary>Draws the GUI for the `animancerEvent`.</summary> public static void Draw(ref Rect area, string name, AnimancerEvent animancerEvent) { area.height = AnimancerGUI.LineHeight; if (_EventTimeCache == null) { _EventTimeCache = new ConversionCache <float, string>((time) => float.IsNaN(time) ? "Time = Auto" : $"Time = {time.ToStringCached()}x"); } EditorGUI.LabelField(area, name, _EventTimeCache.Convert(animancerEvent.normalizedTime)); AnimancerGUI.NextVerticalArea(ref area); EditorGUI.indentLevel++; DrawInvocationList(ref area, animancerEvent.callback); EditorGUI.indentLevel--; }
/// <summary> /// Draws a label showing the `weight` aligned to the right side of the `area` and reduces its /// <see cref="Rect.width"/> to remove that label from its area. /// </summary> public static void DoWeightLabel(ref Rect area, float weight) { if (_F1Cache == null) { _F1Cache = new ConversionCache <float, string>((value) => value.ToString("F1")); _WeightLabelStyle = new GUIStyle(GUI.skin.label); _WeightValueWidth = _WeightLabelStyle.CalculateWidth("0.0"); } var weightArea = StealFromRight(ref area, _WeightValueWidth); var label = _F1Cache.Convert(weight); _WeightLabelStyle.normal.textColor = Color.Lerp(Color.grey, TextColor, weight); _WeightLabelStyle.fontStyle = Mathf.Approximately(weight * 10, (int)(weight * 10)) ? FontStyle.Normal : FontStyle.Italic; GUI.Label(weightArea, label, _WeightLabelStyle); }
/// <summary>Gathers all the animations in the `tracks`.</summary> private static void GatherAnimationClips(IEnumerable tracks, ICollection <AnimationClip> clips) { if (tracks == null) { return; } if (_TrackAssetToGetClips == null) { _TrackAssetToGetClips = new Editor.ConversionCache <Type, MethodInfo>((type) => { var method = type.GetMethod("GetClips"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) { return(method); } else { return(null); } }); _TimelineClipToAnimationClip = new Editor.ConversionCache <Type, MethodInfo>((type) => { var property = type.GetProperty("animationClip"); if (property != null && property.PropertyType == typeof(AnimationClip)) { return(property.GetGetMethod()); } else { return(null); } }); _TrackAssetToGetChildTracks = new Editor.ConversionCache <Type, MethodInfo>((type) => { var method = type.GetMethod("GetChildTracks"); if (method != null && typeof(IEnumerable).IsAssignableFrom(method.ReturnType) && method.GetParameters().Length == 0) { return(method); } else { return(null); } }); } foreach (var track in tracks) { if (track == null) { continue; } var trackType = track.GetType(); var getClips = _TrackAssetToGetClips.Convert(trackType); if (getClips != null) { var trackClips = getClips.Invoke(track, null) as IEnumerable; if (trackClips != null) { foreach (var clip in trackClips) { var getClip = _TimelineClipToAnimationClip.Convert(clip.GetType()); if (getClip != null) { clips.Gather(getClip.Invoke(clip, null) as AnimationClip); } } } } var getChildTracks = _TrackAssetToGetChildTracks.Convert(trackType); if (getChildTracks != null) { var childTracks = getChildTracks.Invoke(track, null); GatherAnimationClips(childTracks as IEnumerable, clips); } } }
/************************************************************************************************************************/ /// <summary>Draws the GUI for the `events`.</summary> public void Draw(ref Rect area, Sequence events, GUIContent label) { if (events == null) { return; } area.height = AnimancerGUI.LineHeight; var headerArea = area; const string LogLabel = "Log"; if (float.IsNaN(_LogButtonWidth)) { _LogButtonWidth = EditorStyles.miniButton.CalculateWidth(LogLabel); } var logArea = AnimancerGUI.StealFromRight(ref headerArea, _LogButtonWidth); if (GUI.Button(logArea, LogLabel, EditorStyles.miniButton)) { Debug.Log(events.DeepToString()); } _IsExpanded = EditorGUI.Foldout(headerArea, _IsExpanded, GUIContent.none, true); using (ObjectPool.Disposable.AcquireContent(out var summary, GetSummary(events))) EditorGUI.LabelField(headerArea, label, summary); AnimancerGUI.NextVerticalArea(ref area); if (!_IsExpanded) { return; } var enabled = GUI.enabled; GUI.enabled = false; EditorGUI.indentLevel++; for (int i = 0; i < events.Count; i++) { var name = events.GetName(i); if (string.IsNullOrEmpty(name)) { if (_EventNumberCache == null) { _EventNumberCache = new ConversionCache <int, string>((index) => $"Event {index}"); } name = _EventNumberCache.Convert(i); } Draw(ref area, name, events[i]); } Draw(ref area, "End Event", events.endEvent); EditorGUI.indentLevel--; GUI.enabled = enabled; }
/// <summary>Returns a string which approximates the `weight` into no more than 3 digits.</summary> private static string WeightToShortString(float weight, out bool isExact) { isExact = true; if (weight == 0) { return("0.0"); } if (weight == 1) { return("1.0"); } isExact = false; if (weight >= -0.5f && weight < 0.05f) { return("~0."); } if (weight >= 0.95f && weight < 1.05f) { return("~1."); } if (weight <= -99.5f) { return("-??"); } if (weight >= 999.5f) { return("???"); } if (_ShortWeightCache == null) { _ShortWeightCache = new ConversionCache <float, string>((value) => { if (value < -9.5f) { return($"{value:F0}"); } if (value < -0.5f) { return($"{value:F0}."); } if (value < 9.5f) { return($"{value:F1}"); } if (value < 99.5f) { return($"{value:F0}."); } return($"{value:F0}"); }); } var rounded = weight > 0 ? Mathf.Floor(weight * 10) : Mathf.Ceil(weight * 10); isExact = Mathf.Approximately(weight * 10, rounded); return(_ShortWeightCache.Convert(weight)); }
/// <summary>[Animancer Extension] /// Calls <see cref="float.ToString(string)"/> using <c>"g"</c> as the format and caches the result. /// </summary> public static string ToStringCached(this float value) => FloatToString.Convert(value);