Native window decorations: fixed missing top border line

This commit is contained in:
Karl Tauber
2021-02-24 23:17:41 +01:00
parent 49bd53194a
commit 7341008449
4 changed files with 212 additions and 39 deletions

View File

@@ -16,6 +16,7 @@
package com.formdev.flatlaf.ui; package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@@ -26,8 +27,10 @@ import javax.swing.JFrame;
import javax.swing.JRootPane; import javax.swing.JRootPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.event.ChangeListener;
import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatSystemProperties; import com.formdev.flatlaf.FlatSystemProperties;
import com.formdev.flatlaf.ui.JBRCustomDecorations.JBRWindowTopBorder;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
/** /**
@@ -244,5 +247,57 @@ public class FlatNativeWindowBorder
void setTitleBarHeight( Window window, int titleBarHeight ); void setTitleBarHeight( Window window, int titleBarHeight );
void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots ); void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots );
void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds ); 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();
}
} }
} }

View File

@@ -63,7 +63,7 @@ import javax.swing.border.AbstractBorder;
import javax.swing.border.Border; import javax.swing.border.Border;
import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatSystemProperties; 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.ScaledImageIcon;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.formdev.flatlaf.util.UIScale; import com.formdev.flatlaf.util.UIScale;
@@ -704,8 +704,8 @@ debug*/
} else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) ) } else if( borderColor != null && (rootPane.getJMenuBar() == null || !rootPane.getJMenuBar().isVisible()) )
insets.bottom += UIScale.scale( 1 ); insets.bottom += UIScale.scale( 1 );
if( hasJBRCustomDecoration() ) if( hasNativeCustomDecoration() )
insets = FlatUIUtils.addInsets( insets, JBRWindowTopBorder.getInstance().getBorderInsets() ); insets = FlatUIUtils.addInsets( insets, WindowTopBorder.getInstance().getBorderInsets() );
return insets; return insets;
} }
@@ -723,8 +723,8 @@ debug*/
FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight ); FlatUIUtils.paintFilledRectangle( g, borderColor, x, y + height - lineHeight, width, lineHeight );
} }
if( hasJBRCustomDecoration() ) if( hasNativeCustomDecoration() )
JBRWindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height ); WindowTopBorder.getInstance().paintBorder( c, g, x, y, width, height );
} }
protected Border getMenuBarBorder() { protected Border getMenuBarBorder() {
@@ -770,8 +770,8 @@ debug*/
activeChanged( true ); activeChanged( true );
updateNativeTitleBarHeightAndHitTestSpots(); updateNativeTitleBarHeightAndHitTestSpots();
if( hasJBRCustomDecoration() ) if( hasNativeCustomDecoration() )
JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder(); repaintWindowBorder();
} }
@@ -781,8 +781,8 @@ debug*/
activeChanged( false ); activeChanged( false );
updateNativeTitleBarHeightAndHitTestSpots(); updateNativeTitleBarHeightAndHitTestSpots();
if( hasJBRCustomDecoration() ) if( hasNativeCustomDecoration() )
JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this ); WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
repaintWindowBorder(); repaintWindowBorder();
} }

View File

