diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 9529baf637..a9f85f843a 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -351,9 +351,10 @@ static SDL_Scancode WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam) } #if !defined(__XBOXONE__) && !defined(__XBOXSERIES__) -static SDL_bool WIN_ShouldIgnoreFocusClick() +static SDL_bool WIN_ShouldIgnoreFocusClick(SDL_WindowData *data) { - return !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE); + return !SDL_WINDOW_IS_POPUP(data->window) && + !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, SDL_FALSE); } static void WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed, Uint32 mouseFlags, SDL_bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID) @@ -372,7 +373,7 @@ static void WIN_CheckWParamMouseButton(SDL_bool bwParamMousePressed, Uint32 mous data->focus_click_pending &= ~SDL_BUTTON(button); WIN_UpdateClipCursor(data->window); } - if (WIN_ShouldIgnoreFocusClick()) { + if (WIN_ShouldIgnoreFocusClick(data)) { return; } } @@ -512,7 +513,7 @@ static void WIN_UpdateFocus(SDL_Window *window, SDL_bool expect_focus) data->focus_click_pending |= SDL_BUTTON_X2MASK; } - SDL_SetKeyboardFocus(window); + SDL_SetKeyboardFocus(data->keyboard_focus ? data->keyboard_focus : window); /* In relative mode we are guaranteed to have mouse focus if we have keyboard focus */ if (!SDL_GetMouse()->relative_mode) { @@ -788,6 +789,13 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) WIN_UpdateFocus(data->window, !!LOWORD(wParam)); } break; + case WM_MOUSEACTIVATE: + { + if (SDL_WINDOW_IS_POPUP(data->window)) { + return MA_NOACTIVATE; + } + } break; + case WM_SETFOCUS: { /* Update the focus in case it's changing between top-level windows in the same application */ @@ -1234,6 +1242,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_WINDOWPOSCHANGED: { + SDL_Window *win; RECT rect; int x, y; int w, h; @@ -1260,6 +1269,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) y = rect.top; WIN_ScreenPointToSDL(&x, &y); + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); // Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds @@ -1297,6 +1307,11 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) /* Display changed, check ICC profile */ WIN_UpdateWindowICCProfile(data->window, SDL_TRUE); } + + /* Update the position of any child windows */ + for (win = data->window->first_child; win != NULL; win = win->next_sibling) { + WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE); + } } break; case WM_SIZE: @@ -1507,6 +1522,11 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_NCHITTEST: { SDL_Window *window = data->window; + + if (window->flags & SDL_WINDOW_TOOLTIP) { + return HTTRANSPARENT; + } + if (window->hit_test) { POINT winpoint; winpoint.x = GET_X_LPARAM(lParam); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 519c1ef204..0a9200c951 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -260,6 +260,8 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->free = WIN_DeleteDevice; + device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT; + return device; } diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index c9ab6d480c..e9f758dde3 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -79,7 +79,9 @@ static DWORD GetWindowStyle(SDL_Window *window) { DWORD style = 0; - if (window->flags & SDL_WINDOW_FULLSCREEN) { + if (SDL_WINDOW_IS_POPUP(window)) { + style |= WS_POPUP; + } else if (window->flags & SDL_WINDOW_FULLSCREEN) { style |= STYLE_FULLSCREEN; } else { if (window->flags & SDL_WINDOW_BORDERLESS) { @@ -114,6 +116,16 @@ static DWORD GetWindowStyle(SDL_Window *window) return style; } +static DWORD GetWindowStyleEx(SDL_Window *window) +{ + DWORD style = WS_EX_APPWINDOW; + + if (SDL_WINDOW_IS_POPUP(window)) { + style = WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE; + } + return style; +} + /** * Returns arguments to pass to SetWindowPos - the window rect, including frame, in Windows coordinates. * Can be called before we have a HWND. @@ -128,8 +140,10 @@ static void WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL #endif /* Client rect, in SDL screen coordinates */ - *x = (use_current ? window->x : window->windowed.x); - *y = (use_current ? window->y : window->windowed.y); + SDL_RelativeToGlobalForWindow(window, + (use_current ? window->x : window->windowed.x), + (use_current ? window->y : window->windowed.y), + x, y); *width = (use_current ? window->w : window->windowed.w); *height = (use_current ? window->h : window->windowed.h); @@ -222,8 +236,9 @@ static void WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, WIN_AdjustWindowRectWithStyle(window, style, menu, x, y, width, height, use_current); } -static void WIN_SetWindowPositionInternal(_THIS, SDL_Window *window, UINT flags) +void WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags) { + SDL_Window *child_window; SDL_WindowData *data = window->driverdata; HWND hwnd = data->hwnd; HWND top; @@ -242,6 +257,11 @@ static void WIN_SetWindowPositionInternal(_THIS, SDL_Window *window, UINT flags) data->expected_resize = SDL_TRUE; SetWindowPos(hwnd, top, x, y, w, h, flags); data->expected_resize = SDL_FALSE; + + /* Update any child windows */ + for (child_window = window->first_child; child_window != NULL; child_window = child_window->next_sibling) { + WIN_SetWindowPositionInternal(child_window, flags); + } } static void SDLCALL WIN_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char *oldValue, const char *hint) @@ -304,7 +324,7 @@ static int SetupWindowData(_THIS, SDL_Window *window, HWND hwnd, HWND parent, SD data->hwnd = hwnd; data->parent = parent; #if defined(__XBOXONE__) || defined(__XBOXSERIES__) - data->hdc = (HDC) data->hwnd; + data->hdc = (HDC)data->hwnd; #else data->hdc = GetDC(hwnd); #endif @@ -445,6 +465,10 @@ static int SetupWindowData(_THIS, SDL_Window *window, HWND hwnd, HWND parent, SD window->flags |= SDL_WINDOW_ALLOW_HIGHDPI; } + if (data->parent && !window->parent) { + data->destroy_parent_with_window = SDL_TRUE; + } + data->initializing = SDL_FALSE; /* All done! */ @@ -470,7 +494,7 @@ static void CleanupWindowData(_THIS, SDL_Window *window) #endif if (data->created) { DestroyWindow(data->hwnd); - if (data->parent) { + if (data->destroy_parent_with_window && data->parent) { DestroyWindow(data->parent); } } else { @@ -490,29 +514,81 @@ static void CleanupWindowData(_THIS, SDL_Window *window) window->driverdata = NULL; } +static void WIN_ConstrainPopup(SDL_Window *window) +{ + /* Clamp popup windows to the output borders */ + if (SDL_WINDOW_IS_POPUP(window)) { + SDL_Window *w; + SDL_DisplayID displayID; + SDL_Rect rect; + int abs_x = window->x; + int abs_y = window->y; + int offset_x = 0, offset_y = 0; + + /* Calculate the total offset from the parents */ + for (w = window->parent; w->parent != NULL; w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + + offset_x += w->x; + offset_y += w->y; + abs_x += offset_x; + abs_y += offset_y; + + /* Constrain the popup window to the display of the toplevel parent */ + displayID = SDL_GetDisplayForWindow(w); + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + window->w > rect.x + rect.w) { + abs_x -= (abs_x + window->w) - (rect.x + rect.w); + } + if (abs_y + window->h > rect.y + rect.h) { + abs_y -= (abs_y + window->h) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); + + window->x = window->windowed.x = abs_x - offset_x; + window->y = window->windowed.y = abs_y - offset_y; + } +} + +static void WIN_SetKeyboardFocus(SDL_Window *window) +{ + SDL_Window *topmost = window; + + /* Find the topmost parent */ + while (topmost->parent != NULL) { + topmost = topmost->parent; + } + + topmost->driverdata->keyboard_focus = window; + SDL_SetKeyboardFocus(window); +} + int WIN_CreateWindow(_THIS, SDL_Window *window) { HWND hwnd, parent = NULL; DWORD style = STYLE_BASIC; + DWORD styleEx = 0; int x, y; int w, h; - if (window->flags & SDL_WINDOW_SKIP_TASKBAR) { + if (SDL_WINDOW_IS_POPUP(window)) { + parent = window->parent->driverdata->hwnd; + } else if (window->flags & SDL_WINDOW_SKIP_TASKBAR) { parent = CreateWindow(SDL_Appname, TEXT(""), STYLE_BASIC, 0, 0, 32, 32, NULL, NULL, SDL_Instance, NULL); } style |= GetWindowStyle(window); + styleEx |= GetWindowStyleEx(window); /* Figure out what the window area will be */ + WIN_ConstrainPopup(window); WIN_AdjustWindowRectWithStyle(window, style, FALSE, &x, &y, &w, &h, SDL_FALSE); - if (window->undefined_x && window->undefined_y && - window->last_displayID == SDL_GetPrimaryDisplay()) { - x = CW_USEDEFAULT; - y = CW_USEDEFAULT; /* Not actually used */ - } - - hwnd = CreateWindow(SDL_Appname, TEXT(""), style, x, y, w, h, parent, NULL, SDL_Instance, NULL); + hwnd = CreateWindowEx(styleEx, SDL_Appname, TEXT(""), style, + x, y, w, h, parent, NULL, SDL_Instance, NULL); if (!hwnd) { return WIN_SetError("Couldn't create window"); } @@ -711,12 +787,13 @@ void WIN_SetWindowPosition(_THIS, SDL_Window *window) /* HighDPI support: removed SWP_NOSIZE. If the move results in a DPI change, we need to allow * the window to resize (e.g. AdjustWindowRectExForDpi frame sizes are different). */ - WIN_SetWindowPositionInternal(_this, window, SWP_NOCOPYBITS | SWP_NOACTIVATE); + WIN_ConstrainPopup(window); + WIN_SetWindowPositionInternal(window, SWP_NOCOPYBITS | SWP_NOACTIVATE); } void WIN_SetWindowSize(_THIS, SDL_Window *window) { - WIN_SetWindowPositionInternal(_this, window, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(window, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOACTIVATE); } int WIN_GetWindowBordersSize(_THIS, SDL_Window *window, int *top, int *left, int *bottom, int *right) @@ -814,12 +891,32 @@ void WIN_ShowWindow(_THIS, SDL_Window *window) nCmdShow = SW_SHOWNOACTIVATE; } ShowWindow(hwnd, nCmdShow); + + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (window->parent == SDL_GetKeyboardFocus()) { + WIN_SetKeyboardFocus(window); + } + } } void WIN_HideWindow(_THIS, SDL_Window *window) { HWND hwnd = window->driverdata->hwnd; ShowWindow(hwnd, SW_HIDE); + + /* Transfer keyboard focus back to the parent */ + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (window == SDL_GetKeyboardFocus()) { + SDL_Window *new_focus = window->parent; + + /* Find the highest level window that isn't being hidden or destroyed. */ + while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { + new_focus = new_focus->parent; + } + + WIN_SetKeyboardFocus(new_focus); + } + } } void WIN_RaiseWindow(_THIS, SDL_Window *window) @@ -885,7 +982,7 @@ void WIN_SetWindowBordered(_THIS, SDL_Window *window, SDL_bool bordered) data->in_border_change = SDL_TRUE; SetWindowLong(hwnd, GWL_STYLE, style); - WIN_SetWindowPositionInternal(_this, window, SWP_NOCOPYBITS | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(window, SWP_NOCOPYBITS | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE); data->in_border_change = SDL_FALSE; } @@ -1228,7 +1325,7 @@ void WIN_OnWindowEnter(_THIS, SDL_Window *window) } if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { - WIN_SetWindowPositionInternal(_this, window, SWP_NOCOPYBITS | SWP_NOSIZE | SWP_NOACTIVATE); + WIN_SetWindowPositionInternal(window, SWP_NOCOPYBITS | SWP_NOSIZE | SWP_NOACTIVATE); } } diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 9e31e583c6..2e345b0120 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -62,8 +62,10 @@ struct SDL_WindowData RECT cursor_clipped_rect; SDL_Point last_raw_mouse_position; SDL_bool mouse_tracked; + SDL_bool destroy_parent_with_window; SDL_DisplayID last_displayID; WCHAR *ICMFileName; + SDL_Window *keyboard_focus; struct SDL_VideoData *videodata; #if SDL_VIDEO_OPENGL_EGL EGLSurface egl_surface; @@ -111,6 +113,7 @@ extern void WIN_ClientPointFromSDLFloat(const SDL_Window *window, float x, float extern void WIN_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int WIN_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation); extern void WIN_UpdateDarkModeForHWND(HWND hwnd); +extern void WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags); /* Ends C function definitions when using C++ */ #ifdef __cplusplus