123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- // Inspector Gadgets // https://kybernetik.com.au/animancer // Copyright 2017-2024 Kybernetik //
- #if UNITY_EDITOR && UNITY_IMGUI
- using Animancer.Editor;
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using UnityEditor;
- namespace Animancer.Units.Editor
- //namespace InspectorGadgets.Editor
- {
- /// <summary>[Editor-Only]
- /// A system for formatting floats as strings that fit into a limited area and storing the results so they can be
- /// reused to minimise the need for garbage collection, particularly for string construction.
- /// </summary>
- ///
- /// <remarks>
- /// This system only affects the display value. Once you select a field, it shows its actual value.
- /// <para></para>
- /// <strong>Example:</strong>
- /// With <c>"x"</c> as the suffix:
- /// <list type="bullet">
- /// <item><c>1.111111</c> could instead show <c>1.111~x</c>.</item>
- /// <item><c>0.00001234567</c> would normally show <c>1.234567e-05</c>, but with this it instead shows <c>0~x</c>
- /// because very small values generally aren't useful.</item>
- /// <item><c>99999999</c> shows <c>1e+08x</c> because very large values are already approximations and trying to
- /// format them correctly would be very difficult.</item>
- /// </list>
- /// </remarks>
- ///
- /// https://kybernetik.com.au/animancer/api/Animancer.Units.Editor/CompactUnitConversionCache
- /// https://kybernetik.com.au/inspector-gadgets/api/InspectorGadgets.Editor/CompactUnitConversionCache
- ///
- public class CompactUnitConversionCache
- {
- /************************************************************************************************************************/
- /// <summary>Should the fields show approximations if the value is too long for the GUI?</summary>
- public static bool ShowApproximations
- => AnimancerSettingsGroup<AnimationTimeAttributeSettings>.Instance.showApproximations;
- // => PropertyDrawers.TransformPropertyDrawer.ShowApproximations;
- /************************************************************************************************************************/
- /// <summary>The suffix added to the end of each value.</summary>
- public readonly string Suffix;
- /// <summary>The <see cref="Suffix"/> with a <c>~</c> before it to indicate an approximation.</summary>
- public readonly string ApproximateSuffix;
- /// <summary>The value <c>0</c> with the <see cref="Suffix"/>.</summary>
- public readonly string ConvertedZero;
- /// <summary>The value <c>0</c> with the <see cref="ApproximateSuffix"/>.</summary>
- public readonly string ConvertedSmallPositive;
- /// <summary>The value <c>-0</c> with the <see cref="ApproximateSuffix"/>.</summary>
- public readonly string ConvertedSmallNegative;
- /// <summary>The pixel width of the <see cref="Suffix"/> when drawn by <see cref="EditorStyles.numberField"/>.</summary>
- public float _SuffixWidth;
- /// <summary>The caches for each character count.</summary>
- /// <remarks><c>this[x]</c> is a cache that outputs strings with <c>x</c> characters.</remarks>
- private readonly List<ConversionCache<float, string>>
- Caches = new();
- /************************************************************************************************************************/
- /// <summary>Strings mapped to the width they would require for a <see cref="EditorStyles.numberField"/>.</summary>
- private static ConversionCache<string, float> _WidthCache;
- /// <summary>Padding around the text in a <see cref="EditorStyles.numberField"/>.</summary>
- public static float _FieldPadding;
- /// <summary>The pixel width of the <c>~</c> character when drawn by <see cref="EditorStyles.numberField"/>.</summary>
- public static float _ApproximateSymbolWidth;
- /// <summary>The character(s) used to separate decimal values in the current OS language.</summary>
- public static string _DecimalSeparator;
- /// <summary>Values smaller than this become <c>0~</c> or <c>-0~</c>.</summary>
- public const float
- SmallExponentialThreshold = 0.0001f;
- /// <summary>Values larger than this can't be approximated.</summary>
- public const float
- LargeExponentialThreshold = 9999999f;
- /************************************************************************************************************************/
- /// <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;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Returns a cached string representing the `value` trimmed to fit within the `width` (if necessary) and with
- /// the <see cref="Suffix"/> added on the end.
- /// </summary>
- public string Convert(float value, float width)
- {
- if (value == 0)
- return ConvertedZero;
- if (!ShowApproximations)
- return GetCache(0).Convert(value);
- if (value < SmallExponentialThreshold &&
- value > -SmallExponentialThreshold)
- return value > 0 ? ConvertedSmallPositive : ConvertedSmallNegative;
- var index = CalculateCacheIndex(value, width);
- return GetCache(index).Convert(value);
- }
- /************************************************************************************************************************/
- /// <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 = ConversionCache.CreateWidthCache(EditorStyles.numberField);
- _FieldPadding = EditorStyles.numberField.padding.horizontal;
- _ApproximateSymbolWidth = _WidthCache.Convert("~") - _FieldPadding;
- }
- if (!string.IsNullOrWhiteSpace(Suffix))
- _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>Creates and returns a cache for the specified `characterCount`.</summary>
- private ConversionCache<float, string> GetCache(int characterCount)
- {
- while (Caches.Count <= characterCount)
- Caches.Add(null);
- var cache = Caches[characterCount];
- if (cache == null)
- {
- if (characterCount == 0)
- {
- cache = new((value) =>
- {
- return value.ToStringCached() + Suffix;
- });
- }
- else
- {
- cache = new((value) =>
- {
- var valueString = value.ToStringCached();
- if (value > LargeExponentialThreshold ||
- value < -LargeExponentialThreshold)
- goto IsExponential;
- _DecimalSeparator ??= CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
- var decimalIndex = valueString.IndexOf(_DecimalSeparator);
- if (decimalIndex < 0 || decimalIndex > characterCount)
- goto IsExponential;
- // Not exponential.
- return valueString[..characterCount] + ApproximateSuffix;
- IsExponential:
- var digits = Math.Max(0, characterCount - ApproximateSuffix.Length - 1);
- var format = GetExponentialFormat(digits);
- valueString = value.ToString(format);
- TrimExponential(ref valueString);
- return valueString + Suffix;
- });
- }
- Caches[characterCount] = cache;
- }
- return cache;
- }
- /************************************************************************************************************************/
- private static List<string> _ExponentialFormats;
- /// <summary>Returns a format string to include the specified number of `digits` in an exponential number.</summary>
- public static string GetExponentialFormat(int digits)
- {
- _ExponentialFormats ??= new();
- while (_ExponentialFormats.Count <= digits)
- _ExponentialFormats.Add("g" + _ExponentialFormats.Count);
- return _ExponentialFormats[digits];
- }
- /************************************************************************************************************************/
- private static void TrimExponential(ref string valueString)
- {
- var length = valueString.Length;
- if (length <= 4 ||
- valueString[length - 4] != 'e' ||
- valueString[length - 2] != '0')
- return;
- valueString =
- valueString[..(length - 2)] +
- valueString[length - 1];
- }
- /************************************************************************************************************************/
- }
- }
- #endif
|