From 6b583ae9b2e91c78e1ccd60c15fe295f898b0a17 Mon Sep 17 00:00:00 2001 From: Quest <1403153+Quezion@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:31:48 -0800 Subject: [PATCH 1/2] Add Windows implementation of setTitlebarVisible JWM demo works with Ctrl+T "Toggle Resize" Update README.md with JWM_DEBUG env instructions & $JAVA_HOME/bin --- README.md | 31 ++++++++++++++++++++++ docs/windows/build.md | 3 +++ examples/dashboard/java/PanelScreens.java | 10 +++++++ windows/cc/WindowWin32.cc | 32 ++++++++++++++++++++++- windows/cc/WindowWin32.hh | 1 + windows/java/WindowWin32.java | 6 +++-- 6 files changed, 80 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f69a650..30735f32 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,8 @@ Alpha. Expect API breakages. | setMinimumSize | ❌ | ❌ | ❌ | | setMaximumSize | ❌ | ❌ | ❌ | | setResizable | ❌ | ❌ | ❌ | +| bringToFront | ✅ | ❌ | ❌ | +| isFront | ✅ | ✅ | ❌ | ### Events @@ -226,6 +228,35 @@ Run examples without building (use version from the table above): ./script/run.py --jwm-version ``` +### Local JAR + +Generate & install a local .jar file: + +``` +./script/install.py +``` + +This outputs `target/jwm-0.0.0-SNAPSHOT.jar` for use in testing (e.g. `io.github.humbleui/jwm {:local/root "..."}` if using deps.edn) + +### MacOS + +Before running the build, ensure you've installed: +* XCode Developer Tools (`xcode-select --install`) +* Ninja (`brew install ninja`) +* Python 3 (`brew install python`) + +### Debugging + +Set `JWM_VERBOSE` in process env to see extra log output when running locally. + +``` bash +# Mac / Linux +export JWM_VERBOSE=true + +# Windows +set JWM_VERBOSE=true +``` + # Contributing PRs & issue reports are welcome! diff --git a/docs/windows/build.md b/docs/windows/build.md index f611c87d..e29068db 100644 --- a/docs/windows/build.md +++ b/docs/windows/build.md @@ -25,6 +25,9 @@ or from chocolatey ```sh choco install -y python ``` + +You'll need to ensure your Java installation's `$JAVA_HOME/bin` is on your system PATH. This exposes necessary interop codefiles like `jni.h` to the compiler. Your JAVA bin path will look similar to "`C:\Program Files\Java\jdk-17.0.2\bin`" + ## Install Ninja Download executable from https://github.com/ninja-build/ninja/releases and export path. diff --git a/examples/dashboard/java/PanelScreens.java b/examples/dashboard/java/PanelScreens.java index 378f8ef3..aff2a92c 100644 --- a/examples/dashboard/java/PanelScreens.java +++ b/examples/dashboard/java/PanelScreens.java @@ -20,6 +20,8 @@ public PanelScreens(Window window) { titleStyles = new Options("Default", "Hidden", "Transparent", "Unified", "Unified Compact", "Unified Transparent", "Unified Compact Transparent"); } else if (Platform.X11 == Platform.CURRENT) { titleStyles = new Options("Default", "Hidden"); + } else if (Platform.WINDOWS == Platform.CURRENT) { + titleStyles = new Options("Default", "Hidden"); } } @@ -66,6 +68,14 @@ public void setTitleStyle(String style) { case "Hidden" -> w.setTitlebarVisible(false); } + } else if (Platform.WINDOWS == Platform.CURRENT) { + WindowWin32 w = (WindowWin32) window; + switch (style) { + case "Default" -> + w.setTitlebarVisible(true); + case "Hidden" -> + w.setTitlebarVisible(false); + } } } diff --git a/windows/cc/WindowWin32.cc b/windows/cc/WindowWin32.cc index 36937e7d..7bb1f70f 100644 --- a/windows/cc/WindowWin32.cc +++ b/windows/cc/WindowWin32.cc @@ -70,6 +70,31 @@ void jwm::WindowWin32::setTitle(const std::wstring& title) { SetWindowTextW(_hWnd, title.c_str()); } +void jwm::WindowWin32::setTitlebarVisible(bool isVisible) { + JWM_VERBOSE("Set titlebar visible=" << isVisible << " for window 0x" << this); + if (isVisible == true) { + LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE); + lStyle |= (WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + SetWindowLongPtr(_hWnd, GWL_STYLE, lStyle); + + IRect rect = getWindowRect(); + int windowWidth = rect.getWidth(); + int windowHeight = rect.getHeight(); + + setContentSize(windowWidth, windowHeight); + } else { + IRect rect = getContentRect(); + int contentWidth = rect.getWidth(); + int contentHeight = rect.getHeight(); + + LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE); + lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + SetWindowLongPtr(_hWnd, GWL_STYLE, lStyle); + + setWindowSize(contentWidth, contentHeight); + } +} + void jwm::WindowWin32::setIcon(const std::wstring& iconPath) { JWM_VERBOSE("Set window icon '" << iconPath << "'"); // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size. @@ -656,7 +681,6 @@ LRESULT jwm::WindowWin32::processEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) dispatch(classes::EventWindowFocusOut::kInstance); break; - case WM_CLOSE: JWM_VERBOSE("Event close"); dispatch(classes::EventWindowCloseRequest::kInstance); @@ -1013,6 +1037,12 @@ extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWin32__1nSet env->ReleaseStringChars(title, titleStr); } +extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWin32__1nSetTitlebarVisible + (JNIEnv* env, jobject obj, jboolean isVisible) { + jwm::WindowWin32* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); + instance->setTitlebarVisible(isVisible); +} + extern "C" JNIEXPORT void JNICALL Java_io_github_humbleui_jwm_WindowWin32__1nSetIcon (JNIEnv* env, jobject obj, jstring iconPath) { jwm::WindowWin32* instance = reinterpret_cast(jwm::classes::Native::fromJava(env, obj)); diff --git a/windows/cc/WindowWin32.hh b/windows/cc/WindowWin32.hh index f5ca97e2..4b3a91b3 100644 --- a/windows/cc/WindowWin32.hh +++ b/windows/cc/WindowWin32.hh @@ -51,6 +51,7 @@ namespace jwm { void unmarkText(); void setImeEnabled(bool enabled); void setTitle(const std::wstring& title); + void setTitlebarVisible(bool isVisible); void setIcon(const std::wstring& iconPath); void setOpacity(float opacity); float getOpacity(); diff --git a/windows/java/WindowWin32.java b/windows/java/WindowWin32.java index 1379268e..5548c638 100644 --- a/windows/java/WindowWin32.java +++ b/windows/java/WindowWin32.java @@ -68,7 +68,6 @@ public Window setTitle(String title) { return this; } - @Override public Window setIcon(File icon){ assert _onUIThread() : "Should be run on UI thread"; @@ -78,7 +77,9 @@ public Window setIcon(File icon){ @Override public Window setTitlebarVisible(boolean value) { - throw new UnsupportedOperationException("impl me!"); + assert _onUIThread(); + _nSetTitlebarVisible(value); + return this; } @Override @@ -225,6 +226,7 @@ public Window winSetParent(long hwnd) { @ApiStatus.Internal public native void _nSetWindowSize(int width, int height); @ApiStatus.Internal public native void _nSetContentSize(int width, int height); @ApiStatus.Internal public native void _nSetTitle(String title); + @ApiStatus.Internal public native void _nSetTitlebarVisible(boolean isVisible); @ApiStatus.Internal public native void _nSetIcon(String iconPath); @ApiStatus.Internal public native void _nSetVisible(boolean isVisible); @ApiStatus.Internal public native void _nSetOpacity(float opacity); From 2e8d4674713c487d179d9bcf8caa7db027bfda0a Mon Sep 17 00:00:00 2001 From: Quest <1403153+Quezion@users.noreply.github.com> Date: Mon, 26 Feb 2024 12:31:43 -0800 Subject: [PATCH 2/2] Use WindowWin32 dwmapi.h to account for window dropshadow See getWindowRect, setTitlebarVisible, setContentSize, setWindowSize --- windows/cc/ThemeWin32.cc | 2 +- windows/cc/WindowWin32.cc | 72 ++++++++++++++++++++++++++++++--------- windows/cc/WindowWin32.hh | 3 ++ 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/windows/cc/ThemeWin32.cc b/windows/cc/ThemeWin32.cc index d1802853..d426fa36 100644 --- a/windows/cc/ThemeWin32.cc +++ b/windows/cc/ThemeWin32.cc @@ -18,7 +18,7 @@ bool _isHighContrast() { return false; } bool result = (HCF_HIGHCONTRASTON & highContrast.dwFlags) == 1; - JWM_VERBOSE("is HighContrast? '" << result << "'"); + // JWM_VERBOSE("is HighContrast? '" << result << "'"); return result; } diff --git a/windows/cc/WindowWin32.cc b/windows/cc/WindowWin32.cc index 7bb1f70f..754fedde 100644 --- a/windows/cc/WindowWin32.cc +++ b/windows/cc/WindowWin32.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#pragma comment(lib,"dwmapi.lib") jwm::WindowWin32::WindowWin32(JNIEnv *env, class WindowManagerWin32 &windowManagerWin32) : Window(env), _windowManager(windowManagerWin32) { @@ -72,26 +74,37 @@ void jwm::WindowWin32::setTitle(const std::wstring& title) { void jwm::WindowWin32::setTitlebarVisible(bool isVisible) { JWM_VERBOSE("Set titlebar visible=" << isVisible << " for window 0x" << this); + LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE); + if (isVisible == true) { - LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE); + IRect windowRect = getWindowRect(); + lStyle |= (WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); SetWindowLongPtr(_hWnd, GWL_STYLE, lStyle); + setWindowSize(windowRect.getWidth(), windowRect.getHeight()); + JWM_VERBOSE("window shadow width '" << _windowShadowWidth << "'"); + + // Reposition window to fix Windows SWP_NOMOVE still causing move in setWindowSize + setWindowPosition(windowRect.fLeft - (int)(_windowShadowWidth * 0.5), + windowRect.fTop - (int)(_windowShadowHeight * 0.5)); + _windowShadowHeight = 0; + _windowShadowWidth = 0; + } else { IRect rect = getWindowRect(); int windowWidth = rect.getWidth(); int windowHeight = rect.getHeight(); - setContentSize(windowWidth, windowHeight); - } else { - IRect rect = getContentRect(); - int contentWidth = rect.getWidth(); - int contentHeight = rect.getHeight(); + RECT shadowRect; + GetWindowRect(_hWnd, &shadowRect); - LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE); lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); SetWindowLongPtr(_hWnd, GWL_STYLE, lStyle); - setWindowSize(contentWidth, contentHeight); + _windowShadowHeight = (shadowRect.bottom - shadowRect.top) - windowHeight; + _windowShadowWidth = (shadowRect.right - shadowRect.left) - windowWidth; + setContentSize(windowWidth, windowHeight); + setWindowPosition(rect.fLeft, rect.fTop); } } @@ -245,17 +258,28 @@ void jwm::WindowWin32::requestFrame() { } } -jwm::IRect jwm::WindowWin32::getWindowRect() const { +// Internal function to return simple C rect +RECT jwm::WindowWin32::_getWindowRectSimple() const { RECT rect; - GetWindowRect(_hWnd, &rect); + // Get window rect without dropshadow + HRESULT result = DwmGetWindowAttribute(_hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect)); + // Guard against odd situations where DwmGetWindowAttribute fails + if (result != S_OK || + ((rect.right - rect.left) == 0)) { + GetWindowRect(_hWnd, &rect); + } + return rect; +} + +jwm::IRect jwm::WindowWin32::getWindowRect() const { + RECT rect = _getWindowRectSimple(); return IRect{rect.left, rect.top, rect.right, rect.bottom}; } jwm::IRect jwm::WindowWin32::getContentRect() const { RECT clientRect; GetClientRect(_hWnd, &clientRect); - RECT windowRect; - GetWindowRect(_hWnd, &windowRect); + RECT windowRect = _getWindowRectSimple(); // Convert client area rect to screen space and POINT corners[] = {POINT{clientRect.left, clientRect.top}, POINT{clientRect.right, clientRect.bottom}}; @@ -281,10 +305,22 @@ void jwm::WindowWin32::setWindowPosition(int left, int top) { void jwm::WindowWin32::setWindowSize(int width, int height) { JWM_VERBOSE("Set window size w=" << width << " h=" << height); + // Calculate current shadow (reusing last stored values if set) + RECT rect = _getWindowRectSimple(); + JWM_VERBOSE("rect left=" << rect.left << " right=" << rect.right); + + RECT rectShadow; + GetWindowRect(_hWnd, &rectShadow); + + int shadowWidth = (rectShadow.right - rectShadow.left) - (rect.right - rect.left); + shadowWidth = (_windowShadowWidth > shadowWidth ) ? _windowShadowWidth : shadowWidth; + int shadowHeight = (rectShadow.bottom - rectShadow.top) - (rect.bottom - rect.top); + shadowHeight = (_windowShadowHeight > shadowHeight ) ? _windowShadowHeight : shadowHeight; + SetWindowPos(_hWnd, HWND_TOP, 0, 0, - width, - height, + width + shadowWidth, + height + shadowHeight, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER); } @@ -659,8 +695,7 @@ LRESULT jwm::WindowWin32::processEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) ClientToScreen(getHWnd(), &cursorPos); // Area of the window (interpreted as document area (where we can place ime window)) - RECT documentArea; - GetWindowRect(getHWnd(), &documentArea); + RECT documentArea = _getWindowRectSimple(); // Fill lParam structure // its content will be read after this proc function returns @@ -715,7 +750,10 @@ void jwm::WindowWin32::notifyEvent(Event event) { } DWORD jwm::WindowWin32::_getWindowStyle() const { - return WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE); + DWORD windowStyle; + LongPtrToDWord(lStyle, &windowStyle); + return windowStyle; } DWORD jwm::WindowWin32::_getWindowExStyle() const { diff --git a/windows/cc/WindowWin32.hh b/windows/cc/WindowWin32.hh index 4b3a91b3..5a0ff987 100644 --- a/windows/cc/WindowWin32.hh +++ b/windows/cc/WindowWin32.hh @@ -105,6 +105,7 @@ namespace jwm { void _imeGetCompositionStringConvertedRange(HIMC hImc, int &selFrom, int &selTo) const; bool _imeGetRectForMarkedRange(IRect& rect) const; std::wstring _imeGetCompositionString(HIMC hImc, DWORD compType) const; + RECT _getWindowRectSimple() const; private: friend class WindowManagerWin32; @@ -125,5 +126,7 @@ namespace jwm { bool _maximized = false; int _nextCallbackID = 0; wchar_t _highSurrogate = 0; + int _windowShadowWidth = 0; + int _windowShadowHeight = 0; }; }