Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Windows implementation of setTitlebarVisible (#75) #286

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ Alpha. Expect API breakages.
| setMinimumSize | ❌ | ❌ | ❌ |
| setMaximumSize | ❌ | ❌ | ❌ |
| setResizable | ❌ | ❌ | ❌ |
| bringToFront | ✅ | ❌ | ❌ |
| isFront | ✅ | ✅ | ❌ |

### Events

Expand Down Expand Up @@ -226,6 +228,35 @@ Run examples without building (use version from the table above):
./script/run.py --jwm-version <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!
Expand Down
3 changes: 3 additions & 0 deletions docs/windows/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions examples/dashboard/java/PanelScreens.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand Down Expand Up @@ -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);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion windows/cc/ThemeWin32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
88 changes: 78 additions & 10 deletions windows/cc/WindowWin32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <MouseButton.hh>
#include <Log.hh>
#include <memory>
#include <dwmapi.h>
#pragma comment(lib,"dwmapi.lib")

jwm::WindowWin32::WindowWin32(JNIEnv *env, class WindowManagerWin32 &windowManagerWin32)
: Window(env), _windowManager(windowManagerWin32) {
Expand Down Expand Up @@ -70,6 +72,42 @@ 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);
LONG_PTR lStyle = GetWindowLongPtr(_hWnd, GWL_STYLE);

if (isVisible == true) {
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();

RECT shadowRect;
GetWindowRect(_hWnd, &shadowRect);

lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
SetWindowLongPtr(_hWnd, GWL_STYLE, lStyle);

_windowShadowHeight = (shadowRect.bottom - shadowRect.top) - windowHeight;
_windowShadowWidth = (shadowRect.right - shadowRect.left) - windowWidth;
setContentSize(windowWidth, windowHeight);
setWindowPosition(rect.fLeft, rect.fTop);
}
}

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.
Expand Down Expand Up @@ -220,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}};
Expand All @@ -256,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);
}

Expand Down Expand Up @@ -634,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
Expand All @@ -656,7 +716,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);
Expand Down Expand Up @@ -691,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 {
Expand Down Expand Up @@ -1013,6 +1075,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::WindowWin32*>(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::WindowWin32*>(jwm::classes::Native::fromJava(env, obj));
Expand Down
4 changes: 4 additions & 0 deletions windows/cc/WindowWin32.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -104,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;
Expand All @@ -124,5 +126,7 @@ namespace jwm {
bool _maximized = false;
int _nextCallbackID = 0;
wchar_t _highSurrogate = 0;
int _windowShadowWidth = 0;
int _windowShadowHeight = 0;
};
}
6 changes: 4 additions & 2 deletions windows/java/WindowWin32.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
Loading