1
0
Fork 0
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:
ed 2021-02-05 10:42:12 +00:00
parent 6d8c0b2fc3
commit ac1425f94e
3 changed files with 164 additions and 11 deletions

View file

@ -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;

View file

@ -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)

View file

@ -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;