Browse Source

Windows: Support Windows 11 window snapping

When running on Windows 11 don't need to extend the client frame
anymore. This allows us to give back the titlebar space to the window manager.
Hovering over the maximize button now yields in the snapping popup to appear.
spotless
Jannis Weis 3 years ago
parent
commit
7eef638c11
No known key found for this signature in database
GPG Key ID: 7C9D8D4B558049AB
  1. 128
      windows/src/main/cpp/Decorations.cpp
  2. 3
      windows/src/main/cpp/Decorations.h
  3. 52
      windows/src/main/cpp/Registry.cpp
  4. 41
      windows/src/main/cpp/Registry.h
  5. 3
      windows/src/main/java/com/github/weisj/darklaf/platform/windows/JNIDecorationsWindows.java

128
windows/src/main/cpp/Decorations.cpp

@ -37,7 +37,7 @@ static bool is_windows_11 = false;
std::map<HWND, WindowWrapper*> wrapper_map = std::map<HWND, WindowWrapper*>(); std::map<HWND, WindowWrapper*> wrapper_map = std::map<HWND, WindowWrapper*>();
static bool Maximized(HWND hwnd) { [[nodiscard]] static bool Maximized(HWND hwnd) {
WINDOWPLACEMENT placement; WINDOWPLACEMENT placement;
if (!GetWindowPlacement(hwnd, &placement)) return false; if (!GetWindowPlacement(hwnd, &placement)) return false;
return placement.showCmd == SW_MAXIMIZE; return placement.showCmd == SW_MAXIMIZE;
@ -52,11 +52,11 @@ static bool IsLeftMousePressed(WindowWrapper *wrapper) {
} }
} }
static inline int GetFrameSize() { [[nodiscard]] static inline int GetFrameSize() {
return GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); return GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
} }
static LRESULT HandleHitTest(WindowWrapper *wrapper, int x, int y) { [[nodiscard]] static LRESULT HandleHitTest(WindowWrapper *wrapper, int x, int y) {
if (wrapper->popup_menu) return HTCLIENT; if (wrapper->popup_menu) return HTCLIENT;
POINT ptMouse = { x, y }; POINT ptMouse = { x, y };
@ -69,6 +69,9 @@ static LRESULT HandleHitTest(WindowWrapper *wrapper, int x, int y) {
USHORT uRow = 1; USHORT uRow = 1;
USHORT uCol = 1; USHORT uCol = 1;
bool x_in_button_area = ptMouse.x > rcWindow.right - wrapper->right;
bool y_in_title_area = ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + wrapper->title_height;
if (!Maximized(wrapper->window)) { if (!Maximized(wrapper->window)) {
/* /*
* The horizontal frame should be the same size as the vertical frame, * The horizontal frame should be the same size as the vertical frame,
@ -80,9 +83,8 @@ static LRESULT HandleHitTest(WindowWrapper *wrapper, int x, int y) {
// Make the top resize area smaller for the window buttons area. // Make the top resize area smaller for the window buttons area.
bool top = bool top =
ptMouse.x <= rcWindow.right - wrapper->right ? !x_in_button_area ? ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + frame_size :
ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + frame_size : ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 1;
ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + 1;
bool bottom = !top && (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - frame_size); bool bottom = !top && (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - frame_size);
@ -127,6 +129,15 @@ static LRESULT HandleHitTest(WindowWrapper *wrapper, int x, int y) {
&& ptMouse.x <= rcWindow.right - wrapper->right) { && ptMouse.x <= rcWindow.right - wrapper->right) {
return HTCAPTION; return HTCAPTION;
} }
if (x_in_button_area && y_in_title_area) {
if (ptMouse.x < rcWindow.right - 2 * wrapper->button_width) {
return HTCLIENT;
} else if (ptMouse.x < rcWindow.right - wrapper->button_width) {
return HTMAXBUTTON;
} else {
return HTCLIENT;
}
}
return HTCLIENT; return HTCLIENT;
} else { } else {
return hit; return hit;
@ -171,7 +182,7 @@ static void UpdateRegion(WindowWrapper *wrapper) {
else SetWindowRgn(wrapper->window, CreateRectRgnIndirect(&wrapper->rgn), TRUE); else SetWindowRgn(wrapper->window, CreateRectRgnIndirect(&wrapper->rgn), TRUE);
} }
static bool AutoHideTaskbar(UINT edge, RECT mon) { [[nodiscard]] static bool AutoHideTaskbar(UINT edge, RECT mon) {
APPBARDATA data; APPBARDATA data;
data.cbSize = sizeof(APPBARDATA); data.cbSize = sizeof(APPBARDATA);
data.uEdge = edge; data.uEdge = edge;
@ -179,6 +190,30 @@ static bool AutoHideTaskbar(UINT edge, RECT mon) {
return SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &data); return SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &data);
} }
[[nodiscard]] static inline bool IsWindowSnappedTop(RECT rcWork, RECT rcWindow) {
return rcWindow.left == rcWork.left && rcWindow.right == rcWork.right && rcWindow.top == rcWork.top;
}
[[nodiscard]] static inline bool IsWindowSnappedBottom(RECT rcWork, RECT rcWindow) {
return rcWindow.left == rcWork.left && rcWindow.right == rcWork.right && rcWindow.bottom == rcWork.bottom;
}
[[nodiscard]] static inline bool IsWindowSnappedLeft(RECT rcWork, RECT rcWindow) {
return rcWindow.left == rcWork.left && rcWindow.bottom == rcWork.bottom && rcWindow.top == rcWork.top;
}
[[nodiscard]] static inline bool IsWindowSnappedRight(RECT rcWork, RECT rcWindow) {
return rcWindow.right == rcWork.right && rcWindow.bottom == rcWork.bottom && rcWindow.top == rcWork.top;
}
[[nodiscard]] static inline MONITORINFO GetMonitorInfo(WindowWrapper &wrapper) {
HMONITOR mon = MonitorFromWindow(wrapper.window, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO mi;
mi.cbSize = sizeof(mi);
GetMonitorInfoW(mon, &mi);
return mi;
}
/** /**
* Adjust the maximized frame size to respect auto hiding taskbars. * Adjust the maximized frame size to respect auto hiding taskbars.
*/ */
@ -212,10 +247,7 @@ static void HandleNCCalcSize(WindowWrapper *wrapper, WPARAM wparam, LPARAM lpara
(*params.rect).right = client.right; (*params.rect).right = client.right;
(*params.rect).bottom = client.bottom; (*params.rect).bottom = client.bottom;
HMONITOR mon = MonitorFromWindow(wrapper->window, MONITOR_DEFAULTTOPRIMARY); MONITORINFO mi = GetMonitorInfo(*wrapper);
MONITORINFO mi;
mi.cbSize = sizeof(mi);
GetMonitorInfoW(mon, &mi);
/* /*
* If the client rectangle is the same as the monitor's rectangle, * If the client rectangle is the same as the monitor's rectangle,
@ -240,9 +272,22 @@ static void HandleNCCalcSize(WindowWrapper *wrapper, WPARAM wparam, LPARAM lpara
*/ */
if (wrapper->resizable) { if (wrapper->resizable) {
int frame_size = GetFrameSize(); int frame_size = GetFrameSize();
nonclient.left += frame_size; if (is_windows_11) {
nonclient.right -= frame_size; MONITORINFO mi = GetMonitorInfo(*wrapper);
nonclient.bottom -= frame_size;
bool top_equals = mi.rcMonitor.top == nonclient.top;
bool bottom_equals = mi.rcMonitor.bottom == nonclient.bottom;
bool left_equals = mi.rcMonitor.left == nonclient.left;
bool right_equals = mi.rcMonitor.right == nonclient.right;
if (!(left_equals && (top_equals || bottom_equals))) nonclient.left += frame_size;
if (!(right_equals && (top_equals || bottom_equals))) nonclient.right -= frame_size;
if (!(bottom_equals && (left_equals || right_equals))) nonclient.bottom -= frame_size;
} else {
nonclient.left += frame_size;
nonclient.right -= frame_size;
nonclient.bottom -= frame_size;
}
} }
*params.rect = nonclient; *params.rect = nonclient;
} }
@ -281,17 +326,32 @@ static void ExtendClientFrame(HWND handle) {
*/ */
static void SetupWindowStyle(HWND handle) { static void SetupWindowStyle(HWND handle) {
auto style = GetWindowLongPtr(handle, GWL_STYLE); auto style = GetWindowLongPtr(handle, GWL_STYLE);
style |= WS_THICKFRAME; style |= WS_THICKFRAME | WS_MAXIMIZEBOX;
SetWindowLongPtr(handle, GWL_STYLE, style); SetWindowLongPtr(handle, GWL_STYLE, style);
} }
enum M_DWMWINDOWATTRIBUTE {
DWMWA_WINDOW_CORNER_PREFERENCE = 33
};
enum DWM_WINDOW_CORNER_PREFERENCE {
DWMWCP_DEFAULT = 0, DWMWCP_DONOTROUND = 1, DWMWCP_ROUND = 2, DWMWCP_ROUNDSMALL = 3
};
static bool InstallDecorations(HWND handle, bool is_popup) { static bool InstallDecorations(HWND handle, bool is_popup) {
// Prevent multiple installations overriding the real window procedure. // Prevent multiple installations overriding the real window procedure.
auto it = wrapper_map.find(handle); auto it = wrapper_map.find(handle);
if (it != wrapper_map.end()) return false; if (it != wrapper_map.end()) return false;
SetupWindowStyle(handle); SetupWindowStyle(handle);
ExtendClientFrame(handle); if (is_popup || !is_windows_11) {
ExtendClientFrame(handle);
if (is_popup) {
auto attribute = M_DWMWINDOWATTRIBUTE::DWMWA_WINDOW_CORNER_PREFERENCE;
auto preference = DWM_WINDOW_CORNER_PREFERENCE::DWMWCP_ROUNDSMALL;
DwmSetWindowAttribute(handle, attribute, &preference, sizeof(preference));
}
}
WNDPROC proc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(handle, GWLP_WNDPROC)); WNDPROC proc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(handle, GWLP_WNDPROC));
@ -391,7 +451,38 @@ LRESULT CALLBACK WindowWrapper::WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_
default: default:
break; break;
} }
return CallWindowProc(wrapper->prev_proc, hwnd, uMsg, wParam, lParam); if (is_windows_11) {
return WindowProc_Windows11(wrapper, uMsg, wParam, lParam);
} else {
return CallWindowProc(wrapper->prev_proc, hwnd, uMsg, wParam, lParam);
}
}
[[maybe_unused]] LRESULT WindowWrapper::WindowProc_Windows11(WindowWrapper* wrapper, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) {
switch (uMsg) {
case WM_NCLBUTTONDOWN:
if (wParam == HTMAXBUTTON) {
wParam = 0;
uMsg = WM_LBUTTONDOWN;
}
break;
case WM_NCLBUTTONUP:
if (wParam == HTMAXBUTTON) {
wParam = 0;
uMsg = WM_LBUTTONUP;
}
break;
case WM_NCMOUSEMOVE:
if (wParam == HTMAXBUTTON) {
wParam = 0;
uMsg = WM_MOUSEMOVE;
}
break;
default:
break;
}
if (uMsg >= WM_NCXBUTTONUP && uMsg <= WM_NCXBUTTONUP) return TRUE;
return CallWindowProc(wrapper->prev_proc, wrapper->window, uMsg, wParam, lParam);
} }
// @formatter:on // @formatter:on
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -404,13 +495,14 @@ Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_setResizabl
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_updateValues(JNIEnv*, jclass, jlong hwnd, jint l, jint r, jint h) { Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_updateValues(JNIEnv*, jclass, jlong hwnd, jint l, jint r, jint h, jint bw) {
HWND handle = reinterpret_cast<HWND>(hwnd); HWND handle = reinterpret_cast<HWND>(hwnd);
auto wrap = wrapper_map[handle]; auto wrap = wrapper_map[handle];
if (wrap) { if (wrap) {
wrap->left = l; wrap->left = l;
wrap->right = r; wrap->right = r;
wrap->title_height = h; wrap->title_height = h;
wrap->button_width = bw;
} }
} }

3
windows/src/main/cpp/Decorations.h

@ -50,6 +50,7 @@ class WindowWrapper {
HWND window; HWND window;
int width; int width;
int height; int height;
int button_width;
// The window region. // The window region.
RECT rgn; RECT rgn;
@ -59,5 +60,5 @@ class WindowWrapper {
int right = 0; int right = 0;
int title_height = 0; int title_height = 0;
static LRESULT CALLBACK WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam); static LRESULT CALLBACK WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);static LRESULT WindowProc_Windows11(WindowWrapper*, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
}; };

