From d84772d620ce961108ab33e9dba27855ee5b0616 Mon Sep 17 00:00:00 2001 From: Camilla Berglund Date: Thu, 13 Feb 2014 02:57:59 +0100 Subject: [PATCH] Add size limits and aspect ratio functions Fixes #555. --- README.md | 2 + docs/news.dox | 5 ++ docs/window.dox | 39 ++++++++++++ examples/boing.c | 2 + include/GLFW/glfw3.h | 72 ++++++++++++++++++++++ src/cocoa_window.m | 23 ++++++++ src/internal.h | 10 ++++ src/mir_window.c | 14 +++++ src/win32_platform.h | 4 ++ src/win32_window.c | 138 +++++++++++++++++++++++++++++++++++++++---- src/window.c | 34 +++++++++++ src/wl_window.c | 14 +++++ src/x11_window.c | 55 +++++++++++++++++ 13 files changed, 399 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index edf21983..0213356e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ GLFW bundles a number of dependencies in the `deps/` directory. ## Changelog + - Added `glfwSetWindowSizeLimits` and `glfwSetWindowAspectRatio` for setting + absolute and relative window size limits - Added `GLFW_TRUE` and `GLFW_FALSE` as client API independent boolean values - Removed dependency on external OpenGL or OpenGL ES headers - [WGL] Removed dependency on external WGL headers diff --git a/docs/news.dox b/docs/news.dox index eca16207..bde1af0e 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -4,6 +4,11 @@ @section news_32 New features in 3.2 +@subsection news_32_sizelimits Window size limit support + +GLFW now supports setting both absolute and relative window size limits with +@ref glfwSetWindowSizeLimits and @ref glfwSetWindowAspectRatio. + @section news_31 New features in 3.1 diff --git a/docs/window.dox b/docs/window.dox index 648f5afc..a183f65c 100644 --- a/docs/window.dox +++ b/docs/window.dox @@ -485,6 +485,45 @@ The size of a framebuffer may change independently of the size of a window, for example if the window is dragged between a regular monitor and a high-DPI one. +@subsection window_sizelimits Window size limits + +The minimum and maximum size of the client area of a windowed mode window can be +set with @ref glfwSetWindowSizeLimits. The user may resize the window to any +size and aspect ratio within the specified limits, unless the aspect ratio is +also set. + +@code +glfwSetWindowSizeLimits(window, 200, 200, 400, 400); +@endcode + +To disable size limits for a window, set them to `GLFW_DONT_CARE`. + +@code +glfwSetWindowSizeLimits(window, GLFW_DONT_CARE, GLFW_DONT_CARE, GLFW_DONT_CARE, GLFW_DONT_CARE); +@endcode + +The aspect ratio of the client area of a windowed mode window can be set with +@ref glfwSetWindowAspectRatio. The user may resize the window freely unless +size limits are also set, but the size will be constrained to maintain the +aspect ratio. + +The aspect ratio is specified as a numerator and denominator, corresponding to +the width and height, respectively. + +@code +glfwSetWindowAspectRatio(window, 16, 9); +@endcode + +To disable aspect ratio for a window, set it to `GLFW_DONT_CARE`. + +@code +glfwSetWindowAspectRatio(window, GLFW_DONT_CARE, GLFW_DONT_CARE); +@endcode + +You can have both size limits and aspect ratio set for a window, but the results +are undefined if they conflict. + + @subsection window_pos Window position The position of a windowed-mode window can be changed with @ref diff --git a/examples/boing.c b/examples/boing.c index f2ea3da9..886cf1f4 100644 --- a/examples/boing.c +++ b/examples/boing.c @@ -611,6 +611,8 @@ int main( void ) exit( EXIT_FAILURE ); } + glfwSetWindowAspectRatio(window, 1, 1); + glfwSetFramebufferSizeCallback(window, reshape); glfwSetKeyCallback(window, key_callback); glfwSetMouseButtonCallback(window, mouse_button_callback); diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index bb473335..1348785e 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1845,6 +1845,78 @@ GLFWAPI void glfwSetWindowPos(GLFWwindow* window, int xpos, int ypos); */ GLFWAPI void glfwGetWindowSize(GLFWwindow* window, int* width, int* height); +/*! @brief Sets the size limits of the specified window. + * + * This function sets the size limits of the client area of the specified + * window. If the window is full screen or not resizable, this function does + * nothing. + * + * The size limits are applied immediately and may cause the window to be + * resized. + * + * @param[in] window The window to set limits for. + * @param[in] minwidth The minimum width, in screen coordinates, of the client + * area, or `GLFW_DONT_CARE`. + * @param[in] minheight The minimum height, in screen coordinates, of the + * client area, or `GLFW_DONT_CARE`. + * @param[in] maxwidth The maximum width, in screen coordinates, of the client + * area, or `GLFW_DONT_CARE`. + * @param[in] maxheight The maximum height, in screen coordinates, of the + * client area, or `GLFW_DONT_CARE`. + * + * @remarks If you set size limits and an aspect ratio that conflict, the + * results are undefined. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_sizelimits + * @sa glfwSetWindowAspectRatio + * + * @since Added in GLFW 3.2. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight); + +/*! @brief Sets the aspect ratio of the specified window. + * + * This function sets the required aspect ratio of the client area of the + * specified window. If the window is full screen or not resizable, this + * function does nothing. + * + * The aspect ratio is specified as a numerator and a denominator. For + * example, the common 16:9 aspect ratio is specified as 16 and 9, + * respectively. The denominator may not be zero. + * + * If the numerator and denominator is set to `GLFW_DONT_CARE` then the window + * may be resized to any aspect ratio permitted by the window system and any + * limits set by @ref glfwSetWindowSizeLimits. + * + * The aspect ratio is applied immediately and may cause the window to be + * resized. + * + * @param[in] window The window to set limits for. + * @param[in] numer The numerator of the desired aspect ratio, or + * `GLFW_DONT_CARE`. + * @param[in] denom The denominator of the desired aspect ratio, or + * `GLFW_DONT_CARE`. + * + * @remarks If you set size limits and an aspect ratio that conflict, the + * results are undefined. + * + * @par Thread Safety + * This function may only be called from the main thread. + * + * @sa @ref window_sizelimits + * @sa glfwSetWindowSizeLimits + * + * @since Added in GLFW 3.2. + * + * @ingroup window + */ +GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* window, int numer, int denom); + /*! @brief Sets the size of the client area of the specified window. * * This function sets the size, in screen coordinates, of the client area of diff --git a/src/cocoa_window.m b/src/cocoa_window.m index a0b29693..a3527e39 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -1004,6 +1004,29 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) [window->ns.object setContentSize:NSMakeSize(width, height)]; } +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) + [window->ns.object setContentMinSize:NSMakeSize(0, 0)]; + else + [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)]; + + if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE) + [window->ns.object setContentMaxSize:NSMakeSize(0, 0)]; + else + [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)]; +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) + [window->ns.object setContentAspectRatio:NSMakeSize(0, 0)]; + else + [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; +} + void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = [window->ns.view frame]; diff --git a/src/internal.h b/src/internal.h index a5579612..56647a31 100644 --- a/src/internal.h +++ b/src/internal.h @@ -540,6 +540,16 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height); */ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height); +/*! @copydoc glfwSetWindowSizeLimits + * @ingroup platform + */ +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight); + +/*! @copydoc glfwSetWindowAspectRatio + * @ingroup platform + */ +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom); + /*! @copydoc glfwGetFramebufferSize * @ingroup platform */ diff --git a/src/mir_window.c b/src/mir_window.c index 7acd77e1..23cfa520 100644 --- a/src/mir_window.c +++ b/src/mir_window.c @@ -529,6 +529,20 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) mir_surface_spec_release(spec); } +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + _glfwInputError(GLFW_PLATFORM_ERROR, + "Mir: Unsupported function %s", __PRETTY_FUNCTION__); +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + _glfwInputError(GLFW_PLATFORM_ERROR, + "Mir: Unsupported function %s", __PRETTY_FUNCTION__); +} + void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) { _glfwInputError(GLFW_PLATFORM_ERROR, diff --git a/src/win32_platform.h b/src/win32_platform.h index 9be21c8d..0068fbe2 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -154,6 +154,10 @@ typedef struct _GLFWwindowWin32 GLFWbool cursorTracked; GLFWbool iconified; + int minwidth, minheight; + int maxwidth, maxheight; + int numer, denom; + // The last received cursor position, regardless of source int cursorPosX, cursorPosY; diff --git a/src/win32_window.c b/src/win32_window.c index 5f60b9b6..098306ee 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -69,6 +69,47 @@ static DWORD getWindowExStyle(const _GLFWwindow* window) return style; } +// Translate client window size to full window size (including window borders) +// +static void getFullWindowSize(_GLFWwindow* window, + int clientWidth, int clientHeight, + int* fullWidth, int* fullHeight) +{ + RECT rect = { 0, 0, clientWidth, clientHeight }; + AdjustWindowRectEx(&rect, getWindowStyle(window), + FALSE, getWindowExStyle(window)); + *fullWidth = rect.right - rect.left; + *fullHeight = rect.bottom - rect.top; +} + +// Enforce the client rect aspect ratio based on which edge is being dragged +// +static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area) +{ + int xoff, yoff; + const float ratio = (float) window->win32.numer / + (float) window->win32.denom; + + getFullWindowSize(window, 0, 0, &xoff, &yoff); + + if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT || + edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT) + { + area->bottom = area->top + yoff + + (int) ((area->right - area->left - xoff) / ratio); + } + else if (edge == WMSZ_TOPLEFT || edge == WMSZ_TOPRIGHT) + { + area->top = area->bottom - yoff - + (int) ((area->right - area->left - xoff) / ratio); + } + else if (edge == WMSZ_TOP || edge == WMSZ_BOTTOM) + { + area->right = area->left + xoff + + (int) ((area->bottom - area->top - yoff) * ratio); + } +} + // Updates the cursor clip rect // static void updateClipRect(_GLFWwindow* window) @@ -503,6 +544,41 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, return 0; } + case WM_SIZING: + { + if (window->win32.numer == GLFW_DONT_CARE || + window->win32.denom == GLFW_DONT_CARE) + { + break; + } + + applyAspectRatio(window, (int) wParam, (RECT*) lParam); + return TRUE; + } + + case WM_GETMINMAXINFO: + { + int xoff, yoff; + MINMAXINFO* mmi = (MINMAXINFO*) lParam; + getFullWindowSize(window, 0, 0, &xoff, &yoff); + + if (window->win32.minwidth != GLFW_DONT_CARE && + window->win32.minheight != GLFW_DONT_CARE) + { + mmi->ptMinTrackSize.x = window->win32.minwidth + xoff; + mmi->ptMinTrackSize.y = window->win32.minheight + yoff; + } + + if (window->win32.maxwidth != GLFW_DONT_CARE && + window->win32.maxheight != GLFW_DONT_CARE) + { + mmi->ptMaxTrackSize.x = window->win32.maxwidth + xoff; + mmi->ptMaxTrackSize.y = window->win32.maxheight + yoff; + } + + return 0; + } + case WM_PAINT: { _glfwInputWindowDamage(window); @@ -582,19 +658,6 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, return DefWindowProc(hWnd, uMsg, wParam, lParam); } -// Translate client window size to full window size (including window borders) -// -static void getFullWindowSize(_GLFWwindow* window, - int clientWidth, int clientHeight, - int* fullWidth, int* fullHeight) -{ - RECT rect = { 0, 0, clientWidth, clientHeight }; - AdjustWindowRectEx(&rect, getWindowStyle(window), - FALSE, getWindowExStyle(window)); - *fullWidth = rect.right - rect.left; - *fullHeight = rect.bottom - rect.top; -} - // Creates the GLFW window and rendering context // static GLFWbool createWindow(_GLFWwindow* window, @@ -677,6 +740,13 @@ static GLFWbool createWindow(_GLFWwindow* window, if (!_glfwCreateContext(window, ctxconfig, fbconfig)) return GLFW_FALSE; + window->win32.minwidth = GLFW_DONT_CARE; + window->win32.minheight = GLFW_DONT_CARE; + window->win32.maxwidth = GLFW_DONT_CARE; + window->win32.maxheight = GLFW_DONT_CARE; + window->win32.numer = GLFW_DONT_CARE; + window->win32.denom = GLFW_DONT_CARE; + return GLFW_TRUE; } @@ -872,6 +942,48 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) } } +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + RECT area; + + window->win32.minwidth = minwidth; + window->win32.minheight = minheight; + window->win32.maxwidth = maxwidth; + window->win32.maxheight = maxheight; + + if ((minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) && + (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)) + { + return; + } + + GetWindowRect(window->win32.handle, &area); + MoveWindow(window->win32.handle, + area.left, area.top, + area.right - area.left, + area.bottom - area.top, TRUE); +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + RECT area; + + window->win32.numer = numer; + window->win32.denom = denom; + + if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) + return; + + GetWindowRect(window->win32.handle, &area); + applyAspectRatio(window, WMSZ_BOTTOMRIGHT, &area); + MoveWindow(window->win32.handle, + area.left, area.top, + area.right - area.left, + area.bottom - area.top, TRUE); +} + void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height); diff --git a/src/window.c b/src/window.c index 1bea6f7f..1b2e1f20 100644 --- a/src/window.c +++ b/src/window.c @@ -481,6 +481,40 @@ GLFWAPI void glfwSetWindowSize(GLFWwindow* handle, int width, int height) _glfwPlatformSetWindowSize(window, width, height); } +GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* handle, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + + _GLFW_REQUIRE_INIT(); + + if (window->monitor || !window->resizable) + return; + + _glfwPlatformSetWindowSizeLimits(window, + minwidth, minheight, + maxwidth, maxheight); +} + +GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* handle, int numer, int denom) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + + _GLFW_REQUIRE_INIT(); + + if (window->monitor || !window->resizable) + return; + + if (!denom) + { + _glfwInputError(GLFW_INVALID_VALUE, "Denominator cannot be zero"); + return; + } + + _glfwPlatformSetWindowAspectRatio(window, numer, denom); +} + GLFWAPI void glfwGetFramebufferSize(GLFWwindow* handle, int* width, int* height) { _GLFWwindow* window = (_GLFWwindow*) handle; diff --git a/src/wl_window.c b/src/wl_window.c index b561a2a7..3c65720d 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -302,6 +302,20 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) window->wl.height = height; } +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + // TODO + fprintf(stderr, "_glfwPlatformSetWindowSizeLimits not implemented yet\n"); +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + // TODO + fprintf(stderr, "_glfwPlatformSetWindowAspectRatio not implemented yet\n"); +} + void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height); diff --git a/src/x11_window.c b/src/x11_window.c index e05d1df1..147ef7b6 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1632,6 +1632,61 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) XFlush(_glfw.x11.display); } +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + long supplied; + XSizeHints* hints = XAllocSizeHints(); + + if (XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied)) + { + if (minwidth == GLFW_DONT_CARE || minwidth == GLFW_DONT_CARE) + hints->flags &= ~PMinSize; + else + { + hints->flags |= PMinSize; + hints->min_width = minwidth; + hints->min_height = minheight; + } + + if (maxwidth == GLFW_DONT_CARE || maxwidth == GLFW_DONT_CARE) + hints->flags &= ~PMaxSize; + else + { + hints->flags |= PMaxSize; + hints->max_width = maxwidth; + hints->max_height = maxheight; + } + + XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); + } + + XFree(hints); +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + long supplied; + XSizeHints* hints = XAllocSizeHints(); + + if (XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied)) + { + if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) + hints->flags &= ~PAspect; + else + { + hints->flags |= PAspect; + hints->min_aspect.x = hints->max_aspect.x = numer; + hints->min_aspect.y = hints->max_aspect.y = denom; + } + + XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); + } + + XFree(hints); +} + void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height);