mirror of
https://github.com/RaindropViewer/RaindropViewer.git
synced 2026-07-30 19:12:15 +00:00
246 lines
10 KiB
C#
246 lines
10 KiB
C#
//#define DEBUG_NOTCH_SOLUTION
|
|
|
|
using UnityEngine;
|
|
#if DEBUG_NOTCH_SOLUTION
|
|
using System.Linq;
|
|
#endif
|
|
|
|
namespace E7.NotchSolution
|
|
{
|
|
/// <summary>
|
|
/// <para>
|
|
/// Make the <see cref="RectTransform"/> of object with this component
|
|
/// driven into full stretch to its immediate parent, then apply padding according to
|
|
/// device's reported <see cref="Screen.safeArea"/>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Therefore makes an area inside this object safe for input-receiving components.
|
|
/// Then it is possible to make other objects safe area responsive by anchoring their
|
|
/// positions to this object's edges while being a child object.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Safe area defines an area on the phone's screen where it is safe to place your game-related input
|
|
/// receiving components without colliding with other on-screen features on the phone.
|
|
/// Usually this means it is also "visually safe" as all possible notches should be outside of safe area.
|
|
/// </para>
|
|
/// <para>
|
|
/// The amount of padding is a <see cref="Screen.safeArea"/> interpolated into <see cref="RectTransform"/>
|
|
/// of root <see cref="Canvas"/> found traveling up from this object. It should be a direct child of
|
|
/// top canvas, or deeper child of some similarly full stretch rect in order to look right,
|
|
/// although in reality it just pad in the shape of <see cref="Screen.safeArea"/>
|
|
/// regardless of its parent rectangle size.
|
|
/// </para>
|
|
/// </remarks>
|
|
[DisallowMultipleComponent]
|
|
[RequireComponent(typeof(RectTransform))]
|
|
[HelpURL("https://exceed7.com/notch-solution/components/ui-behaviour/safe-padding.html")]
|
|
public class SafePadding : NotchSolutionUIBehaviourBase
|
|
{
|
|
protected override void UpdateRect()
|
|
{
|
|
var selectedOrientation =
|
|
orientationType == SupportedOrientations.Dual
|
|
? NotchSolutionUtility.GetCurrentOrientation() == ScreenOrientation.Landscape ? landscapePaddings :
|
|
portraitOrDefaultPaddings
|
|
: portraitOrDefaultPaddings;
|
|
|
|
m_Tracker.Clear();
|
|
m_Tracker.Add(this, rectTransform,
|
|
(LockSide(selectedOrientation.left) ? DrivenTransformProperties.AnchorMinX : 0) |
|
|
(LockSide(selectedOrientation.right) ? DrivenTransformProperties.AnchorMaxX : 0) |
|
|
(LockSide(selectedOrientation.bottom) ? DrivenTransformProperties.AnchorMinY : 0) |
|
|
(LockSide(selectedOrientation.top) ? DrivenTransformProperties.AnchorMaxY : 0) |
|
|
(LockSide(selectedOrientation.left) && LockSide(selectedOrientation.right)
|
|
? DrivenTransformProperties.SizeDeltaX | DrivenTransformProperties.AnchoredPositionX
|
|
: 0) |
|
|
(LockSide(selectedOrientation.top) && LockSide(selectedOrientation.bottom)
|
|
? DrivenTransformProperties.SizeDeltaY | DrivenTransformProperties.AnchoredPositionY
|
|
: 0)
|
|
);
|
|
|
|
bool LockSide(EdgeEvaluationMode saem)
|
|
{
|
|
switch (saem)
|
|
{
|
|
case EdgeEvaluationMode.On:
|
|
case EdgeEvaluationMode.Balanced:
|
|
case EdgeEvaluationMode.Off:
|
|
return true;
|
|
//When "Unlocked" is supported, it will be false.
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Lock the anchor mode to full stretch first.
|
|
|
|
rectTransform.anchorMin = Vector2.zero;
|
|
rectTransform.anchorMax = Vector2.one;
|
|
|
|
var topRect = GetCanvasRect();
|
|
var safeAreaRelative = SafeAreaRelative;
|
|
|
|
#if DEBUG_NOTCH_SOLUTION
|
|
Debug.Log($"Top {topRect} safe {safeAreaRelative} min {safeAreaRelative.xMin} {safeAreaRelative.yMin}");
|
|
#endif
|
|
|
|
var relativeLDUR = new Vector4(
|
|
safeAreaRelative.xMin,
|
|
safeAreaRelative.yMin,
|
|
1 - (safeAreaRelative.yMin + safeAreaRelative.height),
|
|
1 - (safeAreaRelative.xMin + safeAreaRelative.width)
|
|
);
|
|
|
|
#if DEBUG_NOTCH_SOLUTION
|
|
Debug.Log($"SafeLDUR {string.Join(" ", relativeLDUR.Select(x => x.ToString()))}");
|
|
#endif
|
|
|
|
// fixed: sometimes relativeLDUR will be NAN when start at some android devices.
|
|
// if relativeLDUR is NAN then sizeDelta will be NAN, the safe area will be wrong.
|
|
if (float.IsNaN(relativeLDUR[0]))
|
|
{
|
|
relativeLDUR[0] = 0;
|
|
}
|
|
|
|
if (float.IsNaN(relativeLDUR[1]))
|
|
{
|
|
relativeLDUR[1] = 0;
|
|
}
|
|
|
|
if (float.IsNaN(relativeLDUR[2]))
|
|
{
|
|
relativeLDUR[2] = 0;
|
|
}
|
|
|
|
if (float.IsNaN(relativeLDUR[3]))
|
|
{
|
|
relativeLDUR[3] = 0;
|
|
}
|
|
|
|
var currentRect = rectTransform.rect;
|
|
|
|
//TODO : Calculate the current padding relative, to enable "Unlocked" mode. (Not forcing zero padding)
|
|
var finalPaddingsLDUR = new Vector4();
|
|
|
|
switch (selectedOrientation.left)
|
|
{
|
|
case EdgeEvaluationMode.On:
|
|
finalPaddingsLDUR[0] = topRect.width * relativeLDUR[0];
|
|
break;
|
|
case EdgeEvaluationMode.Balanced:
|
|
finalPaddingsLDUR[0] = relativeLDUR[3] > relativeLDUR[0]
|
|
? topRect.width * relativeLDUR[3]
|
|
: topRect.width * relativeLDUR[0];
|
|
break;
|
|
}
|
|
|
|
switch (selectedOrientation.right)
|
|
{
|
|
case EdgeEvaluationMode.On:
|
|
finalPaddingsLDUR[3] = topRect.width * relativeLDUR[3];
|
|
break;
|
|
case EdgeEvaluationMode.Balanced:
|
|
finalPaddingsLDUR[3] = relativeLDUR[0] > relativeLDUR[3]
|
|
? topRect.width * relativeLDUR[0]
|
|
: topRect.width * relativeLDUR[3];
|
|
break;
|
|
}
|
|
|
|
switch (selectedOrientation.bottom)
|
|
{
|
|
case EdgeEvaluationMode.On:
|
|
finalPaddingsLDUR[1] = topRect.height * relativeLDUR[1];
|
|
break;
|
|
case EdgeEvaluationMode.Balanced:
|
|
finalPaddingsLDUR[1] = relativeLDUR[2] > relativeLDUR[1]
|
|
? topRect.height * relativeLDUR[2]
|
|
: topRect.height * relativeLDUR[1];
|
|
break;
|
|
}
|
|
|
|
switch (selectedOrientation.top)
|
|
{
|
|
case EdgeEvaluationMode.On:
|
|
finalPaddingsLDUR[2] = topRect.height * relativeLDUR[2];
|
|
break;
|
|
case EdgeEvaluationMode.Balanced:
|
|
finalPaddingsLDUR[2] = relativeLDUR[1] > relativeLDUR[2]
|
|
? topRect.height * relativeLDUR[1]
|
|
: topRect.height * relativeLDUR[2];
|
|
break;
|
|
}
|
|
|
|
//Apply influence to the calculated padding
|
|
finalPaddingsLDUR[0] *= influence;
|
|
finalPaddingsLDUR[1] *= influence;
|
|
finalPaddingsLDUR[2] *= influence;
|
|
finalPaddingsLDUR[3] *= influence;
|
|
|
|
if (flipPadding)
|
|
{
|
|
float remember = 0;
|
|
finalPaddingsLDUR[0] = remember;
|
|
finalPaddingsLDUR[0] = finalPaddingsLDUR[3];
|
|
finalPaddingsLDUR[3] = remember;
|
|
|
|
finalPaddingsLDUR[1] = remember;
|
|
finalPaddingsLDUR[1] = finalPaddingsLDUR[2];
|
|
finalPaddingsLDUR[2] = remember;
|
|
}
|
|
|
|
#if DEBUG_NOTCH_SOLUTION
|
|
Debug.Log($"FinalLDUR {string.Join(" ", finalPaddingsLDUR.Select(x => x.ToString()))}");
|
|
#endif
|
|
|
|
//Combined padding becomes size delta.
|
|
var sizeDelta = rectTransform.sizeDelta;
|
|
sizeDelta.x = -(finalPaddingsLDUR[0] + finalPaddingsLDUR[3]);
|
|
sizeDelta.y = -(finalPaddingsLDUR[1] + finalPaddingsLDUR[2]);
|
|
rectTransform.sizeDelta = sizeDelta;
|
|
|
|
//The rect remaining after subtracted the size delta.
|
|
var rectWidthHeight = new Vector2(topRect.width + sizeDelta.x, topRect.height + sizeDelta.y);
|
|
|
|
#if DEBUG_NOTCH_SOLUTION
|
|
Debug.Log($"RectWidthHeight {rectWidthHeight}");
|
|
#endif
|
|
|
|
//Anchor position's answer is depending on pivot too. Where the pivot point is defines where 0 anchor point is.
|
|
var zeroPosition =
|
|
new Vector2(rectTransform.pivot.x * topRect.width, rectTransform.pivot.y * topRect.height);
|
|
var pivotInRect = new Vector2(rectTransform.pivot.x * rectWidthHeight.x,
|
|
rectTransform.pivot.y * rectWidthHeight.y);
|
|
|
|
#if DEBUG_NOTCH_SOLUTION
|
|
Debug.Log($"zeroPosition {zeroPosition}");
|
|
#endif
|
|
|
|
//Calculate like zero position is at bottom left first, then diff with the real zero position.
|
|
rectTransform.anchoredPosition3D = new Vector3(
|
|
finalPaddingsLDUR[0] + pivotInRect.x - zeroPosition.x,
|
|
finalPaddingsLDUR[1] + pivotInRect.y - zeroPosition.y,
|
|
rectTransform.anchoredPosition3D.z);
|
|
}
|
|
#pragma warning disable 0649
|
|
[SerializeField]
|
|
private SupportedOrientations orientationType;
|
|
|
|
[SerializeField]
|
|
private PerEdgeEvaluationModes portraitOrDefaultPaddings;
|
|
|
|
[SerializeField]
|
|
private PerEdgeEvaluationModes landscapePaddings;
|
|
|
|
[Tooltip("Scale down the resulting value read from an edge to be less than an actual value.")]
|
|
[SerializeField] [Range(0f, 1f)]
|
|
private float influence = 1;
|
|
|
|
[Tooltip(
|
|
"The value read from all edges are applied to the opposite side of a RectTransform instead. Useful when you have rotated or negatively scaled RectTransform.")]
|
|
[SerializeField]
|
|
private bool flipPadding;
|
|
#pragma warning restore 0649
|
|
}
|
|
} |