I want to handle safeArea in my UIDocument.
I’ve subscribed to GeometryChangedEvent and want to adjust selected elements paddings based on current safeArea size.
How can I convert Screen.safeArea size to a padding size taking Panel Settings screen mode into account ?
Hello, sorry it took so long to get to your question but if you haven’t found a solution yet try looking at RuntimePanelUtils, it has a few helpful methods to translate coordinates to and from panels like ScreenToPanel.
Oh, I didn’t know that class.
Thanks! It seems to be working.
Here is how I made it:
public struct RectOffsetFloat
{
public RectOffsetFloat(float left, float right, float top, float bottom)
{
Left = left;
Right = right;
Top = top;
Bottom = bottom;
}
public float Left { get; private set; }
public float Right { get; private set; }
public float Top { get; private set; }
public float Bottom { get; private set; }
}
public static RectOffsetFloat GetSafeArea(this IPanel panel)
{
var safeLeftTop = RuntimePanelUtils.ScreenToPanel(
panel,
new Vector2(Screen.safeArea.xMin, Screen.height - Screen.safeArea.yMax)
);
var safeRightBottom = RuntimePanelUtils.ScreenToPanel(
panel,
new Vector2(Screen.width - Screen.safeArea.xMax, Screen.safeArea.yMin)
);
return new RectOffsetFloat(
safeLeftTop.x,
safeRightBottom.x,
safeLeftTop.y,
safeRightBottom.y
);
}
and how I use it:
// Update safe area related paddings
var safeAreaRectOffset = document.rootVisualElement.panel.GetSafeArea();
safeAreaElement.style.paddingLeft = safeAreaRectOffset.Left;
safeAreaElement.style.paddingRight = safeAreaRectOffset.Right;
safeAreaElement.style.paddingTop = safeAreaRectOffset.Top;
I have made some changes to have in account the padding option and to be compatible with Unity 6 and forward:
SafeArea
using System;
using UnityEngine;
using UnityEngine.UIElements;
/*
Disclaimer: Minimum version compatible -> Unity 6
TODO: Make non-editable the Spacing property in the UI Builder
*/
[UxmlElement("SafeArea")] // Cannot contain special characters
public partial class SafeAreaManager : VisualElement
{
[UxmlAttribute]
public SafeAreaSpacing SafeAreaType { get; set; }
public enum SafeAreaSpacing
{
Margin, // Makes visible the screen behind or the camera background
Padding // Makes visible the background of the visual element (SafeArea)
}
public SafeAreaManager() // Constructors are called on Awake()
{
style.flexGrow = 1;
style.flexShrink = 1;
RegisterCallback<GeometryChangedEvent>(LayoutChanged);
}
private void LayoutChanged(GeometryChangedEvent geomentryChangedEvent)
{
try
{
(Vector2 leftTop, Vector2 rightBottom) = SafeArea();
switch (SafeAreaType)
{
case SafeAreaSpacing.Margin:
style.marginLeft = leftTop.x;
style.marginTop = leftTop.y;
style.marginRight = rightBottom.x;
style.marginBottom = rightBottom.y;
break;
case SafeAreaSpacing.Padding:
style.paddingLeft = leftTop.x;
style.paddingTop = leftTop.y;
style.paddingRight = rightBottom.x;
style.paddingBottom = rightBottom.y;
break;
default:
Debug.LogWarning("Not implemented");
break;
}
}
catch (InvalidCastException){} // Throwed each time we safe the in the UI Builder
}
private (Vector2, Vector2) SafeArea()
{
Rect safeArea = Screen.safeArea;
Vector2 leftTop = RuntimePanelUtils.ScreenToPanel(this.panel,
new Vector2(safeArea.xMin, Screen.height - safeArea.yMax));
Vector2 rightBottom = RuntimePanelUtils.ScreenToPanel(this.panel,
new Vector2(Screen.width - safeArea.xMax, safeArea.yMin));
return (leftTop, rightBottom);
}
}
I have added to his version an extra option to shrink the content instead of displace it (SafeAreaMarginInsideViewport):
SafeAreaMarginInsideViewport
private (Vector2, Vector2) SafeArea(VisualElement visualElement)
{
Rect safeArea = Screen.safeArea;
Vector2 leftTop = RuntimePanelUtils.ScreenToPanel(visualElement.panel,
new Vector2(safeArea.xMin, Screen.height - safeArea.yMax));
Vector2 rightBottom = RuntimePanelUtils.ScreenToPanel(visualElement.panel,
new Vector2(Screen.width - safeArea.xMax, safeArea.yMin));
return (leftTop, rightBottom);
}
private void SafeAreaMargin(VisualElement visualElment) // This push the area (always from the zero x & y points). So the opposite part will be hidden (taken out of the screen) if your are using the 100% of the space.
{
(Vector2 leftTop, Vector2 rightBottom) = visualElment.SafeArea();
visualElment.style.marginLeft = leftTop.x;
visualElment.style.marginTop = leftTop.y;
visualElment.style.marginBottom = rightBottom.y;
visualElment.style.marginRight = rightBottom.x;
}
private void SafeAreaMarginInsideViewport(VisualElement visualElement) // Do not call via GeometryChangedEvent or you will get a recursive error. I you do it, make sure that in the function called, the first thing you do is unregister the GeometryChangedEvent (VisualElement.UnregisterCallback<GeometryChangedEvent>(FunctionCalled))
{
// Reset the size to 100% width and height
visualElement.style.width = new Length(100f, LengthUnit.Percent);
visualElement.style.height = new Length(100f, LengthUnit.Percent);
// Force update layout to get the correct resolved size
visualElement.MarkDirtyRepaint();
visualElement.panel?.visualTree?.schedule.Execute(() =>
{
// After layout pass, we have correct resolved sizes
(Vector2 leftTop, Vector2 rightBottom) = visualElement.SafeArea();
// Apply the safe area margins
visualElement.style.marginLeft = leftTop.x;
visualElement.style.marginTop = leftTop.y;
visualElement.style.marginBottom = rightBottom.y;
visualElement.style.marginRight = rightBottom.x;
// Adjust the size of the visual element to be inside the safe area
float newWidth = visualElement.resolvedStyle.width - leftTop.x - rightBottom.x;
float newHeight = visualElement.resolvedStyle.height - leftTop.y - rightBottom.y;
visualElement.style.width = new Length(newWidth, LengthUnit.Pixel);
visualElement.style.height = new Length(newHeight, LengthUnit.Pixel);
}).StartingIn(0);
}
private void SafeAreaPadding(VisualElement visualElement)
{
var (leftTop, rightBottom) = visualElement.SafeArea();
visualElement.style.paddingLeft = leftTop.x;
visualElement.style.paddingTop = leftTop.y;
visualElement.style.paddingBottom = rightBottom.y;
visualElement.style.paddingRight = rightBottom.x;
}
I am having some very weird behaviour when running this in the UI Builder, even if the UI builder’s resolution matches the game view screen pixels. I can get it to work in the game view, but not in both. How do you deal with this?