1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

SidePanel: Add an option to restrict content to the safe screen area

A major benefit of this change is that the menu in the DemoRunner will
now display reasonably on mobile devices with notches or other
decorations.
This commit is contained in:
reuk 2025-05-12 19:48:08 +01:00
parent f30d70049c
commit cfc006aaf9
No known key found for this signature in database
4 changed files with 70 additions and 8 deletions

View file

@ -308,6 +308,7 @@ MainComponent::MainComponent()
demosPanel.setTitle ("Demos");
demosPanel.setFocusContainerType (FocusContainerType::focusContainer);
demosPanel.setContentRestrictedToSafeArea (true);
showDemosButton.onClick = [this] { demosPanel.showOrHide (true); };

View file

@ -56,19 +56,41 @@ public:
/** The total area of this display in logical pixels including any OS-dependent objects
like the taskbar, menu bar, etc.
On mobile (Android, iOS) this is the full area of the display.
*/
Rectangle<int> totalArea;
/** The total area of this display in logical pixels which isn't covered by OS-dependent
objects like the taskbar, menu bar, etc.
On mobile (iOS, Android), the system UI will be made transparent whenever possible, and
the JUCE app may draw behind these bars. Therefore, on these platforms, the userArea
is *not* restricted by the system UI. Instead, potentially-obscured areas of the
display can be found by querying the safeAreaInsets and keyboardInsets.
Mobile platforms that support multiple windows (e.g. Android in split screen) will
return the screen area currently available to the application here. The resulting
area may be significantly smaller than the total screen area, but may overlap the
system decorations.
*/
Rectangle<int> userArea;
/** Represents the area of this display in logical pixels that is not functional for
displaying content.
These insets are applied relative to the userArea.
On mobile devices this may be the area covered by display cutouts and notches, where
you still want to draw a background but should not position important content.
Note that these insets may change depending on the current state of the system.
As a simple example, entering/leaving kiosk mode may cause the system UI visibility
to change, which may affect the safe areas.
A more complex example would be split-screen state on Android, where an activity
occupying the top portion of the screen is likely to have insets for the status bar but
not the navigation bar, whereas an activity on the bottom may have navigation insets
but not status insets.
*/
BorderSize<int> safeAreaInsets;

View file

@ -131,6 +131,24 @@ void SidePanel::resized()
calculateAndRemoveShadowBounds (bounds);
const auto fullScreen = std::invoke ([&]
{
if (auto* peer = getPeer())
return peer->isFullScreen();
return false;
});
if (fullScreen && isContentRestrictedToSafeArea() && parent != nullptr)
{
if (auto* display = Desktop::getInstance().getDisplays().getDisplayForRect (parent->getScreenBounds()))
{
const auto safeArea = display->safeAreaInsets.subtractedFrom (display->keyboardInsets.subtractedFrom (display->userArea));
const auto safeAreaInLocalSpace = getLocalArea (nullptr, safeArea) + getCurrentOffset();
bounds = bounds.getIntersection (safeAreaInLocalSpace);
}
}
auto titleBounds = bounds.removeFromTop (titleBarHeight);
if (titleBarComponent != nullptr)
@ -268,18 +286,25 @@ void SidePanel::changeListenerCallback (ChangeBroadcaster*)
}
}
Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const
Rectangle<int> SidePanel::calculateShowingBoundsInParent (Component& parentComp) const
{
auto parentBounds = parentComp.getLocalBounds();
if (isOnLeft)
{
return isShowing ? parentBounds.removeFromLeft (panelWidth)
: parentBounds.withX (parentBounds.getX() - panelWidth).withWidth (panelWidth);
}
return isOnLeft ? parentBounds.removeFromLeft (panelWidth)
: parentBounds.removeFromRight (panelWidth);
}
return isShowing ? parentBounds.removeFromRight (panelWidth)
: parentBounds.withX (parentBounds.getRight()).withWidth (panelWidth);
Point<int> SidePanel::getCurrentOffset() const
{
if (isShowing)
return {};
return { isOnLeft ? -panelWidth : panelWidth, 0 };
}
Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const
{
return calculateShowingBoundsInParent (parentComp) + getCurrentOffset();
}
void SidePanel::calculateAndRemoveShadowBounds (Rectangle<int>& bounds)

View file

@ -154,6 +154,17 @@ public:
/** Returns the text that is displayed in the title bar at the top of the SidePanel. */
String getTitleText() const noexcept { return titleLabel.getText(); }
/** @see isContentRestrictedToSafeArea() */
void setContentRestrictedToSafeArea (bool x) noexcept { restrictToSafeArea = x; }
/** When true, will avoid displaying menu content within areas of the screen that may be
obscured by display cutouts or operating system decorations. When false, the menu's
content will entirely fill the menu bounds. True by default.
@see setContentRestrictedToSafeArea()
*/
bool isContentRestrictedToSafeArea() const noexcept { return restrictToSafeArea; }
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes to provide
SidePanel drawing functionality.
@ -230,12 +241,15 @@ private:
int amountMoved = 0;
bool shouldShowDismissButton = true;
bool restrictToSafeArea = true;
//==============================================================================
void lookAndFeelChanged() override;
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
void changeListenerCallback (ChangeBroadcaster*) override;
Rectangle<int> calculateShowingBoundsInParent (Component&) const;
Point<int> getCurrentOffset() const;
Rectangle<int> calculateBoundsInParent (Component&) const;
void calculateAndRemoveShadowBounds (Rectangle<int>& bounds);