diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index ae11aefc2a..4c6138a686 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -658,11 +658,58 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window *window); * * \since This function is available since SDL 3.0.0. * + * \sa SDL_CreatePopupWindow * \sa SDL_CreateWindowFrom * \sa SDL_DestroyWindow */ extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindow(const char *title, int w, int h, Uint32 flags); +/** + * Create a child popup window of the specified parent window. + * + * 'flags' **must** contain exactly one of the following: + * - 'SDL_WINDOW_TOOLTIP': The popup window is a tooltip and will not pass any input events + * - 'SDL_WINDOW_POPUP_MENU': The popup window is a popup menu + * + * The following flags are not valid for popup windows and will be ignored: + * - 'SDL_WINDOW_MINIMIZED' + * - 'SDL_WINDOW_MAXIMIZED' + * - 'SDL_WINDOW_FULLSCREEN' + * - `SDL_WINDOW_BORDERLESS` + * - `SDL_WINDOW_MOUSE_GRABBED` + * + * The parent parameter **must** be non-null and a valid window. + * The parent of a popup window can be either a regular, toplevel window, + * or another popup window. + * + * Popup windows cannot be minimized, maximized, made fullscreen, or grab + * the mouse. Attempts to do so will fail. + * + * If a parent window is hidden, any child popup windows will be recursively + * hidden as well. Child popup windows not explicitly hidden will be restored + * when the parent is shown. + * + * If the parent window is destroyed, any child popup windows will be + * recursively destroyed as well. + * + * \param parent the parent of the window, must not be NULL + * \param offset_x the x position of the popup window relative to the origin + * of the parent, in screen coordinates + * \param offset_y the y position of the popup window relative to the origin + * of the parent window, in screen coordinates + * \param w the width of the window, in screen coordinates + * \param h the height of the window, in screen coordinates + * \param flags 0, or one or more SDL_WindowFlags OR'd together + * \returns the window that was created or NULL on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CreateWindow + * \sa SDL_DestroyWindow + */ +extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, Uint32 flags); + /** * Create an SDL window from an existing native window. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 025d992d50..a9431a817d 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -839,6 +839,7 @@ SDL3_0.0.0 { SDL_GetRenderScale; SDL_GetRenderWindowSize; SDL_GetSystemTheme; + SDL_CreatePopupWindow; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 5d86ddf8b3..50d70af8eb 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -866,3 +866,4 @@ #define SDL_GetRenderScale SDL_GetRenderScale_REAL #define SDL_GetRenderWindowSize SDL_GetRenderWindowSize_REAL #define SDL_GetSystemTheme SDL_GetSystemTheme_REAL +#define SDL_CreatePopupWindow SDL_CreatePopupWindow_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 8b95da73f9..8bbde00e56 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -911,3 +911,4 @@ SDL_DYNAPI_PROC(int,SDL_SetRenderScale,(SDL_Renderer *a, float b, float c),(a,b, SDL_DYNAPI_PROC(int,SDL_GetRenderScale,(SDL_Renderer *a, float *b, float *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetRenderWindowSize,(SDL_Renderer *a, int *b, int *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_SystemTheme,SDL_GetSystemTheme,(void),(),return) +SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return) diff --git a/src/events/SDL_windowevents.c b/src/events/SDL_windowevents.c index 8addc8d0ff..3079f54731 100644 --- a/src/events/SDL_windowevents.c +++ b/src/events/SDL_windowevents.c @@ -212,10 +212,18 @@ int SDL_SendWindowEvent(SDL_Window *window, SDL_EventType windowevent, break; } - if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { - if (!window->prev && !window->next) { + if (windowevent == SDL_EVENT_WINDOW_CLOSE_REQUESTED && window->parent == NULL) { + int toplevel_count = 0; + SDL_Window *n; + for (n = SDL_GetVideoDevice()->windows; n != NULL; n = n->next) { + if (n->parent == NULL) { + ++toplevel_count; + } + } + + if (toplevel_count == 1) { if (SDL_GetHintBoolean(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, SDL_TRUE)) { - SDL_SendQuit(); /* This is the last window in the list so send the SDL_EVENT_QUIT event */ + SDL_SendQuit(); /* This is the last toplevel window in the list so send the SDL_EVENT_QUIT event */ } } } diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 1942d09788..b256f27447 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -98,6 +98,7 @@ struct SDL_Window SDL_bool surface_valid; SDL_bool is_hiding; + SDL_bool restore_on_show; /* Child was hidden recursively by the parent, restore when shown. */ SDL_bool is_destroying; SDL_bool is_dropping; /* drag/drop in progress, expecting SDL_SendDropComplete(). */ @@ -114,12 +115,21 @@ struct SDL_Window SDL_Window *prev; SDL_Window *next; + + SDL_Window *parent; + SDL_Window *first_child; + SDL_Window *prev_sibling; + SDL_Window *next_sibling; }; #define SDL_WINDOW_FULLSCREEN_VISIBLE(W) \ ((((W)->flags & SDL_WINDOW_FULLSCREEN) != 0) && \ (((W)->flags & SDL_WINDOW_HIDDEN) == 0) && \ (((W)->flags & SDL_WINDOW_MINIMIZED) == 0)) +#define SDL_WINDOW_IS_POPUP(W) \ + ((((W)->flags & SDL_WINDOW_TOOLTIP) != 0) || \ + (((W)->flags & SDL_WINDOW_POPUP_MENU) != 0)) \ + \ /* * Define the SDL display structure. * This corresponds to physical monitors attached to the system. @@ -153,6 +163,7 @@ typedef enum { VIDEO_DEVICE_QUIRK_MODE_SWITCHING_EMULATED = 0x01, VIDEO_DEVICE_QUIRK_DISABLE_UNSET_FULLSCREEN_ON_MINIMIZE = 0x02, + VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT = 0x04, } DeviceQuirkFlags; struct SDL_VideoDevice @@ -496,6 +507,8 @@ extern void SDL_GL_DeduceMaxSupportedESProfile(int *major, int *minor); extern int SDL_RecreateWindow(SDL_Window *window, Uint32 flags); extern SDL_bool SDL_HasWindows(void); +extern void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y); +extern void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y); extern void SDL_OnWindowShown(SDL_Window *window); extern void SDL_OnWindowHidden(SDL_Window *window); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index be9722d51b..eac968e5ca 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -145,6 +145,12 @@ static VideoBootStrap *bootstrap[] = { return retval; \ } \ +#define CHECK_WINDOW_NOT_POPUP(window, retval) \ + if (SDL_WINDOW_IS_POPUP(window)) { \ + SDL_SetError("Operation invalid on popup windows"); \ + return retval; \ + } + #if defined(__MACOS__) && defined(SDL_VIDEO_DRIVER_COCOA) /* Support for macOS fullscreen spaces */ extern SDL_bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window); @@ -1230,6 +1236,46 @@ static SDL_DisplayID GetDisplayForRect(int x, int y, int w, int h) return closest; } +void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y) +{ + SDL_Window *w; + + if (SDL_WINDOW_IS_POPUP(window)) { + /* Calculate the total offset of the popup from the parents */ + for (w = window->parent; w != NULL; w = w->parent) { + rel_x += w->x; + rel_y += w->y; + } + } + + if (abs_x) { + *abs_x = rel_x; + } + if (abs_y) { + *abs_y = rel_y; + } +} + +void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y) +{ + SDL_Window *w; + + if (SDL_WINDOW_IS_POPUP(window)) { + /* Convert absolute window coordinates to relative for a popup */ + for (w = window->parent; w != NULL; w = w->parent) { + abs_x -= w->x; + abs_y -= w->y; + } + } + + if (rel_x) { + *rel_x = abs_x; + } + if (rel_y) { + *rel_y = abs_y; + } +} + SDL_DisplayID SDL_GetDisplayForPoint(const SDL_Point *point) { return GetDisplayForRect(point->x, point->y, 1, 1); @@ -1242,6 +1288,7 @@ SDL_DisplayID SDL_GetDisplayForRect(const SDL_Rect *rect) static SDL_DisplayID SDL_GetDisplayForWindowPosition(SDL_Window *window) { + int x, y; SDL_DisplayID displayID = 0; CHECK_WINDOW_MAGIC(window, 0); @@ -1254,8 +1301,10 @@ static SDL_DisplayID SDL_GetDisplayForWindowPosition(SDL_Window *window) * (for example if the window is off-screen), but other code may expect it * to succeed in that situation, so we fall back to a generic position- * based implementation in that case. */ + SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y); + if (!displayID) { - displayID = GetDisplayForRect(window->x, window->y, window->w, window->h); + displayID = GetDisplayForRect(x, y, window->w, window->h); } if (!displayID) { /* Use the primary display for a window if we can't find it anywhere else */ @@ -1536,6 +1585,7 @@ error: int SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (mode) { if (!SDL_GetFullscreenModeMatch(mode)) { @@ -1561,6 +1611,7 @@ int SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode) const SDL_DisplayMode *SDL_GetWindowFullscreenMode(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, NULL); + CHECK_WINDOW_NOT_POPUP(window, NULL); if (window->flags & SDL_WINDOW_FULLSCREEN) { return SDL_GetFullscreenModeMatch(&window->current_fullscreen_mode); @@ -1668,12 +1719,10 @@ static int SDL_DllNotSupported(const char *name) return SDL_SetError("No dynamic %s support in current SDL video driver (%s)", name, _this->name); } -SDL_Window *SDL_CreateWindow(const char *title, int w, int h, Uint32 flags) +SDL_Window *SDL_CreateWindowInternal(const char *title, int x, int y, int w, int h, SDL_Window *parent, Uint32 flags) { SDL_Window *window; Uint32 type_flags, graphics_flags; - int x = SDL_WINDOWPOS_UNDEFINED; - int y = SDL_WINDOWPOS_UNDEFINED; SDL_bool undefined_x = SDL_FALSE; SDL_bool undefined_y = SDL_FALSE; @@ -1701,6 +1750,12 @@ SDL_Window *SDL_CreateWindow(const char *title, int w, int h, Uint32 flags) return NULL; } + /* Tooltip and popup menu window must specify a parent window */ + if (!parent && ((type_flags & SDL_WINDOW_TOOLTIP) || (type_flags & SDL_WINDOW_POPUP_MENU))) { + SDL_SetError("Tooltip and popup menu windows must specify a parent window"); + return NULL; + } + /* Some platforms can't create zero-sized windows */ if (w < 1) { w = 1; @@ -1824,6 +1879,16 @@ SDL_Window *SDL_CreateWindow(const char *title, int w, int h, Uint32 flags) } _this->windows = window; + if (parent) { + window->parent = parent; + + window->next_sibling = parent->first_child; + if (parent->first_child) { + parent->first_child->prev_sibling = window; + } + parent->first_child = window; + } + if (_this->CreateSDLWindow && _this->CreateSDLWindow(_this, window) < 0) { SDL_DestroyWindow(window); return NULL; @@ -1866,6 +1931,33 @@ SDL_Window *SDL_CreateWindow(const char *title, int w, int h, Uint32 flags) return window; } +SDL_Window *SDL_CreateWindow(const char *title, int w, int h, Uint32 flags) +{ + return SDL_CreateWindowInternal(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w , h, NULL, flags); +} + +SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, Uint32 flags) +{ + if (!(_this->quirk_flags & VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT)) { + SDL_Unsupported(); + return NULL; + } + + /* Parent must be a valid window */ + CHECK_WINDOW_MAGIC(parent, NULL); + + /* Remove invalid flags */ + flags &= ~(SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MOUSE_GRABBED); + + /* Popups must specify either the tooltip or popup menu window flags */ + if ((flags & SDL_WINDOW_TOOLTIP) || (flags & SDL_WINDOW_POPUP_MENU)) { + return SDL_CreateWindowInternal(NULL, offset_x, offset_y, w, h, parent, flags); + } + + SDL_SetError("Popup windows must specify either the 'SDL_WINDOW_TOOLTIP' or the 'SDL_WINDOW_POPUP_MENU' flag"); + return NULL; +} + SDL_Window *SDL_CreateWindowFrom(const void *data) { SDL_Window *window; @@ -2109,6 +2201,7 @@ Uint32 SDL_GetWindowFlags(SDL_Window *window) int SDL_SetWindowTitle(SDL_Window *window, const char *title) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (title == window->title) { return 0; @@ -2329,6 +2422,7 @@ int SDL_GetWindowPosition(SDL_Window *window, int *x, int *y) int SDL_SetWindowBordered(SDL_Window *window, SDL_bool bordered) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { const int want = (bordered != SDL_FALSE); /* normalize the flag. */ const int have = !(window->flags & SDL_WINDOW_BORDERLESS); @@ -2347,6 +2441,7 @@ int SDL_SetWindowBordered(SDL_Window *window, SDL_bool bordered) int SDL_SetWindowResizable(SDL_Window *window, SDL_bool resizable) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { const int want = (resizable != SDL_FALSE); /* normalize the flag. */ const int have = ((window->flags & SDL_WINDOW_RESIZABLE) != 0); @@ -2365,6 +2460,7 @@ int SDL_SetWindowResizable(SDL_Window *window, SDL_bool resizable) int SDL_SetWindowAlwaysOnTop(SDL_Window *window, SDL_bool on_top) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { const int want = (on_top != SDL_FALSE); /* normalize the flag. */ const int have = ((window->flags & SDL_WINDOW_ALWAYS_ON_TOP) != 0); @@ -2583,27 +2679,54 @@ int SDL_GetWindowMaximumSize(SDL_Window *window, int *max_w, int *max_h) int SDL_ShowWindow(SDL_Window *window) { + SDL_Window *child; CHECK_WINDOW_MAGIC(window, -1); if (!(window->flags & SDL_WINDOW_HIDDEN)) { return 0; } + /* If the parent is hidden, set the flag to restore this when the parent is shown */ + if (window->parent && (window->parent->flags & SDL_WINDOW_HIDDEN)) { + window->restore_on_show = SDL_TRUE; + return 0; + } + if (_this->ShowWindow) { _this->ShowWindow(_this, window); } SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + + /* Restore child windows */ + for (child = window->first_child; child != NULL; child = child->next_sibling) { + if (!child->restore_on_show && (child->flags & SDL_WINDOW_HIDDEN)) { + break; + } + SDL_ShowWindow(child); + child->restore_on_show = SDL_FALSE; + } return 0; } int SDL_HideWindow(SDL_Window *window) { + SDL_Window *child; CHECK_WINDOW_MAGIC(window, -1); if (window->flags & SDL_WINDOW_HIDDEN) { + window->restore_on_show = SDL_FALSE; return 0; } + /* Hide all child windows */ + for (child = window->first_child; child != NULL; child = child->next_sibling) { + if (child->flags & SDL_WINDOW_HIDDEN) { + break; + } + SDL_HideWindow(child); + child->restore_on_show = SDL_TRUE; + } + window->is_hiding = SDL_TRUE; if (_this->HideWindow) { _this->HideWindow(_this, window); @@ -2629,6 +2752,7 @@ int SDL_RaiseWindow(SDL_Window *window) int SDL_MaximizeWindow(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (window->flags & SDL_WINDOW_MAXIMIZED) { return 0; @@ -2644,7 +2768,7 @@ int SDL_MaximizeWindow(SDL_Window *window) static SDL_bool SDL_CanMinimizeWindow(SDL_Window *window) { - if (!_this->MinimizeWindow) { + if (!_this->MinimizeWindow || SDL_WINDOW_IS_POPUP(window)) { return SDL_FALSE; } return SDL_TRUE; @@ -2653,6 +2777,7 @@ static SDL_bool SDL_CanMinimizeWindow(SDL_Window *window) int SDL_MinimizeWindow(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (window->flags & SDL_WINDOW_MINIMIZED) { return 0; @@ -2675,6 +2800,7 @@ int SDL_MinimizeWindow(SDL_Window *window) int SDL_RestoreWindow(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (!(window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) { return 0; @@ -2692,6 +2818,7 @@ int SDL_SetWindowFullscreen(SDL_Window *window, SDL_bool fullscreen) Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN : 0; CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); if (flags == (window->flags & SDL_WINDOW_FULLSCREEN)) { return 0; @@ -2952,6 +3079,7 @@ void SDL_UpdateWindowGrab(SDL_Window *window) int SDL_SetWindowGrab(SDL_Window *window, SDL_bool grabbed) { CHECK_WINDOW_MAGIC(window, -1); + CHECK_WINDOW_NOT_POPUP(window, -1); SDL_SetWindowMouseGrab(window, grabbed); @@ -3236,6 +3364,23 @@ void SDL_DestroyWindow(SDL_Window *window) window->is_destroying = SDL_TRUE; + /* Destroy any child windows of this window */ + while (window->first_child) { + SDL_DestroyWindow(window->first_child); + } + + /* If this is a child window, unlink it from its siblings */ + if (window->parent) { + if (window->next_sibling) { + window->next_sibling->prev_sibling = window->prev_sibling; + } + if (window->prev_sibling) { + window->prev_sibling->next_sibling = window->next_sibling; + } else { + window->parent->first_child = window->next_sibling; + } + } + /* Restore video mode, etc. */ SDL_UpdateFullscreenMode(window, SDL_FALSE); if (!(window->flags & SDL_WINDOW_FOREIGN)) {