mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added Display::safeAreaInsets and implementations for iOS and Android
This commit is contained in:
parent
6d8c0b2fc3
commit
ac1425f94e
3 changed files with 164 additions and 11 deletions
|
|
@ -45,7 +45,8 @@ public:
|
|||
bool isMain;
|
||||
|
||||
/** The total area of this display in logical pixels including any OS-dependent objects
|
||||
like the taskbar, menu bar, etc. */
|
||||
like the taskbar, menu bar, etc.
|
||||
*/
|
||||
Rectangle<int> totalArea;
|
||||
|
||||
/** The total area of this display in logical pixels which isn't covered by OS-dependent
|
||||
|
|
@ -53,6 +54,14 @@ public:
|
|||
*/
|
||||
Rectangle<int> userArea;
|
||||
|
||||
/** Represents the area of this display in logical pixels that is not functional for
|
||||
displaying content.
|
||||
|
||||
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.
|
||||
*/
|
||||
BorderSize<int> safeAreaInsets;
|
||||
|
||||
/** The top-left of this display in physical coordinates. */
|
||||
Point<int> topLeftPhysical;
|
||||
|
||||
|
|
|
|||
|
|
@ -225,6 +225,21 @@ DECLARE_JNI_CLASS (AndroidWindowManagerLayoutParams, "android/view/WindowManager
|
|||
DECLARE_JNI_CLASS (AndroidWindow, "android/view/Window")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getDisplayCutout, "getDisplayCutout", "()Landroid/view/DisplayCutout;")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidWindowInsets, "android/view/WindowInsets", 28)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getSafeInsetBottom, "getSafeInsetBottom", "()I") \
|
||||
METHOD (getSafeInsetLeft, "getSafeInsetLeft", "()I") \
|
||||
METHOD (getSafeInsetRight, "getSafeInsetRight", "()I") \
|
||||
METHOD (getSafeInsetTop, "getSafeInsetTop", "()I")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidDisplayCutout, "android/view/DisplayCutout", 28)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
namespace
|
||||
{
|
||||
|
|
@ -244,6 +259,30 @@ namespace
|
|||
constexpr int FLAG_NOT_FOCUSABLE = 0x8;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool supportsDisplayCutout()
|
||||
{
|
||||
return getAndroidSDKVersion() >= 28;
|
||||
}
|
||||
|
||||
static BorderSize<int> androidDisplayCutoutToBorderSize (LocalRef<jobject> displayCutout, double displayScale)
|
||||
{
|
||||
if (displayCutout.get() == nullptr)
|
||||
return {};
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
auto getInset = [&] (jmethodID methodID)
|
||||
{
|
||||
return roundToInt (env->CallIntMethod (displayCutout.get(), methodID) / displayScale);
|
||||
};
|
||||
|
||||
return { getInset (AndroidDisplayCutout.getSafeInsetTop),
|
||||
getInset (AndroidDisplayCutout.getSafeInsetLeft),
|
||||
getInset (AndroidDisplayCutout.getSafeInsetBottom),
|
||||
getInset (AndroidDisplayCutout.getSafeInsetRight) };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AndroidComponentPeer : public ComponentPeer,
|
||||
private Timer
|
||||
|
|
@ -309,7 +348,7 @@ public:
|
|||
env->SetIntField (windowLayoutParams.get(), AndroidWindowManagerLayoutParams.gravity, GRAVITY_LEFT | GRAVITY_TOP);
|
||||
env->SetIntField (windowLayoutParams.get(), AndroidWindowManagerLayoutParams.windowAnimations, 0x01030000 /* android.R.style.Animation */);
|
||||
|
||||
if (getAndroidSDKVersion() >= 28)
|
||||
if (supportsDisplayCutout())
|
||||
{
|
||||
jfieldID layoutInDisplayCutoutModeFieldId = env->GetFieldID (AndroidWindowManagerLayoutParams,
|
||||
"layoutInDisplayCutoutMode",
|
||||
|
|
@ -333,6 +372,18 @@ public:
|
|||
env->CallVoidMethod (viewGroup.get(), AndroidViewManager.addView, view.get(), windowLayoutParams.get());
|
||||
}
|
||||
|
||||
if (supportsDisplayCutout())
|
||||
{
|
||||
jmethodID setOnApplyWindowInsetsListenerMethodId = env->GetMethodID (AndroidView,
|
||||
"setOnApplyWindowInsetsListener",
|
||||
"(Landroid/view/View$OnApplyWindowInsetsListener;)V");
|
||||
|
||||
if (setOnApplyWindowInsetsListenerMethodId != nullptr)
|
||||
env->CallVoidMethod (view.get(), setOnApplyWindowInsetsListenerMethodId,
|
||||
CreateJavaInterface (new ViewWindowInsetsListener,
|
||||
"android/view/View$OnApplyWindowInsetsListener").get());
|
||||
}
|
||||
|
||||
if (isFocused())
|
||||
handleFocusGain();
|
||||
}
|
||||
|
|
@ -856,6 +907,61 @@ private:
|
|||
static void JNICALL handleAppPausedJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast<AndroidComponentPeer*> (host)) myself->handleAppPausedCallback(); }
|
||||
static void JNICALL handleAppResumedJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast<AndroidComponentPeer*> (host)) myself->handleAppResumedCallback(); }
|
||||
|
||||
//==============================================================================
|
||||
struct ViewWindowInsetsListener : public juce::AndroidInterfaceImplementer
|
||||
{
|
||||
jobject onApplyWindowInsets (LocalRef<jobject> v, LocalRef<jobject> insets)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> displayCutout (env->CallObjectMethod (insets.get(), AndroidWindowInsets.getDisplayCutout));
|
||||
|
||||
if (displayCutout != nullptr)
|
||||
{
|
||||
auto& displays = Desktop::getInstance().getDisplays();
|
||||
auto& mainDisplay = *displays.getPrimaryDisplay();
|
||||
|
||||
auto newSafeAreaInsets = androidDisplayCutoutToBorderSize (displayCutout, mainDisplay.scale);
|
||||
|
||||
if (newSafeAreaInsets != mainDisplay.safeAreaInsets)
|
||||
const_cast<Displays&> (displays).refresh();
|
||||
|
||||
auto* fieldId = env->GetStaticFieldID (AndroidWindowInsets, "CONSUMED", "Landroid/view/WindowInsets");
|
||||
jassert (fieldId != nullptr);
|
||||
|
||||
return env->GetStaticObjectField (AndroidWindowInsets, fieldId);
|
||||
}
|
||||
|
||||
jmethodID onApplyWindowInsetsMethodId = env->GetMethodID (AndroidView,
|
||||
"onApplyWindowInsets",
|
||||
"(Landroid/view/WindowInsets;)Landroid/view/WindowInsets;");
|
||||
|
||||
jassert (onApplyWindowInsetsMethodId != nullptr);
|
||||
|
||||
return env->CallObjectMethod (v.get(), onApplyWindowInsetsMethodId, insets.get());
|
||||
}
|
||||
|
||||
private:
|
||||
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
|
||||
|
||||
if (methodName == "onApplyWindowInsets")
|
||||
{
|
||||
jassert (env->GetArrayLength (args) == 2);
|
||||
|
||||
LocalRef<jobject> windowView (env->GetObjectArrayElement (args, 0));
|
||||
LocalRef<jobject> insets (env->GetObjectArrayElement (args, 1));
|
||||
|
||||
return onApplyWindowInsets (std::move (windowView), std::move (insets));
|
||||
}
|
||||
|
||||
// invoke base class
|
||||
return AndroidInterfaceImplementer::invoke (proxy, method, args);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct PreallocatedImage : public ImagePixelData
|
||||
{
|
||||
|
|
@ -1457,6 +1563,26 @@ void Displays::findDisplays (float masterScale)
|
|||
if (! activityArea.isEmpty())
|
||||
d.userArea = activityArea / d.scale;
|
||||
|
||||
if (supportsDisplayCutout())
|
||||
{
|
||||
jmethodID getRootWindowInsetsMethodId = env->GetMethodID (AndroidView,
|
||||
"getRootWindowInsets",
|
||||
"()Landroid/view/WindowInsets;");
|
||||
|
||||
if (getRootWindowInsetsMethodId != nullptr)
|
||||
{
|
||||
LocalRef<jobject> insets (env->CallObjectMethod (contentView.get(), getRootWindowInsetsMethodId));
|
||||
|
||||
if (insets != nullptr)
|
||||
{
|
||||
LocalRef<jobject> displayCutout (env->CallObjectMethod (insets.get(), AndroidWindowInsets.getDisplayCutout));
|
||||
|
||||
if (displayCutout.get() != nullptr)
|
||||
d.safeAreaInsets = androidDisplayCutoutToBorderSize (displayCutout, d.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasAddedMainActivityListener = false;
|
||||
|
||||
if (! hasAddedMainActivityListener)
|
||||
|
|
|
|||
|
|
@ -661,18 +661,35 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
|
|||
return Orientations::convertToJuce (orientation);
|
||||
}
|
||||
|
||||
// The most straightforward way of retrieving the screen area available to an iOS app
|
||||
// seems to be to create a new window (which will take up all available space) and to
|
||||
// query its frame.
|
||||
struct TemporaryWindow
|
||||
{
|
||||
UIWindow* window = [[UIWindow alloc] init];
|
||||
~TemporaryWindow() noexcept { [window release]; }
|
||||
};
|
||||
|
||||
static Rectangle<int> getRecommendedWindowBounds()
|
||||
{
|
||||
// The most straightforward way of retrieving the screen area available to an iOS app
|
||||
// seems to be to create a new window (which will take up all available space) and to
|
||||
// query its frame.
|
||||
struct TemporaryWindow
|
||||
{
|
||||
UIWindow* window = [[UIWindow alloc] init];
|
||||
~TemporaryWindow() noexcept { [window release]; }
|
||||
};
|
||||
return convertToRectInt (TemporaryWindow().window.frame);
|
||||
}
|
||||
|
||||
return convertToRectInt (TemporaryWindow{}.window.frame);
|
||||
static BorderSize<int> getSafeAreaInsets (float masterScale)
|
||||
{
|
||||
#if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0
|
||||
UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets;
|
||||
|
||||
auto getInset = [&] (float original) { return roundToInt (original / masterScale); };
|
||||
|
||||
return { getInset (safeInsets.top), getInset (safeInsets.left),
|
||||
getInset (safeInsets.bottom), getInset (safeInsets.right) };
|
||||
#else
|
||||
auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
|
||||
auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height);
|
||||
|
||||
return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 };
|
||||
#endif
|
||||
}
|
||||
|
||||
void Displays::findDisplays (float masterScale)
|
||||
|
|
@ -684,6 +701,7 @@ void Displays::findDisplays (float masterScale)
|
|||
Display d;
|
||||
d.totalArea = convertToRectInt ([s bounds]) / masterScale;
|
||||
d.userArea = getRecommendedWindowBounds() / masterScale;
|
||||
d.safeAreaInsets = getSafeAreaInsets (masterScale);
|
||||
d.isMain = true;
|
||||
d.scale = masterScale * s.scale;
|
||||
d.dpi = 160 * d.scale;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue