A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using osu.Framework.Allocation;
6using osu.Framework.Bindables;
7using osu.Framework.Extensions.EnumExtensions;
8using osu.Framework.Graphics.Primitives;
9using osu.Framework.Layout;
10
11namespace osu.Framework.Graphics.Containers
12{
13 /// <summary>
14 /// An <see cref="Container"/> that can also apply a padding based on the safe areas of the nearest cached <see cref="SafeAreaDefiningContainer"/>.
15 /// Padding will only be applied if the contents of this container would otherwise intersect the safe area margins relative to the associated target container.
16 /// </summary>
17 public class SafeAreaContainer : Container
18 {
19 private readonly BindableSafeArea safeAreaPadding = new BindableSafeArea();
20
21 public SafeAreaContainer()
22 {
23 AddLayout(PaddingCache);
24 }
25
26 [BackgroundDependencyLoader]
27 private void load()
28 {
29 safeAreaPadding.ValueChanged += _ => PaddingCache.Invalidate();
30 safeAreaPadding.BindTo(safeArea.SafeAreaPadding);
31 }
32
33 [Resolved(CanBeNull = true)]
34 private ISafeArea safeArea { get; set; }
35
36 /// <summary>
37 /// The <see cref="Edges"/> that should bypass the defined <see cref="ISafeArea" /> to bleed to the screen edge.
38 /// Defaults to <see cref="Edges.None"/>.
39 /// </summary>
40 public Edges SafeAreaOverrideEdges
41 {
42 get => safeAreaOverrideEdges;
43 set
44 {
45 safeAreaOverrideEdges = value;
46 PaddingCache.Invalidate();
47 }
48 }
49
50 private Edges safeAreaOverrideEdges = Edges.None;
51
52 protected readonly LayoutValue PaddingCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit);
53
54 protected override void UpdateAfterChildrenLife()
55 {
56 base.UpdateAfterChildrenLife();
57
58 if (!PaddingCache.IsValid)
59 {
60 Padding = getPadding();
61 PaddingCache.Validate();
62 }
63 }
64
65 private MarginPadding getPadding()
66 {
67 if (safeArea == null)
68 return new MarginPadding();
69
70 var nonSafeLocalSpace = safeArea.ExpandRectangleToSpaceOfOtherDrawable(this);
71
72 var nonSafe = safeArea.AvailableNonSafeSpace;
73
74 var paddedRect = new RectangleF(nonSafe.X + safeAreaPadding.Value.Left, nonSafe.Y + safeAreaPadding.Value.Top, nonSafe.Width - safeAreaPadding.Value.TotalHorizontal, nonSafe.Height - safeAreaPadding.Value.TotalVertical);
75 var localTopLeft = safeArea.ToSpaceOfOtherDrawable(paddedRect.TopLeft, this);
76 var localBottomRight = safeArea.ToSpaceOfOtherDrawable(paddedRect.BottomRight, this);
77
78 return new MarginPadding
79 {
80 Left = SafeAreaOverrideEdges.HasFlagFast(Edges.Left) ? nonSafeLocalSpace.TopLeft.X : Math.Clamp(localTopLeft.X, 0, DrawSize.X),
81 Right = SafeAreaOverrideEdges.HasFlagFast(Edges.Right) ? DrawRectangle.BottomRight.X - nonSafeLocalSpace.BottomRight.X : Math.Clamp(DrawSize.X - localBottomRight.X, 0, DrawSize.X),
82 Top = SafeAreaOverrideEdges.HasFlagFast(Edges.Top) ? nonSafeLocalSpace.TopLeft.Y : Math.Clamp(localTopLeft.Y, 0, DrawSize.Y),
83 Bottom = SafeAreaOverrideEdges.HasFlagFast(Edges.Bottom) ? DrawRectangle.BottomRight.Y - nonSafeLocalSpace.BottomRight.Y : Math.Clamp(DrawSize.Y - localBottomRight.Y, 0, DrawSize.Y)
84 };
85 }
86 }
87}