using UnityEngine; using UnityEngine.UIElements; namespace Uigenx { /// /// Keeps its content inside by padding itself. Screen space is /// bottom left origin and UI Toolkit is top left, so the vertical insets are swapped here. /// [UxmlElement] public partial class SafeAreaRoot : VisualElement { public const string UssClassName = "ugx-safe-area-root"; /// How often the safe area is re-read, in milliseconds. Rotation has no event. public const long PollIntervalMs = 500; [UxmlAttribute("apply-safe-area")] public bool ApplySafeArea { get => _applySafeArea; set { if (_applySafeArea == value) return; _applySafeArea = value; if (!_applySafeArea) ClearPadding(); else Apply(); } } bool _applySafeArea = true; Rect _lastSafeArea; int _lastScreenWidth; int _lastScreenHeight; float _lastWidth; float _lastHeight; IVisualElementScheduledItem _poll; public SafeAreaRoot() { AddToClassList(UssClassName); style.flexGrow = 1f; RegisterCallback(OnGeometryChanged); RegisterCallback(OnAttach); RegisterCallback(OnDetach); } void OnAttach(AttachToPanelEvent evt) { _poll = schedule.Execute(Apply).Every(PollIntervalMs); Apply(); } void OnDetach(DetachFromPanelEvent evt) { _poll?.Pause(); _poll = null; } void OnGeometryChanged(GeometryChangedEvent evt) => Apply(); void Apply() { if (!_applySafeArea) return; int screenWidth = Screen.width; int screenHeight = Screen.height; if (screenWidth <= 0 || screenHeight <= 0) return; float width = resolvedStyle.width; float height = resolvedStyle.height; if (float.IsNaN(width) || float.IsNaN(height) || width <= 0f || height <= 0f) return; var safe = Screen.safeArea; // Re-applying padding triggers another geometry pass, so nothing is written when the // inputs are unchanged. if (safe == _lastSafeArea && screenWidth == _lastScreenWidth && screenHeight == _lastScreenHeight && Mathf.Approximately(width, _lastWidth) && Mathf.Approximately(height, _lastHeight)) return; _lastSafeArea = safe; _lastScreenWidth = screenWidth; _lastScreenHeight = screenHeight; _lastWidth = width; _lastHeight = height; style.paddingLeft = width * Mathf.Clamp01(safe.xMin / screenWidth); style.paddingRight = width * Mathf.Clamp01((screenWidth - safe.xMax) / screenWidth); style.paddingTop = height * Mathf.Clamp01((screenHeight - safe.yMax) / screenHeight); style.paddingBottom = height * Mathf.Clamp01(safe.yMin / screenHeight); } void ClearPadding() { _lastSafeArea = default; _lastScreenWidth = 0; _lastScreenHeight = 0; style.paddingLeft = 0f; style.paddingRight = 0f; style.paddingTop = 0f; style.paddingBottom = 0f; } } }