@@ -211,15 +211,22 @@ public class JBRCustomDecorations
return instance; return instance;
} }
private JBRWindowTopBorder() { JBRWindowTopBorder() {
super( 1, 0, 0, 0 ); super( 1, 0, 0, 0 );
colorizationAffectsBorders = calculateAffectsBorders(); update();
activeColor = calculateActiveBorderColor(); installListeners();
}
void update() {
colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor();
}
void installListeners() {
Toolkit toolkit = Toolkit.getDefaultToolkit(); Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> { toolkit.addPropertyChangeListener( "win.dwm.colorizationColor.affects.borders", e -> {
colorizationAffectsBorders = calculateAffectsBorders(); colorizationAffectsBorders = isColorizationColorAffectsBorders();
activeColor = calculateActiveBorderColor(); activeColor = calculateActiveBorderColor();
} ); } );
@@ -231,21 +238,27 @@ public class JBRCustomDecorations
toolkit.addPropertyChangeListener( "win.frame.activeBorderColor", l ); toolkit.addPropertyChangeListener( "win.frame.activeBorderColor", l );
} }
private boolean calculateAffectsBorders() { boolean isColorizationColorAffectsBorders() {
Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" ); Object value = Toolkit.getDefaultToolkit().getDesktopProperty( "win.dwm.colorizationColor.affects.borders" );
return (value instanceof Boolean) ? (Boolean) value : true; 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() { private Color calculateActiveBorderColor() {
if( !colorizationAffectsBorders ) if( !colorizationAffectsBorders )
return defaultActiveBorder; return defaultActiveBorder;
Toolkit toolkit = Toolkit.getDefaultToolkit(); Color colorizationColor = getColorizationColor();
Color colorizationColor = (Color) toolkit.getDesktopProperty( "win.dwm.colorizationColor" );
if( colorizationColor != null ) { if( colorizationColor != null ) {
Object colorizationColorBalanceObj = toolkit.getDesktopProperty( "win.dwm.colorizationColorBalance" ); int colorizationColorBalance = getColorizationColorBalance();
if( colorizationColorBalanceObj instanceof Integer ) {
int colorizationColorBalance = (Integer) colorizationColorBalanceObj;
if( colorizationColorBalance < 0 || colorizationColorBalance > 100 ) if( colorizationColorBalance < 0 || colorizationColorBalance > 100 )
colorizationColorBalance = 100; colorizationColorBalance = 100;
@@ -267,10 +280,8 @@ public class JBRCustomDecorations
return new Color( r, g, b ); return new Color( r, g, b );
} }
return colorizationColor;
}
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" ); return (activeBorderColor != null) ? activeBorderColor : UIManager.getColor( "MenuBar.borderColor" );
} }

View File

@@ -17,8 +17,11 @@
package com.formdev.flatlaf.nativejna.windows; package com.formdev.flatlaf.nativejna.windows;
import static com.sun.jna.platform.win32.ShellAPI.*; 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 static com.sun.jna.platform.win32.WinUser.*;
import java.awt.Color;
import java.awt.Dialog; import java.awt.Dialog;
import java.awt.EventQueue;
import java.awt.Frame; import java.awt.Frame;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.Point; import java.awt.Point;
@@ -31,12 +34,17 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFrame; 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.ui.FlatNativeWindowBorder;
import com.formdev.flatlaf.util.SystemInfo; import com.formdev.flatlaf.util.SystemInfo;
import com.sun.jna.Native; import com.sun.jna.Native;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.Structure; import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder; 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;
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
import com.sun.jna.platform.win32.Shell32; 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://medium.com/swlh/customizing-the-title-bar-of-an-application-window-50a4ac3ed27e
// https://github.com/kalbetredev/CustomDecoratedJFrame // https://github.com/kalbetredev/CustomDecoratedJFrame
// https://github.com/Guerra24/NanoUI-win32 // https://github.com/Guerra24/NanoUI-win32
// https://github.com/oberth/custom-chrome
// https://github.com/rossy/borderless-window // https://github.com/rossy/borderless-window
// //
@@ -80,6 +89,13 @@ public class FlatWindowsNativeWindowBorder
implements FlatNativeWindowBorder.Provider implements FlatNativeWindowBorder.Provider
{ {
private final Map<Window, WndProc> windowsMap = Collections.synchronizedMap( new IdentityHashMap<>() ); private final Map<Window, WndProc> 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; private static FlatWindowsNativeWindowBorder instance;
@@ -166,6 +182,92 @@ public class FlatWindowsNativeWindowBorder
wndProc.appIconBounds = (appIconBounds != null) ? new Rectangle( appIconBounds ) : null; 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 ------------------------------------------------------ //---- class WndProc ------------------------------------------------------
private class WndProc private class WndProc
@@ -176,7 +278,8 @@ public class FlatWindowsNativeWindowBorder
private static final int private static final int
WM_NCCALCSIZE = 0x0083, WM_NCCALCSIZE = 0x0083,
WM_NCHITTEST = 0x0084, WM_NCHITTEST = 0x0084,
WM_NCRBUTTONUP = 0x00A5; WM_NCRBUTTONUP = 0x00A5,
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
// WM_NCHITTEST mouse position codes // WM_NCHITTEST mouse position codes
private static final int private static final int
@@ -258,6 +361,10 @@ public class FlatWindowsNativeWindowBorder
openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) ); openSystemMenu( hwnd, GET_X_LPARAM( lParam ), GET_Y_LPARAM( lParam ) );
break; break;
case WM_DWMCOLORIZATIONCOLORCHANGED:
fireStateChangedLaterOnce();
break;
case WM_DESTROY: case WM_DESTROY:
return WmDestroy( hwnd, uMsg, wParam, lParam ); return WmDestroy( hwnd, uMsg, wParam, lParam );
} }