From 651ee3e8966da99cc3dd1a990f658e62fbfde338 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Mon, 22 Jul 2024 18:59:53 -0700 Subject: [PATCH] Added SDL_GetDisplaySafeInsets() --- include/SDL3/SDL_video.h | 19 +++++++++++++++++ src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/test/SDL_test_common.c | 13 ++++++++++++ src/video/SDL_sysvideo.h | 9 ++++++++ src/video/SDL_video.c | 34 +++++++++++++++++++++++++++++++ src/video/cocoa/SDL_cocoamodes.m | 14 +++++++++++++ src/video/uikit/SDL_uikitview.h | 2 ++ src/video/uikit/SDL_uikitview.m | 13 ++++++++++++ 10 files changed, 107 insertions(+) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 36e216d31c372b..4ecb0a2c483695 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -503,6 +503,25 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetDisplayBounds(SDL_DisplayID displayID, SD */ extern SDL_DECLSPEC int SDLCALL SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect); +/** + * Get the safe insets of this display, in screen coordinates. + * + * Some displays have portions of the edge which are partially obscured, possibly due to curved edges, camera housing, TV overscan, etc. This function provides the areas on the edge of the display which may be obscured. You should continue rendering into these areas, but it should not be visually important or interactible content. + * + * These areas take current orientation into account and may change if the display changes orientation. + * + * \param displayID the instance ID of the display to query. + * \param left a pointer filled in with the width of the partially obscured area on the left side of the display, in screen coordinates, may be NULL. + * \param right a pointer filled in with the width of the partially obscured area on the right side of the display, in screen coordinates, may be NULL. + * \param top a pointer filled in with the height of the partially obscured area on the top side of the display, in screen coordinates, may be NULL. + * \param bottom a pointer filled in with the height of the partially obscured area on the bottom side of the display, in screen coordinates, may be NULL. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetDisplaySafeInsets(SDL_DisplayID displayID, int *left, int *right, int *top, int *bottom); + /** * Get the orientation of a display when it is unrotated. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 5b9b1722e78bb6..5e45723aeb8fe7 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -222,6 +222,7 @@ SDL3_0.0.0 { SDL_GetDisplayForWindow; SDL_GetDisplayName; SDL_GetDisplayProperties; + SDL_GetDisplaySafeInsets; SDL_GetDisplayUsableBounds; SDL_GetDisplays; SDL_GetError; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 278310dc158bad..5b11b361ba51fe 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -247,6 +247,7 @@ #define SDL_GetDisplayForWindow SDL_GetDisplayForWindow_REAL #define SDL_GetDisplayName SDL_GetDisplayName_REAL #define SDL_GetDisplayProperties SDL_GetDisplayProperties_REAL +#define SDL_GetDisplaySafeInsets SDL_GetDisplaySafeInsets_REAL #define SDL_GetDisplayUsableBounds SDL_GetDisplayUsableBounds_REAL #define SDL_GetDisplays SDL_GetDisplays_REAL #define SDL_GetError SDL_GetError_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 95ebe6753ed7ea..d25b0006f24f6c 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -267,6 +267,7 @@ SDL_DYNAPI_PROC(SDL_DisplayID,SDL_GetDisplayForRect,(const SDL_Rect *a),(a),retu SDL_DYNAPI_PROC(SDL_DisplayID,SDL_GetDisplayForWindow,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetDisplayName,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetDisplayProperties,(SDL_DisplayID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_GetDisplaySafeInsets,(SDL_DisplayID a, int *b, int *c, int *d, int *e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_GetDisplayUsableBounds,(SDL_DisplayID a, SDL_Rect *b),(a,b),return) SDL_DYNAPI_PROC(const SDL_DisplayID*,SDL_GetDisplays,(int *a),(a),return) SDL_DYNAPI_PROC(const char*,SDL_GetError,(void),(),return) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 2de6b74c988539..2d7c858ebcc7d0 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1205,6 +1205,7 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) displays = SDL_GetDisplays(&n); SDL_Log("Number of displays: %d\n", n); for (i = 0; i < n; ++i) { + int inset_left = 0, inset_right = 0, inset_top = 0, inset_bottom = 0; SDL_DisplayID displayID = displays[i]; SDL_Log("Display %" SDL_PRIu32 ": %s\n", displayID, SDL_GetDisplayName(displayID)); @@ -1214,8 +1215,11 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) SDL_zero(usablebounds); SDL_GetDisplayUsableBounds(displayID, &usablebounds); + SDL_GetDisplaySafeInsets(displayID, &inset_left, &inset_right, &inset_top, &inset_bottom); + SDL_Log("Bounds: %dx%d at %d,%d\n", bounds.w, bounds.h, bounds.x, bounds.y); SDL_Log("Usable bounds: %dx%d at %d,%d\n", usablebounds.w, usablebounds.h, usablebounds.x, usablebounds.y); + SDL_Log("Safe insets: left: %d right: %d top: %d bottom: %d\n", inset_left, inset_right, inset_top, inset_bottom); mode = SDL_GetDesktopDisplayMode(displayID); SDL_GetMasksForPixelFormat(mode->format, &bpp, &Rmask, &Gmask, @@ -2518,6 +2522,10 @@ void SDLTest_CommonDrawWindowInfo(SDL_Renderer *renderer, SDL_Window *window, fl const char *name; SDL_RendererLogicalPresentation logical_presentation; SDL_ScaleMode logical_scale_mode; + int safe_inset_left = 0; + int safe_inset_right = 0; + int safe_inset_top = 0; + int safe_inset_bottom = 0; /* Video */ @@ -2656,6 +2664,11 @@ void SDLTest_CommonDrawWindowInfo(SDL_Renderer *renderer, SDL_Window *window, fl SDLTest_DrawString(renderer, 0.0f, textY, text); textY += lineHeight; + SDL_GetDisplaySafeInsets(windowDisplayID, &safe_inset_left, &safe_inset_right, &safe_inset_top, &safe_inset_bottom); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetDisplaySafeInsets: left: %d, right: %d, top: %d, bottom: %d", safe_inset_left, safe_inset_right, safe_inset_top, safe_inset_bottom); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + /* Mouse */ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index ec2eaba606df36..98307df8599aad 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -33,6 +33,14 @@ typedef struct SDL_VideoData SDL_VideoData; typedef struct SDL_DisplayData SDL_DisplayData; typedef struct SDL_WindowData SDL_WindowData; +typedef struct +{ + int left; + int right; + int top; + int bottom; +} SDL_RectInsets; + typedef struct { float SDR_white_level; @@ -145,6 +153,7 @@ struct SDL_VideoDisplay SDL_DisplayOrientation natural_orientation; SDL_DisplayOrientation current_orientation; float content_scale; + SDL_RectInsets safe_insets; SDL_HDROutputProperties HDR; SDL_Window *fullscreen_window; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index d50af934d33dd3..eca2c55734ddc7 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -1014,6 +1014,40 @@ int SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect) return SDL_GetDisplayBounds(displayID, rect); } +int SDL_GetDisplaySafeInsets(SDL_DisplayID displayID, int *left, int *right, int *top, int *bottom) +{ + SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID); + + if (left) { + *left = 0; + } + if (right) { + *right = 0; + } + if (top) { + *top = 0; + } + if (bottom) { + *bottom = 0; + } + + CHECK_DISPLAY_MAGIC(display, -1); + + if (left) { + *left = display->safe_insets.left; + } + if (right) { + *right = display->safe_insets.right; + } + if (top) { + *top = display->safe_insets.top; + } + if (bottom) { + *bottom = display->safe_insets.bottom; + } + return 0; +} + SDL_DisplayOrientation SDL_GetNaturalDisplayOrientation(SDL_DisplayID displayID) { SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID); diff --git a/src/video/cocoa/SDL_cocoamodes.m b/src/video/cocoa/SDL_cocoamodes.m index 9eecb9d6d05774..cc0ed37bfb3381 100644 --- a/src/video/cocoa/SDL_cocoamodes.m +++ b/src/video/cocoa/SDL_cocoamodes.m @@ -290,6 +290,19 @@ static SDL_bool GetDisplayMode(SDL_VideoDevice *_this, CGDisplayModeRef vidmode, return displayName; } +static void Cocoa_GetSafeInsets(CGDirectDisplayID displayID, SDL_RectInsets *insets) +{ + if (@available(macOS 10.12, *)) { + NSScreen *screen = GetNSScreenForDisplayID(displayID); + if (screen) { + insets->left = (int)SDL_ceilf(screen.safeAreaInsets.left); + insets->right = (int)SDL_ceilf(screen.safeAreaInsets.right); + insets->top = (int)SDL_ceilf(screen.safeAreaInsets.top); + insets->bottom = (int)SDL_ceilf(screen.safeAreaInsets.bottom); + } + } +} + static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDROutputProperties *HDR) { HDR->SDR_white_level = 1.0f; @@ -383,6 +396,7 @@ void Cocoa_InitModes(SDL_VideoDevice *_this) CVDisplayLinkRelease(link); CGDisplayModeRelease(moderef); + Cocoa_GetSafeInsets(displaydata->display, &display.safe_insets); Cocoa_GetHDRProperties(displaydata->display, &display.HDR); display.desktop_mode = mode; diff --git a/src/video/uikit/SDL_uikitview.h b/src/video/uikit/SDL_uikitview.h index 0d720da720d215..20375d0f8e2dba 100644 --- a/src/video/uikit/SDL_uikitview.h +++ b/src/video/uikit/SDL_uikitview.h @@ -43,4 +43,6 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)safeAreaInsetsDidChange; + @end diff --git a/src/video/uikit/SDL_uikitview.m b/src/video/uikit/SDL_uikitview.m index 4f0c1f4d5c3c5d..740769c5aa3ac1 100644 --- a/src/video/uikit/SDL_uikitview.m +++ b/src/video/uikit/SDL_uikitview.m @@ -368,6 +368,19 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event } } +- (void)safeAreaInsetsDidChange +{ + // Update the safe area insets + if (@available(iOS 11.0, tvOS 11.0, *)) { + SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(sdlwindow); + + display->safe_insets.left = (int)SDL_ceilf(self.safeAreaInsets.left); + display->safe_insets.right = (int)SDL_ceilf(self.safeAreaInsets.right); + display->safe_insets.top = (int)SDL_ceilf(self.safeAreaInsets.top); + display->safe_insets.bottom = (int)SDL_ceilf(self.safeAreaInsets.bottom); + } +} + #if defined(SDL_PLATFORM_TVOS) || defined(__IPHONE_9_1) - (SDL_Scancode)scancodeFromPress:(UIPress *)press {