From 73410084492c3fe728ba5e0a857083c2cf8741e3 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Wed, 24 Feb 2021 23:17:41 +0100 Subject: [PATCH] Native window decorations: fixed missing top border line --- .../flatlaf/ui/FlatNativeWindowBorder.java | 55 +++++++++ .../com/formdev/flatlaf/ui/FlatTitlePane.java | 18 +-- .../flatlaf/ui/JBRCustomDecorations.java | 69 ++++++----- .../FlatWindowsNativeWindowBorder.java | 109 +++++++++++++++++- 4 files changed, 212 insertions(+), 39 deletions(-) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java index 01665d3a..319f6706 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java @@ -16,6 +16,7 @@ package com.formdev.flatlaf.ui; +import java.awt.Color; import java.awt.Rectangle; import java.awt.Window; import java.beans.PropertyChangeListener; @@ -26,8 +27,10 @@ import javax.swing.JFrame; import javax.swing.JRootPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.event.ChangeListener; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatSystemProperties; +import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder; import com.formdev.flatlaf.util.SystemInfo; /** @@ -244,5 +247,57 @@ public class FlatNativeWindowBorder void setTitleBarHeight( Window window, int titleBarHeight ); void setTitleBarHitTestSpots( Window window, List hitTestSpots ); void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds ); + + boolean isColorizationColorAffectsBorders(); + Color getColorizationColor(); + int getColorizationColorBalance(); + + void addChangeListener( ChangeListener l ); + void removeChangeListener( ChangeListener l ); + } + + //---- class WindowTopBorder ------------------------------------------- + + static class WindowTopBorder + extends JBRCustomDecorations.JBRWindowTopBorder + { + private static WindowTopBorder instance; + + static JBRWindowTopBorder getInstance() { + if( canUseJBRCustomDecorations ) + return JBRWindowTopBorder.getInstance(); + + if( instance == null ) + instance = new WindowTopBorder(); + return instance; + } + + @Override + void installListeners() { + nativeProvider.addChangeListener( e -> { + update(); + + // repaint top borders of all windows + for( Window window : Window.getWindows() ) { + if( window.isDisplayable() ) + window.repaint( 0, 0, window.getWidth(), 1 ); + } + } ); + } + + @Override + boolean isColorizationColorAffectsBorders() { + return nativeProvider.isColorizationColorAffectsBorders(); + } + + @Override + Color getColorizationColor() { + return nativeProvider.getColorizationColor(); + } + + @Override + int getColorizationColorBalance() { + return nativeProvider.getColorizationColorBalance(); + } } } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java index 4c969409..52d7a82f 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTitlePane.java @@ -63,7 +63,7 @@ import javax.swing.border.AbstractBorder; import javax.swing.border.Border; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatSystemProperties; -import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder; +import com.formdev.flatlaf.ui.FlatNativeWindowBorder.WindowTopBorder; import com.formdev.flatlaf.util.ScaledImageIcon; import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.UIScale; @@ -704,8 +704,8 @@ debug*/ } else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) ) insets.bottom += UIScale.scale( 1 ); - if( hasJBRCustomDecoration() ) - insets = FlatUIUtils.addInsets( insets, JBRWindowTopBorder.getInstance().getBorderInsets() ); + if( hasNativeCustomDecoration() ) + insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() ); return insets; } @@ -723,8 +723,8 @@ debug*/ FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight ); } - if( hasJBRCustomDecoration() ) - JBRWindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height ); + if( hasNativeCustomDecoration() ) + WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height ); } protected Border getMenuBarBorder() { @@ -770,8 +770,8 @@ debug*/ activeChanged( true ); updateNativeTitleBarHeightAndHitTestSpots(); - if( hasJBRCustomDecoration() ) - JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); + if( hasNativeCustomDecoration() ) + WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); repaintWindowBorder(); } @@ -781,8 +781,8 @@ debug*/ activeChanged( false ); updateNativeTitleBarHeightAndHitTestSpots(); - if( hasJBRCustomDecoration() ) - JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); + if( hasNativeCustomDecoration() ) + WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); repaintWindowBorder(); } diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java index 680f3699..d026a1ce 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/JBRCustomDecorations.java @@ -211,15 +211,22 @@ public class JBRCustomDecorations return instance; } - private JBRWindowTopBorder() { + JBRWindowTopBorder() { super( 1, 0, 0, 0 ); - colorizationAffectsBorders = calculateAffectsBorders(); - activeColor = calculateActiveBorderColor(); + update(); + installListeners(); + } + void update() { + colorizationAffectsBorders = isColorizationColorAffectsBorders(); + activeColor = calculateActiveBorderColor(); + } + + void installListeners() { Toolkit toolkit = Toolkit.getDefaultToolkit(); toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> { - colorizationAffectsBorders = calculateAffectsBorders(); + colorizationAffectsBorders = isColorizationColorAffectsBorders(); activeColor = calculateActiveBorderColor(); } ); @@ -231,46 +238,50 @@ public class JBRCustomDecorations toolkit.addPropertyChangeListener( "win.frame.activeBorderColor", l ); } - private boolean calculateAffectsBorders() { + boolean isColorizationColorAffectsBorders() { Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" ); return (value instanceof Boolean) ? (Boolean) value : true; } + Color getColorizationColor() { + return (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor" ); + } + + int getColorizationColorBalance() { + Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColorBalance" ); + return (value instanceof Integer) ? (Integer) value : -1; + } + private Color calculateActiveBorderColor() { if( !colorizationAffectsBorders ) return defaultActiveBorder; - Toolkit toolkit = Toolkit.getDefaultToolkit(); - Color colorizationColor = (Color) toolkit.getDesktopProperty( "win.dwm.colorizationColor" ); + Color colorizationColor = getColorizationColor(); if( colorizationColor != null ) { - Object colorizationColorBalanceObj = toolkit.getDesktopProperty( "win.dwm.colorizationColorBalance" ); - if( colorizationColorBalanceObj instanceof Integer ) { - int colorizationColorBalance = (Integer) colorizationColorBalanceObj; - if( colorizationColorBalance < 0 || colorizationColorBalance > 100 ) - colorizationColorBalance = 100; + int colorizationColorBalance = getColorizationColorBalance(); + if( colorizationColorBalance < 0 || colorizationColorBalance > 100 ) + colorizationColorBalance = 100; - if( colorizationColorBalance == 0 ) - return new Color( 0xD9D9D9 ); - if( colorizationColorBalance == 100 ) - return colorizationColor; + if( colorizationColorBalance == 0 ) + return new Color( 0xD9D9D9 ); + if( colorizationColorBalance == 100 ) + return colorizationColor; - float alpha = colorizationColorBalance / 100.0f; - float remainder = 1 - alpha; - int r = Math.round( colorizationColor.getRed() * alpha + 0xD9 * remainder ); - int g = Math.round( colorizationColor.getGreen() * alpha + 0xD9 * remainder ); - int b = Math.round( colorizationColor.getBlue() * alpha + 0xD9 * remainder ); + float alpha = colorizationColorBalance / 100.0f; + float remainder = 1 - alpha; + int r = Math.round( colorizationColor.getRed() * alpha + 0xD9 * remainder ); + int g = Math.round( colorizationColor.getGreen() * alpha + 0xD9 * remainder ); + int b = Math.round( colorizationColor.getBlue() * alpha + 0xD9 * remainder ); - // avoid potential IllegalArgumentException in Color constructor - r = Math.min( Math.max( r, 0 ), 255 ); - g = Math.min( Math.max( g, 0 ), 255 ); - b = Math.min( Math.max( b, 0 ), 255 ); + // avoid potential IllegalArgumentException in Color constructor + r = Math.min( Math.max( r, 0 ), 255 ); + g = Math.min( Math.max( g, 0 ), 255 ); + b = Math.min( Math.max( b, 0 ), 255 ); - return new Color( r, g, b ); - } - return colorizationColor; + return new Color( r, g, b ); } - Color activeBorderColor = (Color) toolkit.getDesktopProperty( "win.frame.activeBorderColor" ); + Color activeBorderColor = (Color) Toolkit.getDefaultToolkit().getDesktopProperty( "win.frame.activeBorderColor" ); return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" ); } diff --git a/flatlaf-native-jna/src/main/java/com/formdev/flatlaf/nativejna/windows/FlatWindowsNativeWindowBorder.java b/flatlaf-native-jna/src/main/java/com/formdev/flatlaf/nativejna/windows/FlatWindowsNativeWindowBorder.java index 617adbf9..9e4439d5 100644 --- a/flatlaf-native-jna/src/main/java/com/formdev/flatlaf/nativejna/windows/FlatWindowsNativeWindowBorder.java +++ b/flatlaf-native-jna/src/main/java/com/formdev/flatlaf/nativejna/windows/FlatWindowsNativeWindowBorder.java @@ -17,8 +17,11 @@ package com.formdev.flatlaf.nativejna.windows; import static com.sun.jna.platform.win32.ShellAPI.*; +import static com.sun.jna.platform.win32.WinReg.*; import static com.sun.jna.platform.win32.WinUser.*; +import java.awt.Color; import java.awt.Dialog; +import java.awt.EventQueue; import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.Point; @@ -31,12 +34,17 @@ import java.util.List; import java.util.Map; import javax.swing.JDialog; import javax.swing.JFrame; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; import com.formdev.flatlaf.ui.FlatNativeWindowBorder; import com.formdev.flatlaf.util.SystemInfo; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.Structure; import com.sun.jna.Structure.FieldOrder; +import com.sun.jna.platform.win32.Advapi32Util; import com.sun.jna.platform.win32.BaseTSD; import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; import com.sun.jna.platform.win32.Shell32; @@ -63,6 +71,7 @@ import com.sun.jna.win32.W32APIOptions; // https://medium.com/swlh/customizing-the-title-bar-of-an-application-window-50a4ac3ed27e // https://github.com/kalbetredev/CustomDecoratedJFrame // https://github.com/Guerra24/NanoUI-win32 +// https://github.com/oberth/custom-chrome // https://github.com/rossy/borderless-window // @@ -80,6 +89,13 @@ public class FlatWindowsNativeWindowBorder implements FlatNativeWindowBorder.Provider { private final Map windowsMap = Collections.synchronizedMap( new IdentityHashMap<>() ); + private final EventListenerList listenerList = new EventListenerList(); + private Timer fireStateChangedTimer; + + private boolean colorizationUpToDate; + private boolean colorizationColorAffectsBorders; + private Color colorizationColor; + private int colorizationColorBalance; private static FlatWindowsNativeWindowBorder instance; @@ -166,6 +182,92 @@ public class FlatWindowsNativeWindowBorder wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null; } + @Override + public boolean isColorizationColorAffectsBorders() { + updateColorization(); + return colorizationColorAffectsBorders; + } + + @Override + public Color getColorizationColor() { + updateColorization(); + return colorizationColor; + } + + @Override + public int getColorizationColorBalance() { + updateColorization(); + return colorizationColorBalance; + } + + private void updateColorization() { + if( colorizationUpToDate ) + return; + colorizationUpToDate = true; + + String subKey = "SOFTWARE\\Microsoft\\Windows\\DWM"; + + int value = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorPrevalence" ); + colorizationColorAffectsBorders = (value > 0); + + value = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorizationColor" ); + colorizationColor = (value != -1) ? new Color( value ) : null; + + colorizationColorBalance = RegGetDword( HKEY_CURRENT_USER, subKey, "ColorizationColorBalance" ); + } + + private static int RegGetDword( HKEY hkey, String lpSubKey, String lpValue ) { + try { + return Advapi32Util.registryGetIntValue( hkey, lpSubKey, lpValue ); + } catch( RuntimeException ex ) { + return -1; + } + } + + @Override + public void addChangeListener( ChangeListener l ) { + listenerList.add( ChangeListener.class, l ); + } + + @Override + public void removeChangeListener( ChangeListener l ) { + listenerList.remove( ChangeListener.class, l ); + } + + private void fireStateChanged() { + Object[] listeners = listenerList.getListenerList(); + if( listeners.length == 0 ) + return; + + ChangeEvent e = new ChangeEvent( this ); + for( int i = 0; i < listeners.length; i += 2 ) { + if( listeners[i] == ChangeListener.class ) + ((ChangeListener)listeners[i+1]).stateChanged( e ); + } + } + + /** + * Because there may be sent many WM_DWMCOLORIZATIONCOLORCHANGED messages, + * slightly delay event firing and fire it only once (on the AWT thread). + */ + void fireStateChangedLaterOnce() { + EventQueue.invokeLater( () -> { + if( fireStateChangedTimer != null ) { + fireStateChangedTimer.restart(); + return; + } + + fireStateChangedTimer = new Timer( 300, e -> { + fireStateChangedTimer = null; + colorizationUpToDate = false; + + fireStateChanged(); + } ); + fireStateChangedTimer.setRepeats( false ); + fireStateChangedTimer.start(); + } ); + } + //---- class WndProc ------------------------------------------------------ private class WndProc @@ -176,7 +278,8 @@ public class FlatWindowsNativeWindowBorder private static final int WM_NCCALCSIZE = 0x0083, WM_NCHITTEST = 0x0084, - WM_NCRBUTTONUP = 0x00A5; + WM_NCRBUTTONUP = 0x00A5, + WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320; // WM_NCHITTEST mouse position codes private static final int @@ -258,6 +361,10 @@ public class FlatWindowsNativeWindowBorder openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) ); break; + case WM_DWMCOLORIZATIONCOLORCHANGED: + fireStateChangedLaterOnce(); + break; + case WM_DESTROY: return WmDestroy( hwnd, uMsg, wParam, lParam ); }