using System; using System.Runtime.InteropServices; using UnityEngine; namespace AOT.UI { /// Offsets authored edge layouts while keeping full-screen backgrounds unchanged. [DisallowMultipleComponent] [RequireComponent(typeof(RectTransform))] public sealed class UiSafeArea : MonoBehaviour { RectTransform _rect; Vector2 _position; bool _captured; static readonly float[] NativeInsets = new float[4]; static int _frame = -1; static Vector4 _insets; static float _nextRead; static int _width, _height; #if UNITY_EDITOR static Vector4? _preview; public static void SetPreviewInsets(Vector4? value) { _preview = value; _frame = -1; } #endif #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] static extern int AIGame_GetUiSafeInsets([Out] float[] values); #endif void OnEnable() { _rect = (RectTransform)transform; _position = _rect.anchoredPosition; _captured = true; Apply(CurrentInsets()); } void OnDisable() { if (_captured && _rect != null) _rect.anchoredPosition = _position; _captured = false; } void LateUpdate() => Apply(CurrentInsets()); void Apply(Vector4 inset) { if (!_captured || !(_rect.parent is RectTransform parent)) return; Vector2 delta = Vector2.zero; if (Mathf.Approximately(_rect.anchorMin.y, 1) && Mathf.Approximately(_rect.anchorMax.y, 1)) delta.y = -parent.rect.height * inset.w; else if (Mathf.Approximately(_rect.anchorMin.y, 0) && Mathf.Approximately(_rect.anchorMax.y, 0)) delta.y = parent.rect.height * inset.y; delta.x = parent.rect.width * (inset.x - inset.z) * .5f; _rect.anchoredPosition = _position + delta; } public static Vector4 NormalizeSafeArea(int width, int height, Rect safe) { if (width <= 0 || height <= 0 || safe.width <= 0 || safe.height <= 0) return Vector4.zero; return Clamp(new Vector4(safe.xMin / width, safe.yMin / height, 1f - safe.xMax / width, 1f - safe.yMax / height)); } static Vector4 CurrentInsets() { if (_frame == Time.frameCount) return _insets; _frame = Time.frameCount; #if UNITY_EDITOR if (_preview.HasValue) return _insets = Clamp(_preview.Value); #endif if (Time.unscaledTime < _nextRead && _width == Screen.width && _height == Screen.height) return _insets; _width = Screen.width; _height = Screen.height; _nextRead = Time.unscaledTime + .25f; _insets = NormalizeSafeArea(Screen.width, Screen.height, Screen.safeArea); #if UNITY_WEBGL && !UNITY_EDITOR if (AIGame_GetUiSafeInsets(NativeInsets) != 0) _insets = Vector4.Max(_insets, Clamp(new Vector4(NativeInsets[0], NativeInsets[1], NativeInsets[2], NativeInsets[3]))); #endif return _insets; } static Vector4 Clamp(Vector4 value) => new Vector4(Valid(value.x), Valid(value.y), Valid(value.z), Valid(value.w)); static float Valid(float value) => float.IsNaN(value) || float.IsInfinity(value) ? 0 : Mathf.Clamp(value, 0, .35f); } }