|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.IO; |
| 4 | +using System.Reflection; |
| 5 | +using UnityEngine; |
| 6 | +using UnityEngine.UIElements; |
| 7 | +using Object = UnityEngine.Object; |
| 8 | + |
| 9 | +#if REACT_VECTOR_GRAPHICS |
| 10 | +using Unity.VectorGraphics; |
| 11 | +#endif |
| 12 | + |
| 13 | +namespace ReactUnity.UIToolkit |
| 14 | +{ |
| 15 | + public class SvgElement : Image |
| 16 | + { |
| 17 | + protected static readonly string styleName = "TabButtonStyles"; |
| 18 | + protected static readonly string UxmlName = "Svg"; |
| 19 | + |
| 20 | + private static Type _vectorImageUtilsType; |
| 21 | + private static MethodInfo _makeVectorImageAsset; |
| 22 | + |
| 23 | +#if REACT_VECTOR_GRAPHICS |
| 24 | +#if !UNITY_WEBGL && !UNITY_IOS && !UNITY_IPHONE && !UNITY_WSA && !UNITY_WSA_10_0 |
| 25 | + private static MakeVectorDelegate _makeVectorHook; |
| 26 | + |
| 27 | + /// <summary> |
| 28 | + /// Delegate of MakeVector method |
| 29 | + /// </summary> |
| 30 | + /// <remarks>This method is not supported on iOS and Web, and needs to fallback to standard reflection</remarks> |
| 31 | + delegate void MakeVectorDelegate(List<VectorUtils.Geometry> geometry, uint gradientResolution, out Object asset, |
| 32 | + out Texture2D texture2D); |
| 33 | +#endif |
| 34 | +#endif |
| 35 | + |
| 36 | + [SerializeField] |
| 37 | + private string rawSvg; |
| 38 | + |
| 39 | + |
| 40 | + public SvgElement() |
| 41 | + { |
| 42 | + } |
| 43 | + |
| 44 | + public SvgElement(string svg) |
| 45 | + { |
| 46 | + RawSvg = svg; |
| 47 | + } |
| 48 | + |
| 49 | +#if REACT_VECTOR_GRAPHICS |
| 50 | + /// <summary> |
| 51 | + /// Get cacheable type of Unity.VectorGraphics.VectorImageUtils |
| 52 | + /// </summary> |
| 53 | + private static Type VectorImageUtilsType => |
| 54 | + _vectorImageUtilsType = _vectorImageUtilsType ?? typeof(VectorUtils).Assembly.GetType("Unity.VectorGraphics.VectorImageUtils"); |
| 55 | + |
| 56 | + /// <summary> |
| 57 | + /// Get cacheable method info of Unity.VectorGraphics.VectorImageUtils.MakeVectorImageAsset(...); |
| 58 | + /// </summary> |
| 59 | + /// <remarks> |
| 60 | + /// Keep all flags just to make sure this call stays relevant even if unity decides someday to expose this method :/ |
| 61 | + /// </remarks> |
| 62 | + private static MethodInfo MakeVectorImageAssetMethodInfo => |
| 63 | + _makeVectorImageAsset = _makeVectorImageAsset ?? VectorImageUtilsType.GetMethod("MakeVectorImageAsset", |
| 64 | + BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); |
| 65 | + |
| 66 | +#if !UNITY_WEBGL && !UNITY_IOS && !UNITY_IPHONE && !UNITY_WSA && !UNITY_WSA_10_0 |
| 67 | + /// <summary> |
| 68 | + /// Speedup reflection execution by wrapping method inside a delegate |
| 69 | + /// </summary> |
| 70 | + private static MakeVectorDelegate MakeVectorHook => _makeVectorHook = _makeVectorHook ?? |
| 71 | + (MakeVectorDelegate) Delegate.CreateDelegate(typeof(MakeVectorDelegate), MakeVectorImageAssetMethodInfo); |
| 72 | +#endif |
| 73 | +#endif |
| 74 | + |
| 75 | + |
| 76 | + public string RawSvg |
| 77 | + { |
| 78 | + get => rawSvg; |
| 79 | + set |
| 80 | + { |
| 81 | + if (rawSvg == value) return; |
| 82 | + |
| 83 | + rawSvg = value; |
| 84 | + RebuildSvg(); |
| 85 | + MarkDirtyRepaint(); |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + |
| 90 | + /// <summary> |
| 91 | + /// Read svg string and assign background image of current visualElement |
| 92 | + /// </summary> |
| 93 | + private void RebuildSvg() |
| 94 | + { |
| 95 | +#if REACT_VECTOR_GRAPHICS |
| 96 | + SVGParser.SceneInfo sceneInfo; |
| 97 | + using (var stream = new StringReader(RawSvg)) |
| 98 | + { |
| 99 | + sceneInfo = SVGParser.ImportSVG(stream, ViewportOptions.DontPreserve, 0, 1, 100, 100); |
| 100 | + } |
| 101 | + |
| 102 | + var stepDist = 0.5f; |
| 103 | + float samplingStepDist = 300; |
| 104 | + var maxCord = float.MaxValue; |
| 105 | + var maxTangent = Mathf.PI * 0.5f; |
| 106 | + |
| 107 | + // Automatically compute sensible tessellation options from the |
| 108 | + // vector scene's bouding box and target resolution |
| 109 | + //ComputeTessellationOptions(sceneInfo, TargetResolution, ResolutionMultiplier, out stepDist, out maxCord, out maxTangent); |
| 110 | + |
| 111 | + var tessOptions = new VectorUtils.TessellationOptions(); |
| 112 | + tessOptions.MaxCordDeviation = maxCord; |
| 113 | + tessOptions.MaxTanAngleDeviation = maxTangent; |
| 114 | + tessOptions.SamplingStepSize = 1.0f / samplingStepDist; |
| 115 | + tessOptions.StepDistance = stepDist; |
| 116 | + |
| 117 | + var geometry = VectorUtils.TessellateScene(sceneInfo.Scene, tessOptions, sceneInfo.NodeOpacity); |
| 118 | + |
| 119 | + vectorImage = GenerateVectorImageAsset(geometry); |
| 120 | + sourceRect = sceneInfo.SceneViewport; |
| 121 | +#else |
| 122 | + Debug.LogError( |
| 123 | + "Unity.VectorGraphics module is required to use SVG components"); |
| 124 | +#endif |
| 125 | + } |
| 126 | + |
| 127 | +#if REACT_VECTOR_GRAPHICS |
| 128 | + /// <summary> |
| 129 | + /// Compute tesselation for target resolution instead of relying on predefined values |
| 130 | + /// </summary> |
| 131 | + /// <param name="sceneInfo"></param> |
| 132 | + /// <param name="targetResolution"></param> |
| 133 | + /// <param name="multiplier"></param> |
| 134 | + /// <param name="stepDist"></param> |
| 135 | + /// <param name="maxCord"></param> |
| 136 | + /// <param name="maxTangent"></param> |
| 137 | + private void ComputeTessellationOptions(SVGParser.SceneInfo sceneInfo, int targetResolution, float multiplier, |
| 138 | + out float stepDist, out float maxCord, out float maxTangent) |
| 139 | + { |
| 140 | + // These tessellation options were found by trial and error to find values that made |
| 141 | + // visual sense with a variety of SVG assets. |
| 142 | + |
| 143 | + // "Pixels per Unit" doesn't make sense for UI Toolkit since it will be displayed in |
| 144 | + // a pixels space. We adjust the magic values below accordingly. |
| 145 | + var ppu = 1.0f; |
| 146 | + |
| 147 | + var bbox = VectorUtils.ApproximateSceneNodeBounds(sceneInfo.Scene.Root); |
| 148 | + var maxDim = Mathf.Max(bbox.width, bbox.height) / ppu; |
| 149 | + |
| 150 | + // The scene ratio gives a rough estimate of coverage % of the vector scene on the screen. |
| 151 | + // Higher values should result in a more dense tessellation. |
| 152 | + var sceneRatio = maxDim / (targetResolution * multiplier); |
| 153 | + |
| 154 | + stepDist = float.MaxValue; // No need for uniform step distance |
| 155 | + maxCord = Mathf.Max(0.01f, 2.0f * sceneRatio); |
| 156 | + maxTangent = Mathf.Max(0.1f, 3.0f * sceneRatio); |
| 157 | + } |
| 158 | + |
| 159 | + |
| 160 | + /// <summary> |
| 161 | + /// Generate Vector Image Asset using reflection |
| 162 | + /// </summary> |
| 163 | + /// <param name="geometry"></param> |
| 164 | + /// <returns></returns> |
| 165 | + private VectorImage GenerateVectorImageAsset(List<VectorUtils.Geometry> geometry) |
| 166 | + { |
| 167 | + var gradientResolution = 64u; |
| 168 | + |
| 169 | +#if !UNITY_WEBGL && !UNITY_IOS && !UNITY_IPHONE && !UNITY_WSA && !UNITY_WSA_10_0 |
| 170 | + MakeVectorHook(geometry, gradientResolution, out var asset, out _); |
| 171 | +#else |
| 172 | + object[] vParams = { geometry, gradientResolution, null, null }; |
| 173 | + MakeVectorImageAssetMethodInfo.Invoke(null, BindingFlags.InvokeMethod, null, vParams, null); |
| 174 | + |
| 175 | + var asset = vParams[2]; |
| 176 | +#endif |
| 177 | + |
| 178 | + if (asset == null) |
| 179 | + { |
| 180 | + Debug.LogError("UIElement asset generation failed"); |
| 181 | + return null; |
| 182 | + } |
| 183 | + |
| 184 | + return asset as VectorImage; |
| 185 | + } |
| 186 | +#endif |
| 187 | + |
| 188 | + new internal class UxmlFactory : UxmlFactory<SvgElement, UxmlTraits> |
| 189 | + { |
| 190 | + } |
| 191 | + |
| 192 | + new internal class UxmlTraits : VisualElement.UxmlTraits |
| 193 | + { |
| 194 | + private readonly UxmlStringAttributeDescription Svg = new UxmlStringAttributeDescription { name = "svg" }; |
| 195 | + |
| 196 | + public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) |
| 197 | + { |
| 198 | + base.Init(ve, bag, cc); |
| 199 | + SvgElement item = ve as SvgElement; |
| 200 | + |
| 201 | + item.RawSvg = Svg.GetValueFromBag(bag, cc); |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | +} |
0 commit comments