diff --git a/include/SDL3/SDL_test_common.h b/include/SDL3/SDL_test_common.h index d881303e33..a7ec4dfca2 100644 --- a/include/SDL3/SDL_test_common.h +++ b/include/SDL3/SDL_test_common.h @@ -76,6 +76,8 @@ typedef struct int window_minH; int window_maxW; int window_maxH; + float window_min_aspect; + float window_max_aspect; int logical_w; int logical_h; SDL_bool auto_scale_content; @@ -84,6 +86,7 @@ typedef struct float scale; int depth; float refresh_rate; + SDL_bool fill_usable_bounds; SDL_bool fullscreen_exclusive; SDL_DisplayMode fullscreen_mode; int num_windows; diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 22f128f0fa..88e53c8961 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1346,9 +1346,6 @@ extern SDL_DECLSPEC int SDLCALL SDL_GetWindowPosition(SDL_Window *window, int *x /** * Request that the size of a window's client area be set. * - * NULL can safely be passed as the `w` or `h` parameter if the width or - * height value is not desired. - * * If, at the time of this request, the window in a fixed-size state, such as * maximized or fullscreen, the request will be deferred until the window * exits this state and becomes resizable again. @@ -1385,9 +1382,6 @@ extern SDL_DECLSPEC int SDLCALL SDL_SetWindowSize(SDL_Window *window, int w, int /** * Get the size of a window's client area. * - * NULL can safely be passed as the `w` or `h` parameter if the width or - * height value is not desired. - * * The window pixel size may differ from its window coordinate size if the * window is on a high pixel density display. Use SDL_GetWindowSizeInPixels() * or SDL_GetRenderOutputSize() to get the real client area size in pixels. @@ -1406,6 +1400,55 @@ extern SDL_DECLSPEC int SDLCALL SDL_SetWindowSize(SDL_Window *window, int w, int */ extern SDL_DECLSPEC int SDLCALL SDL_GetWindowSize(SDL_Window *window, int *w, int *h); +/** + * Request that the aspect ratio of a window's client area be set. + * + * The aspect ratio is the ratio of width divided by height, e.g. 2560x1600 would be 1.6. Larger aspect ratios are wider and smaller aspect ratios are narrower. + * + * If, at the time of this request, the window in a fixed-size state, such as + * maximized or fullscreen, the request will be deferred until the window + * exits this state and becomes resizable again. + * + * On some windowing systems, this request is asynchronous and the new window + * aspect ratio may not have have been applied immediately upon the return of this + * function. If an immediate change is required, call SDL_SyncWindow() to + * block until the changes have taken effect. + * + * When the window size changes, an SDL_EVENT_WINDOW_RESIZED event will be + * emitted with the new window dimensions. Note that the new dimensions may + * not match the exact aspect ratio requested, as some windowing systems can restrict + * the window size in certain scenarios (e.g. constraining the size of the + * content area to remain within the usable desktop bounds). Additionally, as + * this is just a request, it can be denied by the windowing system. + * + * \param window the window to change + * \param min_aspect the minimum aspect ratio of the window, or 0.0f for no limit + * \param max_aspect the maximum aspect ratio of the window, or 0.0f for no limit + * \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. + * + * \sa SDL_GetWindowAspectRatio + * \sa SDL_SyncWindow + */ +extern SDL_DECLSPEC int SDLCALL SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect); + +/** + * Get the size of a window's client area. + * + * \param window the window to query the width and height from + * \param min_aspect a pointer filled in with the minimum aspect ratio of the window, may be NULL + * \param max_aspect a pointer filled in with the maximum aspect ratio of the window, 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. + * + * \sa SDL_SetWindowAspectRatio + */ +extern SDL_DECLSPEC int SDLCALL SDL_GetWindowAspectRatio(SDL_Window *window, float *min_aspect, float *max_aspect); + /** * Get the size of a window's borders (decorations) around the client area. * diff --git a/src/SDL_utils.c b/src/SDL_utils.c index 581964b0d8..e2ce2939d7 100644 --- a/src/SDL_utils.c +++ b/src/SDL_utils.c @@ -49,6 +49,45 @@ int SDL_powerof2(int x) return value; } +// Algorithm adapted with thanks from John Cook's blog post: +// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation +void SDL_CalculateFraction(float x, int *numerator, int *denominator) +{ + const int N = 1000; + int a = 0, b = 1; + int c = 1, d = 0; + + while (b <= N && d <= N) { + float mediant = (float)(a + c) / (b + d); + if (x == mediant) { + if (b + d <= N) { + *numerator = a + c; + *denominator = b + d; + } else if (d > b) { + *numerator = c; + *denominator = d; + } else { + *numerator = a; + *denominator = b; + } + return; + } else if (x > mediant) { + a = a + c; + b = b + d; + } else { + c = a + c; + d = b + d; + } + } + if (b > N) { + *numerator = c; + *denominator = d; + } else { + *numerator = a; + *denominator = b; + } +} + SDL_bool SDL_endswith(const char *string, const char *suffix) { size_t string_length = string ? SDL_strlen(string) : 0; diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h index 98658c3613..bf0a4fc81e 100644 --- a/src/SDL_utils_c.h +++ b/src/SDL_utils_c.h @@ -28,6 +28,8 @@ /* Return the smallest power of 2 greater than or equal to 'x' */ extern int SDL_powerof2(int x); -SDL_bool SDL_endswith(const char *string, const char *suffix); +extern void SDL_CalculateFraction(float x, int *numerator, int *denominator); + +extern SDL_bool SDL_endswith(const char *string, const char *suffix); #endif /* SDL_utils_h_ */ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index d91b6f14b6..53de3ff3ef 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -453,6 +453,7 @@ SDL3_0.0.0 { SDL_GetUserFolder; SDL_GetVersion; SDL_GetVideoDriver; + SDL_GetWindowAspectRatio; SDL_GetWindowBordersSize; SDL_GetWindowDisplayScale; SDL_GetWindowFlags; @@ -748,6 +749,7 @@ SDL3_0.0.0 { SDL_SetTextureScaleMode; SDL_SetThreadPriority; SDL_SetWindowAlwaysOnTop; + SDL_SetWindowAspectRatio; SDL_SetWindowBordered; SDL_SetWindowFocusable; SDL_SetWindowFullscreen; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 967b6bfb26..f64025b4ea 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -478,6 +478,7 @@ #define SDL_GetUserFolder SDL_GetUserFolder_REAL #define SDL_GetVersion SDL_GetVersion_REAL #define SDL_GetVideoDriver SDL_GetVideoDriver_REAL +#define SDL_GetWindowAspectRatio SDL_GetWindowAspectRatio_REAL #define SDL_GetWindowBordersSize SDL_GetWindowBordersSize_REAL #define SDL_GetWindowDisplayScale SDL_GetWindowDisplayScale_REAL #define SDL_GetWindowFlags SDL_GetWindowFlags_REAL @@ -773,6 +774,7 @@ #define SDL_SetTextureScaleMode SDL_SetTextureScaleMode_REAL #define SDL_SetThreadPriority SDL_SetThreadPriority_REAL #define SDL_SetWindowAlwaysOnTop SDL_SetWindowAlwaysOnTop_REAL +#define SDL_SetWindowAspectRatio SDL_SetWindowAspectRatio_REAL #define SDL_SetWindowBordered SDL_SetWindowBordered_REAL #define SDL_SetWindowFocusable SDL_SetWindowFocusable_REAL #define SDL_SetWindowFullscreen SDL_SetWindowFullscreen_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 251660ff98..1cbcb92d15 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -498,6 +498,7 @@ SDL_DYNAPI_PROC(SDL_Finger**,SDL_GetTouchFingers,(SDL_TouchID a, int *b),(a,b),r SDL_DYNAPI_PROC(char*,SDL_GetUserFolder,(SDL_Folder a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetVersion,(void),(),return) SDL_DYNAPI_PROC(const char*,SDL_GetVideoDriver,(int a),(a),return) +SDL_DYNAPI_PROC(int,SDL_GetWindowAspectRatio,(SDL_Window *a, float *b, float *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetWindowBordersSize,(SDL_Window *a, int *b, int *c, int *d, int *e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(float,SDL_GetWindowDisplayScale,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_WindowFlags,SDL_GetWindowFlags,(SDL_Window *a),(a),return) @@ -783,6 +784,7 @@ SDL_DYNAPI_PROC(int,SDL_SetTextureColorModFloat,(SDL_Texture *a, float b, float SDL_DYNAPI_PROC(int,SDL_SetTextureScaleMode,(SDL_Texture *a, SDL_ScaleMode b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetThreadPriority,(SDL_ThreadPriority a),(a),return) SDL_DYNAPI_PROC(int,SDL_SetWindowAlwaysOnTop,(SDL_Window *a, SDL_bool b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_SetWindowAspectRatio,(SDL_Window *a, float b, float c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetWindowBordered,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetWindowFocusable,(SDL_Window *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_SetWindowFullscreen,(SDL_Window *a, SDL_bool b),(a,b),return) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index f63db16af5..25d64352a6 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -35,6 +35,7 @@ static const char *common_usage[] = { static const char *video_usage[] = { "[--always-on-top]", + "[--aspect min-max]", "[--auto-scale-content]", "[--center | --position X,Y]", "[--confine-cursor X,Y,W,H]", @@ -373,12 +374,7 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index) return 2; } if (SDL_strcasecmp(argv[index], "--usable-bounds") == 0) { - /* !!! FIXME: this is a bit of a hack, but I don't want to add a - !!! FIXME: flag to the public structure in 2.0.x */ - state->window_x = -1; - state->window_y = -1; - state->window_w = -1; - state->window_h = -1; + state->fill_usable_bounds = SDL_TRUE; return 1; } if (SDL_strcasecmp(argv[index], "--geometry") == 0) { @@ -438,6 +434,26 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index) state->window_maxH = SDL_atoi(h); return 2; } + if (SDL_strcasecmp(argv[index], "--aspect") == 0) { + char *min_aspect, *max_aspect; + ++index; + if (!argv[index]) { + return -1; + } + min_aspect = argv[index]; + max_aspect = argv[index]; + while (*max_aspect && *max_aspect != '-') { + ++max_aspect; + } + if (*max_aspect) { + *max_aspect++ = '\0'; + } else { + max_aspect = min_aspect; + } + state->window_min_aspect = SDL_atof(min_aspect); + state->window_max_aspect = SDL_atof(max_aspect); + return 2; + } if (SDL_strcasecmp(argv[index], "--logical") == 0) { char *w, *h; ++index; @@ -1308,19 +1324,18 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) SDL_Rect r; SDL_PropertiesID props; - r.x = state->window_x; - r.y = state->window_y; - r.w = state->window_w; - r.h = state->window_h; - if (state->auto_scale_content) { - float scale = SDL_GetDisplayContentScale(state->displayID); - r.w = (int)SDL_ceilf(r.w * scale); - r.h = (int)SDL_ceilf(r.h * scale); - } - - /* !!! FIXME: hack to make --usable-bounds work for now. */ - if ((r.x == -1) && (r.y == -1) && (r.w == -1) && (r.h == -1)) { + if (state->fill_usable_bounds) { SDL_GetDisplayUsableBounds(state->displayID, &r); + } else { + r.x = state->window_x; + r.y = state->window_y; + r.w = state->window_w; + r.h = state->window_h; + if (state->auto_scale_content) { + float scale = SDL_GetDisplayContentScale(state->displayID); + r.w = (int)SDL_ceilf(r.w * scale); + r.h = (int)SDL_ceilf(r.h * scale); + } } if (state->num_windows > 1) { @@ -1349,6 +1364,9 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) if (state->window_maxW || state->window_maxH) { SDL_SetWindowMaximumSize(state->windows[i], state->window_maxW, state->window_maxH); } + if (state->window_min_aspect || state->window_max_aspect) { + SDL_SetWindowAspectRatio(state->windows[i], state->window_min_aspect, state->window_max_aspect); + } SDL_GetWindowSize(state->windows[i], &w, &h); if (!(state->window_flags & SDL_WINDOW_RESIZABLE) && (w != r.w || h != r.h)) { SDL_Log("Window requested size %dx%d, got %dx%d\n", r.w, r.h, w, h); @@ -2376,15 +2394,21 @@ int SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event break; case SDLK_a: if (withControl) { - /* Ctrl-A reports absolute mouse position. */ - float x, y; - const SDL_MouseButtonFlags mask = SDL_GetGlobalMouseState(&x, &y); - SDL_Log("ABSOLUTE MOUSE: (%g, %g)%s%s%s%s%s\n", x, y, - (mask & SDL_BUTTON_LMASK) ? " [LBUTTON]" : "", - (mask & SDL_BUTTON_MMASK) ? " [MBUTTON]" : "", - (mask & SDL_BUTTON_RMASK) ? " [RBUTTON]" : "", - (mask & SDL_BUTTON_X1MASK) ? " [X2BUTTON]" : "", - (mask & SDL_BUTTON_X2MASK) ? " [X2BUTTON]" : ""); + /* Ctrl-A toggle aspect ratio */ + SDL_Window *window = SDL_GetWindowFromID(event->key.windowID); + if (window) { + float min_aspect = 0.0f, max_aspect = 0.0f; + + SDL_GetWindowAspectRatio(window, &min_aspect, &max_aspect); + if (min_aspect > 0.0f || max_aspect > 0.0f) { + min_aspect = 0.0f; + max_aspect = 0.0f; + } else { + min_aspect = 1.0f; + max_aspect = 1.0f; + } + SDL_SetWindowAspectRatio(window, min_aspect, max_aspect); + } } break; case SDLK_0: diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 1de2c3d74a..ba9b6e54d1 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -45,6 +45,8 @@ struct SDL_Window int w, h; int min_w, min_h; int max_w, max_h; + float min_aspect; + float max_aspect; int last_pixel_w, last_pixel_h; SDL_WindowFlags flags; SDL_WindowFlags pending_flags; @@ -240,6 +242,7 @@ struct SDL_VideoDevice void (*SetWindowSize)(SDL_VideoDevice *_this, SDL_Window *window); void (*SetWindowMinimumSize)(SDL_VideoDevice *_this, SDL_Window *window); void (*SetWindowMaximumSize)(SDL_VideoDevice *_this, SDL_Window *window); + void (*SetWindowAspectRatio)(SDL_VideoDevice *_this, SDL_Window *window); int (*GetWindowBordersSize)(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right); void (*GetWindowSizeInPixels)(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); int (*SetWindowOpacity)(SDL_VideoDevice *_this, SDL_Window *window, float opacity); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 7f30b46ffa..1d4911941e 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -2504,6 +2504,10 @@ int SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags) _this->SetWindowMaximumSize(_this, window); } + if (_this->SetWindowAspectRatio && (window->min_aspect > 0.0f || window->max_aspect > 0.0f)) { + _this->SetWindowAspectRatio(_this, window); + } + if (window->hit_test) { _this->SetWindowHitTest(window, SDL_TRUE); } @@ -2776,6 +2780,7 @@ int SDL_SetWindowAlwaysOnTop(SDL_Window *window, SDL_bool on_top) int SDL_SetWindowSize(SDL_Window *window, int w, int h) { CHECK_WINDOW_MAGIC(window, -1); + if (w <= 0) { return SDL_InvalidParamError("w"); } @@ -2783,6 +2788,16 @@ int SDL_SetWindowSize(SDL_Window *window, int w, int h) return SDL_InvalidParamError("h"); } + /* It is possible for the aspect ratio contraints to not satisfy the size constraints. */ + /* The size constraints will override the aspect ratio contraints so we will apply the */ + /* the aspect ratio constraints first */ + float new_aspect = w / (float)h; + if (window->max_aspect > 0.0f && new_aspect > window->max_aspect) { + w = (int)SDL_roundf(h * window->max_aspect); + } else if (window->min_aspect > 0.0f && new_aspect < window->min_aspect) { + h = (int)SDL_roundf(w / window->min_aspect); + } + /* Make sure we don't exceed any window size limits */ if (window->min_w && w < window->min_w) { w = window->min_w; @@ -2821,6 +2836,31 @@ int SDL_GetWindowSize(SDL_Window *window, int *w, int *h) return 0; } +int SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect) +{ + CHECK_WINDOW_MAGIC(window, -1); + + window->min_aspect = min_aspect; + window->max_aspect = max_aspect; + if (_this->SetWindowAspectRatio) { + _this->SetWindowAspectRatio(_this, window); + } + return SDL_SetWindowSize(window, window->floating.w, window->floating.h); +} + +int SDL_GetWindowAspectRatio(SDL_Window *window, float *min_aspect, float *max_aspect) +{ + CHECK_WINDOW_MAGIC(window, -1); + + if (min_aspect) { + *min_aspect = window->min_aspect; + } + if (max_aspect) { + *max_aspect = window->max_aspect; + } + return 0; +} + int SDL_GetWindowBordersSize(SDL_Window *window, int *top, int *left, int *bottom, int *right) { int dummy = 0; diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 067c23af4c..005f75ed74 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -986,6 +986,27 @@ static NSCursor *Cocoa_GetDesiredCursor(void) _data.checking_zoom = NO; } + if (window->min_aspect > 0.0f || window->max_aspect > 0.0f) { + NSWindow *nswindow = _data.nswindow; + NSRect newContentRect = [nswindow contentRectForFrameRect:NSMakeRect(0, 0, frameSize.width, frameSize.height)]; + NSSize newSize = newContentRect.size; + CGFloat minAspectRatio = window->min_aspect; + CGFloat maxAspectRatio = window->max_aspect; + CGFloat aspectRatio; + + if (newSize.height > 0) { + aspectRatio = newSize.width / newSize.height; + + if (maxAspectRatio > 0.0f && aspectRatio > maxAspectRatio) { + newSize.width = (int)SDL_roundf(newSize.height * maxAspectRatio); + } else if (minAspectRatio > 0.0f && aspectRatio < minAspectRatio) { + newSize.height = (int)SDL_roundf(newSize.width / minAspectRatio); + } + + NSRect newFrameRect = [nswindow frameRectForContentRect:NSMakeRect(0, 0, newSize.width, newSize.height)]; + frameSize = newFrameRect.size; + } + } return frameSize; } diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 3aff7ed0db..b4dda500b1 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -1479,6 +1479,134 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks); } break; + case WM_SIZING: + { + Uint32 edge = wParam; + RECT* dragRect = (RECT*)lParam; + RECT clientDragRect = *dragRect; + SDL_bool lock_aspect_ratio = (data->window->max_aspect == data->window->min_aspect) ? SDL_TRUE : SDL_FALSE; + RECT rc; + LONG w, h; + float new_aspect; + + /* if aspect ratio constraints are not enabled then skip this message */ + if (data->window->min_aspect <= 0 && data->window->max_aspect <= 0) { + break; + } + + /* unadjust the dragRect from the window rect to the client rect */ + SetRectEmpty(&rc); + if (!AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) { + break; + } + + clientDragRect.left -= rc.left; + clientDragRect.top -= rc.top; + clientDragRect.right -= rc.right; + clientDragRect.bottom -= rc.bottom; + + w = clientDragRect.right - clientDragRect.left; + h = clientDragRect.bottom - clientDragRect.top; + new_aspect = w / (float)h; + + /* handle the special case in which the min ar and max ar are the same so the window can size symmetrically */ + if (lock_aspect_ratio) { + switch (edge) { + case WMSZ_LEFT: + case WMSZ_RIGHT: + h = (int)(w / data->window->max_aspect); + break; + default: + /* resizing via corners or top or bottom */ + w = (int)(h*data->window->max_aspect); + break; + } + } else { + switch (edge) { + case WMSZ_LEFT: + case WMSZ_RIGHT: + if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) { + w = (int)SDL_roundf(h * data->window->max_aspect); + } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) { + w = (int)SDL_roundf(h * data->window->min_aspect); + } + break; + case WMSZ_TOP: + case WMSZ_BOTTOM: + if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) { + h = (int)SDL_roundf(w / data->window->min_aspect); + } else if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) { + h = (int)SDL_roundf(w / data->window->max_aspect); + } + break; + + default: + /* resizing via corners */ + if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) { + w = (int)SDL_roundf(h * data->window->max_aspect); + } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) { + h = (int)SDL_roundf(w / data->window->min_aspect); + } + break; + } + } + + switch (edge) { + case WMSZ_LEFT: + clientDragRect.left = clientDragRect.right - w; + if (lock_aspect_ratio) { + clientDragRect.top = (clientDragRect.bottom + clientDragRect.top - h) / 2; + } + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_BOTTOMLEFT: + clientDragRect.left = clientDragRect.right - w; + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_RIGHT: + clientDragRect.right = w + clientDragRect.left; + if (lock_aspect_ratio) { + clientDragRect.top = (clientDragRect.bottom + clientDragRect.top - h) / 2; + } + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_TOPRIGHT: + clientDragRect.right = w + clientDragRect.left; + clientDragRect.top = clientDragRect.bottom - h; + break; + case WMSZ_TOP: + if (lock_aspect_ratio) { + clientDragRect.left = (clientDragRect.right + clientDragRect.left - w) / 2; + } + clientDragRect.right = w + clientDragRect.left; + clientDragRect.top = clientDragRect.bottom - h; + break; + case WMSZ_TOPLEFT: + clientDragRect.left = clientDragRect.right - w; + clientDragRect.top = clientDragRect.bottom - h; + break; + case WMSZ_BOTTOM: + if (lock_aspect_ratio) { + clientDragRect.left = (clientDragRect.right + clientDragRect.left - w) / 2; + } + clientDragRect.right = w + clientDragRect.left; + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_BOTTOMRIGHT: + clientDragRect.right = w + clientDragRect.left; + clientDragRect.bottom = h + clientDragRect.top; + break; + } + + /* convert the client rect to a window rect */ + if (!AdjustWindowRectEx(&clientDragRect, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) { + break; + } + + *dragRect = clientDragRect; + } + break; + case WM_SETCURSOR: { Uint16 hittest; diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 952c501d97..4a2692faa4 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -194,6 +194,7 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->SetWindowSize = X11_SetWindowSize; device->SetWindowMinimumSize = X11_SetWindowMinimumSize; device->SetWindowMaximumSize = X11_SetWindowMaximumSize; + device->SetWindowAspectRatio = X11_SetWindowAspectRatio; device->GetWindowBordersSize = X11_GetWindowBordersSize; device->SetWindowOpacity = X11_SetWindowOpacity; device->SetWindowModalFor = X11_SetWindowModalFor; diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index e2dca2d5d0..02737c6480 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -28,6 +28,7 @@ #include "../../events/SDL_mouse_c.h" #include "../../events/SDL_events_c.h" #include "../../core/unix/SDL_appid.h" +#include "../../SDL_utils_c.h" #include "SDL_x11video.h" #include "SDL_x11mouse.h" @@ -1086,7 +1087,7 @@ void X11_SetWindowMinMax(SDL_Window *window, SDL_bool use_current) long hint_flags = 0; X11_XGetWMNormalHints(display, data->xwindow, sizehints, &hint_flags); - sizehints->flags &= ~(PMinSize | PMaxSize); + sizehints->flags &= ~(PMinSize | PMaxSize | PAspect); if (data->window->flags & SDL_WINDOW_RESIZABLE) { if (data->window->min_w || data->window->min_h) { @@ -1099,6 +1100,11 @@ void X11_SetWindowMinMax(SDL_Window *window, SDL_bool use_current) sizehints->max_width = data->window->max_w; sizehints->max_height = data->window->max_h; } + if (data->window->min_aspect > 0.0f || data->window->max_aspect > 0.0f) { + sizehints->flags |= PAspect; + SDL_CalculateFraction(data->window->min_aspect, &sizehints->min_aspect.x, &sizehints->min_aspect.y); + SDL_CalculateFraction(data->window->max_aspect, &sizehints->max_aspect.x, &sizehints->max_aspect.y); + } } else { /* Set the min/max to the same values to make the window non-resizable */ sizehints->flags |= PMinSize | PMaxSize; @@ -1132,6 +1138,17 @@ void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) } } +void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (window->driverdata->pending_operation & X11_PENDING_OP_FULLSCREEN) { + X11_SyncWindow(_this, window); + } + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + X11_SetWindowMinMax(window, SDL_TRUE); + } +} + void X11_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *data = window->driverdata; @@ -1645,7 +1662,7 @@ static int X11_SetWindowFullscreenViaWM(SDL_VideoDevice *_this, SDL_Window *wind long flags = 0; X11_XGetWMNormalHints(display, data->xwindow, sizehints, &flags); /* we are going fullscreen so turn the flags off */ - sizehints->flags &= ~(PMinSize | PMaxSize); + sizehints->flags &= ~(PMinSize | PMaxSize | PAspect); X11_XSetWMNormalHints(display, data->xwindow, sizehints); X11_XFree(sizehints); } diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index 58b2a253d3..204c7e4af5 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -114,6 +114,7 @@ extern int X11_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Sur extern int X11_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window); extern void X11_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window); +extern void X11_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window); extern int X11_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right); extern int X11_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity); extern int X11_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window);