From 279892d952e2a9b1a9b12d7c4b5ad2221b7ad519 Mon Sep 17 00:00:00 2001 From: weisj Date: Fri, 3 Apr 2020 15:08:37 +0200 Subject: [PATCH] Improved decorations setup on windows. --- .../com/github/weisj/darklaf/DarkLaf.java | 5 +- .../weisj/darklaf/ui/DarkPopupFactory.java | 41 +++++++-- core/src/test/java/PreferenceChangeDemo.java | 64 ++++++++++++++ core/src/test/java/ui/ComponentDemo.java | 6 +- windows/src/main/cpp/Decorations.cpp | 84 ++++++++++--------- windows/src/main/cpp/Decorations.h | 2 +- .../windows/JNIDecorationsWindows.java | 4 +- .../windows/WindowsDecorationsProvider.java | 13 +-- .../platform/windows/ui/WindowsTitlePane.java | 18 ---- 9 files changed, 160 insertions(+), 77 deletions(-) create mode 100644 core/src/test/java/PreferenceChangeDemo.java diff --git a/core/src/main/java/com/github/weisj/darklaf/DarkLaf.java b/core/src/main/java/com/github/weisj/darklaf/DarkLaf.java index c5900ddb..14c6d267 100644 --- a/core/src/main/java/com/github/weisj/darklaf/DarkLaf.java +++ b/core/src/main/java/com/github/weisj/darklaf/DarkLaf.java @@ -113,8 +113,9 @@ public class DarkLaf extends BasicLookAndFeel { private void setupDecorations() { DecorationsHandler.getSharedInstance().initialize(); - JFrame.setDefaultLookAndFeelDecorated(true); - JDialog.setDefaultLookAndFeelDecorated(true); + boolean decorated = getSupportsWindowDecorations(); + JFrame.setDefaultLookAndFeelDecorated(decorated); + JDialog.setDefaultLookAndFeelDecorated(decorated); } @Override diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java b/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java index 46f71a5a..0d6762c0 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java @@ -26,6 +26,7 @@ package com.github.weisj.darklaf.ui; import com.github.weisj.darklaf.platform.DecorationsHandler; import com.github.weisj.darklaf.ui.popupmenu.DarkPopupMenuUI; import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI; +import com.github.weisj.darklaf.util.DarkUIUtil; import javax.swing.*; import java.awt.*; @@ -37,9 +38,16 @@ public class DarkPopupFactory extends PopupFactory { public static final String KEY_FORCE_HEAVYWEIGHT = "JPopupFactory.forceHeavyweight"; public static final String KEY_START_HIDDEN = "JPopupFactory.startHidden"; + private HeavyWeightParent heavyWeightParent; + @Override public Popup getPopup(final Component owner, final Component contents, final int x, final int y) throws IllegalArgumentException { + if (heavyWeightParent == null) { + JComponent box = Box.createHorizontalBox(); + super.getPopup(null, box, 0, 0); + heavyWeightParent = new HeavyWeightParent(DarkUIUtil.getWindow(box)); + } Popup popup = super.getPopup(owner, contents, x, y); boolean isMediumWeight = popup.getClass().getSimpleName().endsWith("MediumWeightPopup"); boolean isLightWeight = popup.getClass().getSimpleName().endsWith("LightWeightPopup"); @@ -51,7 +59,7 @@ public class DarkPopupFactory extends PopupFactory { && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_START_HIDDEN)); if (forceHeavy && (isMediumWeight || isLightWeight)) { // null owner forces a heavyweight popup. - popup = super.getPopup(null, contents, x, y); + popup = super.getPopup(heavyWeightParent, contents, x, y); } if (isMediumWeight) { JRootPane rootPane = SwingUtilities.getRootPane(contents); @@ -64,13 +72,20 @@ public class DarkPopupFactory extends PopupFactory { // That is why we set window background explicitly. Window window = SwingUtilities.getWindowAncestor(contents); if (window != null) { - window.setBackground(UIManager.getColor("PopupMenu.translucentBackground")); boolean install = true; + window.setBackground(UIManager.getColor("PopupMenu.translucentBackground")); if (window instanceof RootPaneContainer) { JRootPane rootPane = ((RootPaneContainer) window).getRootPane(); rootPane.putClientProperty(DarkRootPaneUI.KEY_NO_DECORATIONS, true); + window.setBackground(rootPane.getBackground()); install = !Boolean.TRUE.equals(rootPane.getClientProperty(KEY_NO_DECORATION)); } + + if (isFocusable && !window.getFocusableWindowState()) { + window.dispose(); + window.setFocusableWindowState(true); + } + if (install) { DecorationsHandler.getSharedInstance().installPopupWindow(window); } else { @@ -80,11 +95,25 @@ public class DarkPopupFactory extends PopupFactory { ((JComponent) contents).putClientProperty(DarkPopupMenuUI.KEY_MAKE_VISIBLE, true); window.setOpacity(0.0f); } - if (isFocusable && !window.getFocusableWindowState()) { - window.dispose(); - window.setFocusableWindowState(true); - } } return popup; } + + private static class HeavyWeightParent extends JComponent { + + private final Window window; + + private HeavyWeightParent(final Window window) { + this.window = window; + } + + @Override + public Container getParent() { + return window; + } + + public Window getWindow() { + return window; + } + } } diff --git a/core/src/test/java/PreferenceChangeDemo.java b/core/src/test/java/PreferenceChangeDemo.java new file mode 100644 index 00000000..ebe37ef6 --- /dev/null +++ b/core/src/test/java/PreferenceChangeDemo.java @@ -0,0 +1,64 @@ +import com.github.weisj.darklaf.LafManager; +import com.github.weisj.darklaf.theme.Theme; +import ui.ComponentDemo; + +import javax.swing.*; +import java.awt.*; + +/* + * MIT License + * + * Copyright (c) 2020 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. + */ +public class PreferenceChangeDemo implements ComponentDemo { + + public static void main(final String[] args) { + ComponentDemo.showDemo(new PreferenceChangeDemo()); + } + + @Override + public JComponent createComponent() { + LafManager.addThemePreferenceChangeListener(e -> LafManager.installTheme(e.getPreferredThemeStyle())); + return new JPanel(new GridBagLayout()) {{ + add(new JToggleButton("Start") {{ + addActionListener(e -> { + setText(isSelected() ? "Stop" : "Start"); + LafManager.enabledPreferenceChangeReporting(isSelected()); + }); + }}); + }}; + } + + @Override + public String getTitle() { + return "Preference Change Demo"; + } + + @Override + public Theme createTheme() { + return LafManager.themeForPreferredStyle(LafManager.getPreferredThemeStyle()); + } + + @Override + public JMenuBar createMenuBar() { + return null; + } +} diff --git a/core/src/test/java/ui/ComponentDemo.java b/core/src/test/java/ui/ComponentDemo.java index 02935a99..bb714d72 100644 --- a/core/src/test/java/ui/ComponentDemo.java +++ b/core/src/test/java/ui/ComponentDemo.java @@ -48,7 +48,7 @@ public interface ComponentDemo { static void showDemo(final ComponentDemo demo, final Dimension dimension) { SwingUtilities.invokeLater(() -> { - LafManager.install(getTheme()); + LafManager.install(demo.createTheme()); JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setTitle(demo.getTitle()); @@ -100,5 +100,9 @@ public interface ComponentDemo { return menuBar; } + default Theme createTheme() { + return getTheme(); + } + String getTitle(); } diff --git a/windows/src/main/cpp/Decorations.cpp b/windows/src/main/cpp/Decorations.cpp index a603c79e..7ae39a3c 100644 --- a/windows/src/main/cpp/Decorations.cpp +++ b/windows/src/main/cpp/Decorations.cpp @@ -90,43 +90,50 @@ LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam, WindowWrapper *wrapp } } +bool Maximized(HWND hwnd) +{ + WINDOWPLACEMENT placement; + if (!GetWindowPlacement(hwnd, &placement)) return false; + return placement.showCmd == SW_MAXIMIZE; +} + +void AdjustMaximizedClientArea(HWND window, RECT& rect) +{ + if (!Maximized(window)) return; + + auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (!monitor) return; + + MONITORINFO monitor_info{}; + monitor_info.cbSize = sizeof(monitor_info); + if (!GetMonitorInfoW(monitor, &monitor_info)) return; + + rect = monitor_info.rcWork; +} + LRESULT CALLBACK WindowWrapper::WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) { HWND handle = reinterpret_cast(hwnd); auto wrapper = wrapper_map[handle]; if (uMsg == WM_NCCALCSIZE) { - if (wParam == TRUE) - { - SetWindowLong(hwnd, 0, 0); + if (wParam == TRUE) { + NCCALCSIZE_PARAMS& params = *reinterpret_cast(lParam); + AdjustMaximizedClientArea(handle, params.rgrc[0]); return TRUE; } - return FALSE; } else if (uMsg == WM_NCHITTEST) { return HitTestNCA(hwnd, wParam, lParam, wrapper); } - else if (uMsg == WM_GETMINMAXINFO) + else if ((uMsg == WM_PAINT || uMsg == WM_ERASEBKGND) && wrapper->bgBrush) { - HMONITOR hPrimaryMonitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); - HMONITOR hTargetMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - - MONITORINFO primaryMonitorInfo{sizeof(MONITORINFO)}; - MONITORINFO targetMonitorInfo{sizeof(MONITORINFO)}; - - GetMonitorInfo(hPrimaryMonitor, &primaryMonitorInfo); - GetMonitorInfo(hTargetMonitor, &targetMonitorInfo); - - MINMAXINFO *min_max_info = reinterpret_cast(lParam); - RECT max_rect = primaryMonitorInfo.rcWork; - RECT target_rect = targetMonitorInfo.rcWork; - int indent = 2; - min_max_info->ptMaxSize.x = target_rect.right - target_rect.left - 2 * indent; - min_max_info->ptMaxSize.y = target_rect.bottom - target_rect.top - 2 + indent; - min_max_info->ptMaxPosition.x = max_rect.left + indent; - min_max_info->ptMaxPosition.y = max_rect.top + indent; - return 0; + HDC hdc = reinterpret_cast(wParam); + RECT clientRect; + GetClientRect(hwnd, &clientRect); + FillRect(hdc, &clientRect, wrapper->bgBrush); + if (uMsg == WM_ERASEBKGND) return TRUE; } return CallWindowProc(wrapper->prev_proc, hwnd, uMsg, wParam, lParam); @@ -164,47 +171,48 @@ Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_setBackgrou auto wrap = wrapper_map[handle]; if (wrap) { - wrap->background = RGB(r, g, b); + wrap->bgBrush = CreateSolidBrush(RGB(r, g, b)); } } -void extend_client_frame(HWND handle) +void ExtendClientFrame(HWND handle) { - MARGINS margins = {0, 0, 0, 1}; + MARGINS margins = {1, 1, 1, 1}; DwmExtendFrameIntoClientArea(handle, &margins); } -void setup_window_style(HWND handle) +void SetupWindowStyle(HWND handle) { auto style = GetWindowLongPtr(handle, GWL_STYLE); - SetWindowLongPtr(handle, GWL_STYLE, style | WS_THICKFRAME); + SetWindowLongPtr(handle, GWL_STYLE, (style | WS_THICKFRAME)); } -void install_decorations(HWND handle, bool is_popup) +bool InstallDecorations(HWND handle, bool is_popup) { //Prevent multiple installations overriding the real window procedure. auto it = wrapper_map.find(handle); - if (it != wrapper_map.end()) return; + if (it != wrapper_map.end()) return false; - extend_client_frame(handle); - setup_window_style(handle); + SetupWindowStyle(handle); + ExtendClientFrame(handle); - SetWindowPos(handle, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); WNDPROC proc = reinterpret_cast(GetWindowLongPtr(handle, GWL_WNDPROC)); WindowWrapper *wrapper = new WindowWrapper(); wrapper->prev_proc = proc; wrapper->popup_menu = is_popup; wrapper_map[handle] = wrapper; - SetWindowLongPtr(handle, GWL_WNDPROC, (LONG_PTR)WindowWrapper::WindowProc); + UINT flags = SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED; + SetWindowPos(handle, NULL, 0, 0, 0, 0, flags); + return true; } -JNIEXPORT void JNICALL +JNIEXPORT jboolean JNICALL Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_installDecorations(JNIEnv *env, jclass obj, jlong hwnd) { HWND handle = reinterpret_cast(hwnd); - install_decorations(handle, false); + return (jboolean) InstallDecorations(handle, false); } JNIEXPORT void JNICALL @@ -220,11 +228,11 @@ Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_uninstallDe } } -JNIEXPORT void JNICALL +JNIEXPORT jboolean JNICALL Java_com_github_weisj_darklaf_platform_windows_JNIDecorationsWindows_installPopupMenuDecorations(JNIEnv *env, jclass obj, jlong hwnd) { HWND handle = reinterpret_cast(hwnd); - install_decorations(handle, true); + return (jboolean) InstallDecorations(handle, true); } //Window functions. diff --git a/windows/src/main/cpp/Decorations.h b/windows/src/main/cpp/Decorations.h index a253871b..35e75a15 100644 --- a/windows/src/main/cpp/Decorations.h +++ b/windows/src/main/cpp/Decorations.h @@ -31,7 +31,7 @@ public: bool resizable = true; bool popup_menu = false; WNDPROC prev_proc; - COLORREF background = RGB(255, 255, 255); + HBRUSH bgBrush; int left = 0; int right = 0; diff --git a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/JNIDecorationsWindows.java b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/JNIDecorationsWindows.java index 7d688a17..f84505ea 100644 --- a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/JNIDecorationsWindows.java +++ b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/JNIDecorationsWindows.java @@ -40,9 +40,9 @@ public class JNIDecorationsWindows { public static native void restore(final long hwnd); - public static native void installDecorations(final long hwnd); + public static native boolean installDecorations(final long hwnd); public static native void uninstallDecorations(final long hwnd); - public static native void installPopupMenuDecorations(final long hwnd); + public static native boolean installPopupMenuDecorations(final long hwnd); } diff --git a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsDecorationsProvider.java b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsDecorationsProvider.java index f66f623a..2ceb3a02 100644 --- a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsDecorationsProvider.java +++ b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsDecorationsProvider.java @@ -32,8 +32,6 @@ import com.github.weisj.darklaf.platform.windows.ui.WindowsTitlePane; import javax.swing.*; import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.util.Properties; public class WindowsDecorationsProvider implements DecorationsProvider { @@ -60,13 +58,10 @@ public class WindowsDecorationsProvider implements DecorationsProvider { } long hwnd = PointerUtil.getHWND(window); if (hwnd != 0) { - JNIDecorationsWindows.installPopupMenuDecorations(hwnd); - window.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(final WindowEvent e) { - JNIDecorationsWindows.uninstallDecorations(hwnd); - } - }); + if (JNIDecorationsWindows.installPopupMenuDecorations(hwnd)) { + Color bg = window.getBackground(); + JNIDecorationsWindows.setBackground(hwnd, bg.getRed(), bg.getGreen(), bg.getBlue()); + } } } diff --git a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/ui/WindowsTitlePane.java b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/ui/WindowsTitlePane.java index 8bd1ed76..c64d5a24 100644 --- a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/ui/WindowsTitlePane.java +++ b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/ui/WindowsTitlePane.java @@ -230,7 +230,6 @@ public class WindowsTitlePane extends CustomTitlePane { updateResizeBehaviour(); Color color = window.getBackground(); JNIDecorationsWindows.setBackground(windowHandle, color.getRed(), color.getGreen(), color.getBlue()); - forceNativeResize(); } else { uninstall(); return false; @@ -239,23 +238,6 @@ public class WindowsTitlePane extends CustomTitlePane { return true; } - private void forceNativeResize() { - Rectangle bounds = window.getBounds(); - Dimension size = bounds.getSize(); - Point p = bounds.getLocation(); - if (window.isPreferredSizeSet()) { - size = window.getPreferredSize(); - } else { - p.x += size.width / 2; - p.y += size.height / 2; - } - //Resizing triggers #reshapeNativePeer - window.setSize(size.width, size.height + 1); - window.setSize(size.width, size.height); - window.setLocation(p.x - size.width / 2, - p.y - size.height / 2); - } - private void installListeners() { if (window != null) { windowListener = createWindowListener();