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;
}
}
}