52
windows/src/main/cpp/Registry.cpp

@ -0,0 +1,52 @@
/*
* MIT License
*
* Copyright (c) 2021 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "Registry.h"
DWORD RegGetDword(HKEY hKey, const LPCSTR subKey, const LPCSTR value) {
DWORD data {};
DWORD dataSize = sizeof(data);
DWORD flags = RRF_RT_REG_DWORD;
ModifyFlags(flags);
LONG retCode = ::RegGetValueA(hKey, subKey, value, flags, nullptr, &data, &dataSize);
if (retCode != ERROR_SUCCESS) throw retCode;
return data;
}
std::string RegGetString(HKEY hKey, const LPCSTR subKey, const LPCSTR value) {
DWORD dataSize {};
DWORD flags = RRF_RT_REG_SZ;
ModifyFlags(flags);
LONG retCode = ::RegGetValueA(hKey, subKey, value, flags, nullptr, nullptr, &dataSize);
if (retCode != ERROR_SUCCESS) throw retCode;
std::string data;
DWORD stringLengthInChars = dataSize / sizeof(char);
data.resize(stringLengthInChars);
retCode = ::RegGetValueA(hKey, subKey, value, flags, nullptr, &data[0], &dataSize);
if (retCode != ERROR_SUCCESS) throw retCode;
return data;
}

41
windows/src/main/cpp/Registry.h

@ -0,0 +1,41 @@
/*
* MIT License
*
* Copyright (c) 2021 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#pragma once
#include <string>
#include <windows.h>
#include <winreg.h>
inline void ModifyFlags(DWORD &flags) {
#ifdef _WIN64
flags |= RRF_SUBKEY_WOW6464KEY;
#else
flags |= RRF_SUBKEY_WOW6432KEY;
#endif
}
DWORD RegGetDword(HKEY hKey, const LPCSTR subKey, const LPCSTR value);
std::string RegGetString(HKEY hKey, const LPCSTR subKey, const LPCSTR value);

3
windows/src/main/java/com/github/weisj/darklaf/platform/windows/JNIDecorationsWindows.java

@ -32,7 +32,8 @@ public final class JNIDecorationsWindows {
public static native long getWindowHWND(final Window window, final String javaLibPath); public static native long getWindowHWND(final Window window, final String javaLibPath);
public static native void updateValues(final long hwnd, final int left, final int right, final int height); public static native void updateValues(final long hwnd, final int left, final int right, final int height,
final int buttonWidth);
public static native void setResizable(final long hwnd, final boolean resizable); public static native void setResizable(final long hwnd, final boolean resizable);

Loading…
Cancel
Save