mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-11 06:27:13 -06:00
Merge pull request #262 from native-window-decorations-jna
Native window decorations for Windows 10 (using JNA)
This commit is contained in:
@@ -44,6 +44,7 @@ import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.LookAndFeel;
|
||||
import javax.swing.PopupFactory;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -57,8 +58,8 @@ import javax.swing.plaf.UIResource;
|
||||
import javax.swing.plaf.basic.BasicLookAndFeel;
|
||||
import javax.swing.text.StyleContext;
|
||||
import javax.swing.text.html.HTMLEditorKit;
|
||||
import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
|
||||
import com.formdev.flatlaf.ui.FlatPopupFactory;
|
||||
import com.formdev.flatlaf.ui.JBRCustomDecorations;
|
||||
import com.formdev.flatlaf.util.GrayFilter;
|
||||
import com.formdev.flatlaf.util.LoggingFacade;
|
||||
import com.formdev.flatlaf.util.MultiResolutionImageSupport;
|
||||
@@ -144,27 +145,39 @@ public abstract class FlatLaf
|
||||
* This depends on the operating system and on the used Java runtime.
|
||||
* <p>
|
||||
* To use custom window decorations in your application, enable them with
|
||||
* following code (before creating any frames or dialogs). Then custom window
|
||||
* decorations are only enabled if this method returns {@code true}.
|
||||
* following code (before creating any frames or dialogs).
|
||||
* <pre>
|
||||
* JFrame.setDefaultLookAndFeelDecorated( true );
|
||||
* JDialog.setDefaultLookAndFeelDecorated( true );
|
||||
* </pre>
|
||||
* <p>
|
||||
* Returns {@code true} on Windows 10, {@code false} otherwise.
|
||||
* Then custom window decorations are only enabled if this method returns {@code true}.
|
||||
* In this case, when creating a {@link JFrame} or {@link JDialog}, the frame/dialog will be made
|
||||
* undecorated ({@link JFrame#setUndecorated(boolean)} / {@link JDialog#setUndecorated(boolean)}),
|
||||
* the window decoration style of the {@link JRootPane} will
|
||||
* {@link JRootPane#FRAME} / {@link JRootPane#PLAIN_DIALOG}
|
||||
* (see {@link JRootPane#setWindowDecorationStyle(int)}) and the look and feel
|
||||
* is responsible for the whole frame/dialog border (including window resizing).
|
||||
* <p>
|
||||
* Return also {@code false} if running on Windows 10 in
|
||||
* This method returns {@code true} on Windows 10 (see exception below), {@code false} otherwise.
|
||||
* <p>
|
||||
* Returns also {@code false} on Windows 10 if:
|
||||
* <ul>
|
||||
* <li>FlatLaf native window border support is available (requires {@code flatlaf-natives-jna.jar})</li>
|
||||
* <li>running in
|
||||
* <a href="https://confluence.jetbrains.com/display/JBR/JetBrains+Runtime">JetBrains Runtime 11 (or later)</a>
|
||||
* (<a href="https://github.com/JetBrains/JetBrainsRuntime">source code on github</a>)
|
||||
* and JBR supports custom window decorations. In this case, JBR custom decorations
|
||||
* are enabled if {@link JFrame#isDefaultLookAndFeelDecorated()} or
|
||||
* and JBR supports custom window decorations
|
||||
* </li>
|
||||
* </ul>
|
||||
* In this case, custom decorations are enabled by the root pane
|
||||
* if {@link JFrame#isDefaultLookAndFeelDecorated()} or
|
||||
* {@link JDialog#isDefaultLookAndFeelDecorated()} return {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public boolean getSupportsWindowDecorations() {
|
||||
if( SystemInfo.isJetBrainsJVM_11_orLater &&
|
||||
SystemInfo.isWindows_10_orLater &&
|
||||
JBRCustomDecorations.isSupported() )
|
||||
if( SystemInfo.isWindows_10_orLater &&
|
||||
FlatNativeWindowBorder.isSupported() )
|
||||
return false;
|
||||
|
||||
return SystemInfo.isWindows_10_orLater;
|
||||
|
||||
@@ -71,6 +71,25 @@ public interface FlatSystemProperties
|
||||
*/
|
||||
String USE_WINDOW_DECORATIONS = "flatlaf.useWindowDecorations";
|
||||
|
||||
/**
|
||||
* Specifies whether FlatLaf native window decorations should be used
|
||||
* when creating {@code JFrame} or {@code JDialog}.
|
||||
* Requires that {@code flatlaf-natives-jna.jar} is on classpath/modulepath.
|
||||
* <p>
|
||||
* Setting this to {@code true} forces using FlatLaf native window decorations
|
||||
* even if they are not enabled by the application.
|
||||
* <p>
|
||||
* Setting this to {@code false} disables using FlatLaf native window decorations.
|
||||
* <p>
|
||||
* (requires Window 10)
|
||||
* <p>
|
||||
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
|
||||
* <strong>Default</strong> none
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
String USE_NATIVE_WINDOW_DECORATIONS = "flatlaf.useNativeWindowDecorations";
|
||||
|
||||
/**
|
||||
* Specifies whether JetBrains Runtime custom window decorations should be used
|
||||
* when creating {@code JFrame} or {@code JDialog}.
|
||||
@@ -81,10 +100,12 @@ public interface FlatSystemProperties
|
||||
* Setting this to {@code true} forces using JetBrains Runtime custom window decorations
|
||||
* even if they are not enabled by the application.
|
||||
* <p>
|
||||
* Setting this to {@code false} disables using JetBrains Runtime custom window decorations.
|
||||
* <p>
|
||||
* (requires Window 10)
|
||||
* <p>
|
||||
* <strong>Allowed Values</strong> {@code false} and {@code true}<br>
|
||||
* <strong>Default</strong> {@code true}
|
||||
* <strong>Default</strong> none
|
||||
*/
|
||||
String USE_JETBRAINS_CUSTOM_DECORATIONS = "flatlaf.useJetBrainsCustomDecorations";
|
||||
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2021 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.formdev.flatlaf.ui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import javax.swing.JDialog;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Support for custom window decorations with native window border.
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class FlatNativeWindowBorder
|
||||
{
|
||||
// check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
|
||||
private static final boolean canUseJBRCustomDecorations
|
||||
= SystemInfo.isJetBrainsJVM_11_orLater && SystemInfo.isWindows_10_orLater;
|
||||
|
||||
private static Boolean supported;
|
||||
private static Provider nativeProvider;
|
||||
|
||||
public static boolean isSupported() {
|
||||
if( canUseJBRCustomDecorations )
|
||||
return JBRCustomDecorations.isSupported();
|
||||
|
||||
initialize();
|
||||
return supported;
|
||||
}
|
||||
|
||||
static Object install( JRootPane rootPane ) {
|
||||
if( canUseJBRCustomDecorations )
|
||||
return JBRCustomDecorations.install( rootPane );
|
||||
|
||||
if( !isSupported() )
|
||||
return null;
|
||||
|
||||
// Check whether root pane already has a window, which is the case when switching LaF.
|
||||
// Also check whether the window is displayable, which is required to install
|
||||
// FlatLaf native window border.
|
||||
// If the window is not displayable, then it was probably closed/disposed but not yet removed
|
||||
// from the list of windows that AWT maintains and returns with Window.getWindows().
|
||||
// It could be also be a window that is currently hidden, but may be shown later.
|
||||
Window window = SwingUtilities.windowForComponent( rootPane );
|
||||
if( window != null && window.isDisplayable() ) {
|
||||
install( window, FlatSystemProperties.USE_NATIVE_WINDOW_DECORATIONS );
|
||||
return null;
|
||||
}
|
||||
|
||||
// Install FlatLaf native window border, which must be done late,
|
||||
// when the native window is already created, because it needs access to the window.
|
||||
// "ancestor" property change event is fired from JComponent.addNotify() and removeNotify().
|
||||
PropertyChangeListener ancestorListener = e -> {
|
||||
Object newValue = e.getNewValue();
|
||||
if( newValue instanceof Window )
|
||||
install( (Window) newValue, FlatSystemProperties.USE_NATIVE_WINDOW_DECORATIONS );
|
||||
else if( newValue == null && e.getOldValue() instanceof Window )
|
||||
uninstall( (Window) e.getOldValue() );
|
||||
};
|
||||
rootPane.addPropertyChangeListener( "ancestor", ancestorListener );
|
||||
return ancestorListener;
|
||||
}
|
||||
|
||||
static void install( Window window, String systemPropertyKey ) {
|
||||
if( hasCustomDecoration( window ) )
|
||||
return;
|
||||
|
||||
// do not enable native window border if LaF provides decorations
|
||||
if( UIManager.getLookAndFeel().getSupportsWindowDecorations() )
|
||||
return;
|
||||
|
||||
if( window instanceof JFrame ) {
|
||||
JFrame frame = (JFrame) window;
|
||||
|
||||
// do not enable native window border if JFrame should use system window decorations
|
||||
// and if not forced to use FlatLaf/JBR native window decorations
|
||||
if( !JFrame.isDefaultLookAndFeelDecorated() &&
|
||||
!FlatSystemProperties.getBoolean( systemPropertyKey, false ))
|
||||
return;
|
||||
|
||||
// do not enable native window border if frame is undecorated
|
||||
if( frame.isUndecorated() )
|
||||
return;
|
||||
|
||||
// enable native window border for window
|
||||
setHasCustomDecoration( frame, true );
|
||||
|
||||
// enable Swing window decoration
|
||||
frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME );
|
||||
|
||||
} else if( window instanceof JDialog ) {
|
||||
JDialog dialog = (JDialog) window;
|
||||
|
||||
// do not enable native window border if JDialog should use system window decorations
|
||||
// and if not forced to use FlatLaf/JBR native window decorations
|
||||
if( !JDialog.isDefaultLookAndFeelDecorated() &&
|
||||
!FlatSystemProperties.getBoolean( systemPropertyKey, false ))
|
||||
return;
|
||||
|
||||
// do not enable native window border if dialog is undecorated
|
||||
if( dialog.isUndecorated() )
|
||||
return;
|
||||
|
||||
// enable native window border for window
|
||||
setHasCustomDecoration( dialog, true );
|
||||
|
||||
// enable Swing window decoration
|
||||
dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
|
||||
}
|
||||
}
|
||||
|
||||
static void uninstall( JRootPane rootPane, Object data ) {
|
||||
if( canUseJBRCustomDecorations ) {
|
||||
JBRCustomDecorations.uninstall( rootPane, data );
|
||||
return;
|
||||
}
|
||||
|
||||
// remove listener
|
||||
if( data instanceof PropertyChangeListener )
|
||||
rootPane.removePropertyChangeListener( "ancestor", (PropertyChangeListener) data );
|
||||
|
||||
// uninstall native window border, except when switching to another FlatLaf theme
|
||||
Window window = SwingUtilities.windowForComponent( rootPane );
|
||||
if( window != null )
|
||||
uninstall( window );
|
||||
}
|
||||
|
||||
private static void uninstall( Window window ) {
|
||||
if( !hasCustomDecoration( window ) )
|
||||
return;
|
||||
|
||||
// do not uninstall when switching to another FlatLaf theme
|
||||
if( UIManager.getLookAndFeel() instanceof FlatLaf )
|
||||
return;
|
||||
|
||||
// disable native window border for window
|
||||
setHasCustomDecoration( window, false );
|
||||
|
||||
if( window instanceof JFrame ) {
|
||||
JFrame frame = (JFrame) window;
|
||||
|
||||
// disable Swing window decoration
|
||||
frame.getRootPane().setWindowDecorationStyle( JRootPane.NONE );
|
||||
|
||||
} else if( window instanceof JDialog ) {
|
||||
JDialog dialog = (JDialog) window;
|
||||
|
||||
// disable Swing window decoration
|
||||
dialog.getRootPane().setWindowDecorationStyle( JRootPane.NONE );
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasCustomDecoration( Window window ) {
|
||||
if( canUseJBRCustomDecorations )
|
||||
return JBRCustomDecorations.hasCustomDecoration( window );
|
||||
|
||||
if( !isSupported() )
|
||||
return false;
|
||||
|
||||
return nativeProvider.hasCustomDecoration( window );
|
||||
}
|
||||
|
||||
public static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
|
||||
if( canUseJBRCustomDecorations ) {
|
||||
JBRCustomDecorations.setHasCustomDecoration( window, hasCustomDecoration );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !isSupported() )
|
||||
return;
|
||||
|
||||
nativeProvider.setHasCustomDecoration( window, hasCustomDecoration );
|
||||
}
|
||||
|
||||
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight,
|
||||
List<Rectangle> hitTestSpots, Rectangle appIconBounds )
|
||||
{
|
||||
if( canUseJBRCustomDecorations ) {
|
||||
JBRCustomDecorations.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !isSupported() )
|
||||
return;
|
||||
|
||||
nativeProvider.setTitleBarHeight( window, titleBarHeight );
|
||||
nativeProvider.setTitleBarHitTestSpots( window, hitTestSpots );
|
||||
nativeProvider.setTitleBarAppIconBounds( window, appIconBounds );
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
if( supported != null )
|
||||
return;
|
||||
supported = false;
|
||||
|
||||
// requires Windows 10 on x86_64
|
||||
if( !SystemInfo.isWindows_10_orLater || !SystemInfo.isX86_64 )
|
||||
return;
|
||||
|
||||
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_NATIVE_WINDOW_DECORATIONS, true ) )
|
||||
return;
|
||||
|
||||
try {
|
||||
Class<?> cls = Class.forName( "com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder" );
|
||||
Method m = cls.getMethod( "getInstance" );
|
||||
nativeProvider = (Provider) m.invoke( null );
|
||||
|
||||
supported = (nativeProvider != null);
|
||||
} catch( Exception ex ) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
//---- interface Provider -------------------------------------------------
|
||||
|
||||
public interface Provider
|
||||
{
|
||||
boolean hasCustomDecoration( Window window );
|
||||
void setHasCustomDecoration( Window window, boolean hasCustomDecoration );
|
||||
void setTitleBarHeight( Window window, int titleBarHeight );
|
||||
void setTitleBarHitTestSpots( Window window, List<Rectangle> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,16 +70,13 @@ import com.formdev.flatlaf.util.UIScale;
|
||||
public class FlatRootPaneUI
|
||||
extends BasicRootPaneUI
|
||||
{
|
||||
// check this field before using class JBRCustomDecorations to avoid unnecessary loading of that class
|
||||
static final boolean canUseJBRCustomDecorations
|
||||
= SystemInfo.isJetBrainsJVM_11_orLater && SystemInfo.isWindows_10_orLater;
|
||||
|
||||
protected final Color borderColor = UIManager.getColor( "TitlePane.borderColor" );
|
||||
|
||||
protected JRootPane rootPane;
|
||||
protected FlatTitlePane titlePane;
|
||||
protected FlatWindowResizer windowResizer;
|
||||
|
||||
private Object nativeWindowBorderData;
|
||||
private LayoutManager oldLayout;
|
||||
|
||||
public static ComponentUI createUI( JComponent c ) {
|
||||
@@ -97,8 +94,7 @@ public class FlatRootPaneUI
|
||||
else
|
||||
installBorder();
|
||||
|
||||
if( canUseJBRCustomDecorations )
|
||||
JBRCustomDecorations.install( rootPane );
|
||||
nativeWindowBorderData = FlatNativeWindowBorder.install( rootPane );
|
||||
}
|
||||
|
||||
protected void installBorder() {
|
||||
@@ -113,6 +109,8 @@ public class FlatRootPaneUI
|
||||
public void uninstallUI( JComponent c ) {
|
||||
super.uninstallUI( c );
|
||||
|
||||
FlatNativeWindowBorder.uninstall( rootPane, nativeWindowBorderData );
|
||||
|
||||
uninstallClientDecorations();
|
||||
rootPane = null;
|
||||
}
|
||||
@@ -139,10 +137,10 @@ public class FlatRootPaneUI
|
||||
}
|
||||
|
||||
protected void installClientDecorations() {
|
||||
boolean isJBRSupported = canUseJBRCustomDecorations && JBRCustomDecorations.isSupported();
|
||||
boolean isNativeWindowBorderSupported = FlatNativeWindowBorder.isSupported();
|
||||
|
||||
// install border
|
||||
if( rootPane.getWindowDecorationStyle() != JRootPane.NONE && !isJBRSupported )
|
||||
if( rootPane.getWindowDecorationStyle() != JRootPane.NONE && !isNativeWindowBorderSupported )
|
||||
LookAndFeel.installBorder( rootPane, "RootPane.border" );
|
||||
else
|
||||
LookAndFeel.uninstallBorder( rootPane );
|
||||
@@ -155,7 +153,7 @@ public class FlatRootPaneUI
|
||||
rootPane.setLayout( createRootLayout() );
|
||||
|
||||
// install window resizer
|
||||
if( !isJBRSupported )
|
||||
if( !isNativeWindowBorderSupported )
|
||||
windowResizer = createWindowResizer();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -337,7 +337,7 @@ public class FlatTitlePane
|
||||
// show/hide icon
|
||||
iconLabel.setVisible( hasIcon );
|
||||
|
||||
updateJBRHitTestSpotsAndTitleBarHeightLater();
|
||||
updateNativeTitleBarHeightAndHitTestSpotsLater();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -355,7 +355,7 @@ public class FlatTitlePane
|
||||
installWindowListeners();
|
||||
}
|
||||
|
||||
updateJBRHitTestSpotsAndTitleBarHeightLater();
|
||||
updateNativeTitleBarHeightAndHitTestSpotsLater();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -435,7 +435,7 @@ public class FlatTitlePane
|
||||
}
|
||||
|
||||
protected void menuBarLayouted() {
|
||||
updateJBRHitTestSpotsAndTitleBarHeightLater();
|
||||
updateNativeTitleBarHeightAndHitTestSpotsLater();
|
||||
}
|
||||
|
||||
/*debug
|
||||
@@ -449,8 +449,15 @@ public class FlatTitlePane
|
||||
}
|
||||
if( debugHitTestSpots != null ) {
|
||||
g.setColor( Color.blue );
|
||||
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
|
||||
for( Rectangle r : debugHitTestSpots )
|
||||
g.drawRect( r.x, r.y, r.width, r.height );
|
||||
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
|
||||
}
|
||||
if( debugAppIconBounds != null ) {
|
||||
g.setColor( Color.red );
|
||||
Point offset = SwingUtilities.convertPoint( this, 0, 0, window );
|
||||
Rectangle r = debugAppIconBounds;
|
||||
g.drawRect( r.x - offset.x, r.y - offset.y, r.width - 1, r.height - 1 );
|
||||
}
|
||||
}
|
||||
debug*/
|
||||
@@ -503,9 +510,9 @@ debug*/
|
||||
Frame frame = (Frame) window;
|
||||
|
||||
// set maximized bounds to avoid that maximized window overlaps Windows task bar
|
||||
// (if not running in JBR and if not modified from the application)
|
||||
// (if not having native window border and if not modified from the application)
|
||||
Rectangle oldMaximizedBounds = frame.getMaximizedBounds();
|
||||
if( !hasJBRCustomDecoration() &&
|
||||
if( !hasNativeCustomDecoration() &&
|
||||
(oldMaximizedBounds == null ||
|
||||
Objects.equals( oldMaximizedBounds, rootPane.getClientProperty( "_flatlaf.maximizedBounds" ) )) )
|
||||
{
|
||||
@@ -601,46 +608,66 @@ debug*/
|
||||
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
|
||||
}
|
||||
|
||||
protected boolean hasJBRCustomDecoration() {
|
||||
return FlatRootPaneUI.canUseJBRCustomDecorations &&
|
||||
window != null &&
|
||||
JBRCustomDecorations.hasCustomDecoration( window );
|
||||
private boolean hasJBRCustomDecoration() {
|
||||
return window != null && JBRCustomDecorations.hasCustomDecoration( window );
|
||||
}
|
||||
|
||||
protected void updateJBRHitTestSpotsAndTitleBarHeightLater() {
|
||||
/**
|
||||
* Returns whether windows uses native window border and has custom decorations enabled.
|
||||
*/
|
||||
protected boolean hasNativeCustomDecoration() {
|
||||
return window != null && FlatNativeWindowBorder.hasCustomDecoration( window );
|
||||
}
|
||||
|
||||
protected void updateNativeTitleBarHeightAndHitTestSpotsLater() {
|
||||
EventQueue.invokeLater( () -> {
|
||||
updateJBRHitTestSpotsAndTitleBarHeight();
|
||||
updateNativeTitleBarHeightAndHitTestSpots();
|
||||
} );
|
||||
}
|
||||
|
||||
protected void updateJBRHitTestSpotsAndTitleBarHeight() {
|
||||
protected void updateNativeTitleBarHeightAndHitTestSpots() {
|
||||
if( !isDisplayable() )
|
||||
return;
|
||||
|
||||
if( !hasJBRCustomDecoration() )
|
||||
if( !hasNativeCustomDecoration() )
|
||||
return;
|
||||
|
||||
List<Rectangle> hitTestSpots = new ArrayList<>();
|
||||
if( iconLabel.isVisible() )
|
||||
addJBRHitTestSpot( iconLabel, false, hitTestSpots );
|
||||
addJBRHitTestSpot( buttonPanel, false, hitTestSpots );
|
||||
addJBRHitTestSpot( menuBarPlaceholder, true, hitTestSpots );
|
||||
|
||||
int titleBarHeight = getHeight();
|
||||
// slightly reduce height so that component receives mouseExit events
|
||||
if( titleBarHeight > 0 )
|
||||
titleBarHeight--;
|
||||
|
||||
JBRCustomDecorations.setHitTestSpotsAndTitleBarHeight( window, hitTestSpots, titleBarHeight );
|
||||
List<Rectangle> hitTestSpots = new ArrayList<>();
|
||||
Rectangle appIconBounds = null;
|
||||
if( iconLabel.isVisible() ) {
|
||||
// compute real icon size (without insets)
|
||||
Point location = SwingUtilities.convertPoint( iconLabel, 0, 0, window );
|
||||
Insets iconInsets = iconLabel.getInsets();
|
||||
Rectangle iconBounds = new Rectangle(
|
||||
location.x + iconInsets.left,
|
||||
location.y + iconInsets.top,
|
||||
iconLabel.getWidth() - iconInsets.left - iconInsets.right,
|
||||
iconLabel.getHeight() - iconInsets.top - iconInsets.bottom );
|
||||
|
||||
if( hasJBRCustomDecoration() )
|
||||
hitTestSpots.add( iconBounds );
|
||||
else
|
||||
appIconBounds = iconBounds;
|
||||
}
|
||||
addNativeHitTestSpot( buttonPanel, false, hitTestSpots );
|
||||
addNativeHitTestSpot( menuBarPlaceholder, true, hitTestSpots );
|
||||
|
||||
FlatNativeWindowBorder.setTitleBarHeightAndHitTestSpots( window, titleBarHeight, hitTestSpots, appIconBounds );
|
||||
|
||||
/*debug
|
||||
debugHitTestSpots = hitTestSpots;
|
||||
debugTitleBarHeight = titleBarHeight;
|
||||
debugHitTestSpots = hitTestSpots;
|
||||
debugAppIconBounds = appIconBounds;
|
||||
repaint();
|
||||
debug*/
|
||||
}
|
||||
|
||||
protected void addJBRHitTestSpot( JComponent c, boolean subtractMenuBarMargins, List<Rectangle> hitTestSpots ) {
|
||||
protected void addNativeHitTestSpot( JComponent c, boolean subtractMenuBarMargins, List<Rectangle> hitTestSpots ) {
|
||||
Dimension size = c.getSize();
|
||||
if( size.width <= 0 || size.height <= 0 )
|
||||
return;
|
||||
@@ -655,8 +682,9 @@ debug*/
|
||||
}
|
||||
|
||||
/*debug
|
||||
private List<Rectangle> debugHitTestSpots;
|
||||
private int debugTitleBarHeight;
|
||||
private List<Rectangle> debugHitTestSpots;
|
||||
private Rectangle debugAppIconBounds;
|
||||
debug*/
|
||||
|
||||
//---- class TitlePaneBorder ----------------------------------------------
|
||||
@@ -676,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;
|
||||
}
|
||||
@@ -695,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() {
|
||||
@@ -730,7 +758,7 @@ debug*/
|
||||
break;
|
||||
|
||||
case "componentOrientation":
|
||||
updateJBRHitTestSpotsAndTitleBarHeightLater();
|
||||
updateNativeTitleBarHeightAndHitTestSpotsLater();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -740,10 +768,10 @@ debug*/
|
||||
@Override
|
||||
public void windowActivated( WindowEvent e ) {
|
||||
activeChanged( true );
|
||||
updateJBRHitTestSpotsAndTitleBarHeight();
|
||||
updateNativeTitleBarHeightAndHitTestSpots();
|
||||
|
||||
if( hasJBRCustomDecoration() )
|
||||
JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
|
||||
if( hasNativeCustomDecoration() )
|
||||
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
|
||||
|
||||
repaintWindowBorder();
|
||||
}
|
||||
@@ -751,10 +779,10 @@ debug*/
|
||||
@Override
|
||||
public void windowDeactivated( WindowEvent e ) {
|
||||
activeChanged( false );
|
||||
updateJBRHitTestSpotsAndTitleBarHeight();
|
||||
updateNativeTitleBarHeightAndHitTestSpots();
|
||||
|
||||
if( hasJBRCustomDecoration() )
|
||||
JBRWindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
|
||||
if( hasNativeCustomDecoration() )
|
||||
WindowTopBorder.getInstance().repaintBorder( FlatTitlePane.this );
|
||||
|
||||
repaintWindowBorder();
|
||||
}
|
||||
@@ -762,7 +790,7 @@ debug*/
|
||||
@Override
|
||||
public void windowStateChanged( WindowEvent e ) {
|
||||
frameStateChanged();
|
||||
updateJBRHitTestSpotsAndTitleBarHeight();
|
||||
updateNativeTitleBarHeightAndHitTestSpots();
|
||||
}
|
||||
|
||||
//---- interface MouseListener ----
|
||||
@@ -775,7 +803,7 @@ debug*/
|
||||
if( e.getSource() == iconLabel ) {
|
||||
// double-click on icon closes window
|
||||
close();
|
||||
} else if( !hasJBRCustomDecoration() &&
|
||||
} else if( !hasNativeCustomDecoration() &&
|
||||
window instanceof Frame &&
|
||||
((Frame)window).isResizable() )
|
||||
{
|
||||
@@ -808,8 +836,8 @@ debug*/
|
||||
if( window == null )
|
||||
return; // should newer occur
|
||||
|
||||
if( hasJBRCustomDecoration() )
|
||||
return; // do nothing if running in JBR
|
||||
if( hasNativeCustomDecoration() )
|
||||
return; // do nothing if having native window border
|
||||
|
||||
// restore window if it is maximized
|
||||
if( window instanceof Frame ) {
|
||||
@@ -852,7 +880,7 @@ debug*/
|
||||
|
||||
@Override
|
||||
public void componentResized( ComponentEvent e ) {
|
||||
updateJBRHitTestSpotsAndTitleBarHeightLater();
|
||||
updateNativeTitleBarHeightAndHitTestSpotsLater();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,9 +29,8 @@ import java.awt.event.HierarchyEvent;
|
||||
import java.awt.event.HierarchyListener;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
@@ -54,26 +53,29 @@ import com.formdev.flatlaf.util.SystemInfo;
|
||||
*/
|
||||
public class JBRCustomDecorations
|
||||
{
|
||||
private static boolean initialized;
|
||||
private static Boolean supported;
|
||||
private static Method Window_hasCustomDecoration;
|
||||
private static Method Window_setHasCustomDecoration;
|
||||
private static Method WWindowPeer_setCustomDecorationHitTestSpots;
|
||||
private static Method WWindowPeer_setCustomDecorationTitleBarHeight;
|
||||
private static Method WWindowPeer_setCustomDecorationHitTestSpots;
|
||||
private static Method AWTAccessor_getComponentAccessor;
|
||||
private static Method AWTAccessor_ComponentAccessor_getPeer;
|
||||
|
||||
public static boolean isSupported() {
|
||||
initialize();
|
||||
return Window_setHasCustomDecoration != null;
|
||||
return supported;
|
||||
}
|
||||
|
||||
static void install( JRootPane rootPane ) {
|
||||
static Object install( JRootPane rootPane ) {
|
||||
if( !isSupported() )
|
||||
return;
|
||||
return null;
|
||||
|
||||
// check whether root pane already has a parent, which is the case when switching LaF
|
||||
if( rootPane.getParent() != null )
|
||||
return;
|
||||
Window window = SwingUtilities.windowForComponent( rootPane );
|
||||
if( window != null ) {
|
||||
FlatNativeWindowBorder.install( window, FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS );
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use hierarchy listener to wait until the root pane is added to a window.
|
||||
// Enabling JBR decorations must be done very early, probably before
|
||||
@@ -87,8 +89,9 @@ public class JBRCustomDecorations
|
||||
|
||||
Container parent = e.getChangedParent();
|
||||
if( parent instanceof Window )
|
||||
install( (Window) parent );
|
||||
FlatNativeWindowBorder.install( (Window) parent, FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS );
|
||||
|
||||
// remove listener since it is actually not possible to uninstall JBR decorations
|
||||
// use invokeLater to remove listener to avoid that listener
|
||||
// is removed while listener queue is processed
|
||||
EventQueue.invokeLater( () -> {
|
||||
@@ -97,54 +100,20 @@ public class JBRCustomDecorations
|
||||
}
|
||||
};
|
||||
rootPane.addHierarchyListener( addListener );
|
||||
return addListener;
|
||||
}
|
||||
|
||||
static void install( Window window ) {
|
||||
if( !isSupported() )
|
||||
return;
|
||||
static void uninstall( JRootPane rootPane, Object data ) {
|
||||
// remove listener (if not yet done)
|
||||
if( data instanceof HierarchyListener )
|
||||
rootPane.removeHierarchyListener( (HierarchyListener) data );
|
||||
|
||||
// do not enable JBR decorations if LaF provides decorations
|
||||
if( UIManager.getLookAndFeel().getSupportsWindowDecorations() )
|
||||
return;
|
||||
|
||||
if( window instanceof JFrame ) {
|
||||
JFrame frame = (JFrame) window;
|
||||
|
||||
// do not enable JBR decorations if JFrame should use system window decorations
|
||||
// and if not forced to use JBR decorations
|
||||
if( !JFrame.isDefaultLookAndFeelDecorated() &&
|
||||
!FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false ))
|
||||
return;
|
||||
|
||||
// do not enable JBR decorations if frame is undecorated
|
||||
if( frame.isUndecorated() )
|
||||
return;
|
||||
|
||||
// enable JBR custom window decoration for window
|
||||
setHasCustomDecoration( frame );
|
||||
|
||||
// enable Swing window decoration
|
||||
frame.getRootPane().setWindowDecorationStyle( JRootPane.FRAME );
|
||||
|
||||
} else if( window instanceof JDialog ) {
|
||||
JDialog dialog = (JDialog) window;
|
||||
|
||||
// do not enable JBR decorations if JDialog should use system window decorations
|
||||
// and if not forced to use JBR decorations
|
||||
if( !JDialog.isDefaultLookAndFeelDecorated() &&
|
||||
!FlatSystemProperties.getBoolean( FlatSystemProperties.USE_JETBRAINS_CUSTOM_DECORATIONS, false ))
|
||||
return;
|
||||
|
||||
// do not enable JBR decorations if dialog is undecorated
|
||||
if( dialog.isUndecorated() )
|
||||
return;
|
||||
|
||||
// enable JBR custom window decoration for window
|
||||
setHasCustomDecoration( dialog );
|
||||
|
||||
// enable Swing window decoration
|
||||
dialog.getRootPane().setWindowDecorationStyle( JRootPane.PLAIN_DIALOG );
|
||||
}
|
||||
// since it is actually not possible to uninstall JBR decorations,
|
||||
// simply reduce titleBarHeight so that it is still possible to resize window
|
||||
// and remove hitTestSpots
|
||||
Window window = SwingUtilities.windowForComponent( rootPane );
|
||||
if( window != null )
|
||||
setHasCustomDecoration( window, false );
|
||||
}
|
||||
|
||||
static boolean hasCustomDecoration( Window window ) {
|
||||
@@ -159,35 +128,38 @@ public class JBRCustomDecorations
|
||||
}
|
||||
}
|
||||
|
||||
static void setHasCustomDecoration( Window window ) {
|
||||
static void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
|
||||
if( !isSupported() )
|
||||
return;
|
||||
|
||||
try {
|
||||
Window_setHasCustomDecoration.invoke( window );
|
||||
if( hasCustomDecoration )
|
||||
Window_setHasCustomDecoration.invoke( window );
|
||||
else
|
||||
setTitleBarHeightAndHitTestSpots( window, 4, Collections.emptyList() );
|
||||
} catch( Exception ex ) {
|
||||
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||
}
|
||||
}
|
||||
|
||||
static void setHitTestSpotsAndTitleBarHeight( Window window, List<Rectangle> hitTestSpots, int titleBarHeight ) {
|
||||
static void setTitleBarHeightAndHitTestSpots( Window window, int titleBarHeight, List<Rectangle> hitTestSpots ) {
|
||||
if( !isSupported() )
|
||||
return;
|
||||
|
||||
try {
|
||||
Object compAccessor = AWTAccessor_getComponentAccessor.invoke( null );
|
||||
Object peer = AWTAccessor_ComponentAccessor_getPeer.invoke( compAccessor, window );
|
||||
WWindowPeer_setCustomDecorationHitTestSpots.invoke( peer, hitTestSpots );
|
||||
WWindowPeer_setCustomDecorationTitleBarHeight.invoke( peer, titleBarHeight );
|
||||
WWindowPeer_setCustomDecorationHitTestSpots.invoke( peer, hitTestSpots );
|
||||
} catch( Exception ex ) {
|
||||
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||
}
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
if( initialized )
|
||||
if( supported != null )
|
||||
return;
|
||||
initialized = true;
|
||||
supported = false;
|
||||
|
||||
// requires JetBrains Runtime 11 and Windows 10
|
||||
if( !SystemInfo.isJetBrainsJVM_11_orLater || !SystemInfo.isWindows_10_orLater )
|
||||
@@ -203,15 +175,17 @@ public class JBRCustomDecorations
|
||||
AWTAccessor_ComponentAccessor_getPeer = compAccessorClass.getDeclaredMethod( "getPeer", Component.class );
|
||||
|
||||
Class<?> peerClass = Class.forName( "sun.awt.windows.WWindowPeer" );
|
||||
WWindowPeer_setCustomDecorationHitTestSpots = peerClass.getDeclaredMethod( "setCustomDecorationHitTestSpots", List.class );
|
||||
WWindowPeer_setCustomDecorationTitleBarHeight = peerClass.getDeclaredMethod( "setCustomDecorationTitleBarHeight", int.class );
|
||||
WWindowPeer_setCustomDecorationHitTestSpots.setAccessible( true );
|
||||
WWindowPeer_setCustomDecorationHitTestSpots = peerClass.getDeclaredMethod( "setCustomDecorationHitTestSpots", List.class );
|
||||
WWindowPeer_setCustomDecorationTitleBarHeight.setAccessible( true );
|
||||
WWindowPeer_setCustomDecorationHitTestSpots.setAccessible( true );
|
||||
|
||||
Window_hasCustomDecoration = Window.class.getDeclaredMethod( "hasCustomDecoration" );
|
||||
Window_setHasCustomDecoration = Window.class.getDeclaredMethod( "setHasCustomDecoration" );
|
||||
Window_hasCustomDecoration.setAccessible( true );
|
||||
Window_setHasCustomDecoration.setAccessible( true );
|
||||
|
||||
supported = true;
|
||||
} catch( Exception ex ) {
|
||||
// ignore
|
||||
}
|
||||
@@ -236,15 +210,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();
|
||||
} );
|
||||
|
||||
@@ -256,46 +237,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" );
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ public class SystemInfo
|
||||
public static final boolean isMacOS_10_14_Mojave_orLater;
|
||||
public static final boolean isMacOS_10_15_Catalina_orLater;
|
||||
|
||||
// OS architecture
|
||||
public static final boolean isX86_64;
|
||||
|
||||
// Java versions
|
||||
public static final long javaVersion;
|
||||
public static final boolean isJava_9_orLater;
|
||||
@@ -65,6 +68,10 @@ public class SystemInfo
|
||||
isMacOS_10_14_Mojave_orLater = (isMacOS && osVersion >= toVersion( 10, 14, 0, 0 ));
|
||||
isMacOS_10_15_Catalina_orLater = (isMacOS && osVersion >= toVersion( 10, 15, 0, 0 ));
|
||||
|
||||
// OS architecture
|
||||
String osArch = System.getProperty( "os.arch" );
|
||||
isX86_64 = osArch.equals( "amd64" ) || osArch.equals( "x86_64" );
|
||||
|
||||
// Java versions
|
||||
javaVersion = scanVersion( System.getProperty( "java.version" ) );
|
||||
isJava_9_orLater = (javaVersion >= toVersion( 9, 0, 0, 0 ));
|
||||
|
||||
@@ -27,6 +27,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation( project( ":flatlaf-core" ) )
|
||||
implementation( project( ":flatlaf-natives-jna" ) )
|
||||
implementation( project( ":flatlaf-extras" ) )
|
||||
implementation( project( ":flatlaf-intellij-themes" ) )
|
||||
implementation( "com.miglayout:miglayout-swing:5.3-SNAPSHOT" )
|
||||
@@ -36,6 +37,7 @@ dependencies {
|
||||
tasks {
|
||||
jar {
|
||||
dependsOn( ":flatlaf-core:jar" )
|
||||
dependsOn( ":flatlaf-natives-jna:jar" )
|
||||
dependsOn( ":flatlaf-extras:jar" )
|
||||
dependsOn( ":flatlaf-intellij-themes:jar" )
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
|
||||
import com.formdev.flatlaf.extras.FlatSVGIcon;
|
||||
import com.formdev.flatlaf.extras.FlatUIDefaultsInspector;
|
||||
import com.formdev.flatlaf.extras.FlatSVGUtils;
|
||||
import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
|
||||
import com.formdev.flatlaf.ui.JBRCustomDecorations;
|
||||
import net.miginfocom.layout.ConstraintParser;
|
||||
import net.miginfocom.layout.LC;
|
||||
@@ -142,11 +143,16 @@ class DemoFrame
|
||||
boolean windowDecorations = windowDecorationsCheckBoxMenuItem.isSelected();
|
||||
|
||||
// change window decoration of demo main frame
|
||||
dispose();
|
||||
setUndecorated( windowDecorations );
|
||||
getRootPane().setWindowDecorationStyle( windowDecorations ? JRootPane.FRAME : JRootPane.NONE );
|
||||
if( FlatNativeWindowBorder.isSupported() ) {
|
||||
FlatNativeWindowBorder.setHasCustomDecoration( this, windowDecorations );
|
||||
getRootPane().setWindowDecorationStyle( windowDecorations ? JRootPane.FRAME : JRootPane.NONE );
|
||||
} else {
|
||||
dispose();
|
||||
setUndecorated( windowDecorations );
|
||||
getRootPane().setWindowDecorationStyle( windowDecorations ? JRootPane.FRAME : JRootPane.NONE );
|
||||
setVisible( true );
|
||||
}
|
||||
menuBarEmbeddedCheckBoxMenuItem.setEnabled( windowDecorations );
|
||||
setVisible( true );
|
||||
|
||||
// enable/disable window decoration for later created frames/dialogs
|
||||
JFrame.setDefaultLookAndFeelDecorated( windowDecorations );
|
||||
@@ -722,7 +728,7 @@ class DemoFrame
|
||||
pasteMenuItem.addActionListener( new DefaultEditorKit.PasteAction() );
|
||||
|
||||
boolean supportsWindowDecorations = UIManager.getLookAndFeel()
|
||||
.getSupportsWindowDecorations() || JBRCustomDecorations.isSupported();
|
||||
.getSupportsWindowDecorations() || FlatNativeWindowBorder.isSupported();
|
||||
windowDecorationsCheckBoxMenuItem.setEnabled( supportsWindowDecorations && !JBRCustomDecorations.isSupported() );
|
||||
menuBarEmbeddedCheckBoxMenuItem.setEnabled( supportsWindowDecorations );
|
||||
|
||||
|
||||
25
flatlaf-natives/flatlaf-natives-jna/build.gradle.kts
Normal file
25
flatlaf-natives/flatlaf-natives-jna/build.gradle.kts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation( project( ":flatlaf-core" ) )
|
||||
implementation( "net.java.dev.jna:jna:5.7.0" )
|
||||
implementation( "net.java.dev.jna:jna-platform:5.7.0" )
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
/*
|
||||
* Copyright 2021 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.formdev.flatlaf.natives.jna.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;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
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;
|
||||
import com.sun.jna.platform.win32.User32;
|
||||
import com.sun.jna.platform.win32.WTypes.LPWSTR;
|
||||
import com.sun.jna.platform.win32.WinDef.HMENU;
|
||||
import com.sun.jna.platform.win32.WinDef.HWND;
|
||||
import com.sun.jna.platform.win32.WinDef.LPARAM;
|
||||
import com.sun.jna.platform.win32.WinDef.LRESULT;
|
||||
import com.sun.jna.platform.win32.WinDef.RECT;
|
||||
import com.sun.jna.platform.win32.WinDef.UINT_PTR;
|
||||
import com.sun.jna.platform.win32.WinDef.WPARAM;
|
||||
import com.sun.jna.platform.win32.WinUser.HMONITOR;
|
||||
import com.sun.jna.platform.win32.WinUser.WindowProc;
|
||||
import com.sun.jna.win32.W32APIOptions;
|
||||
|
||||
//
|
||||
// Interesting resources:
|
||||
// https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp
|
||||
// https://docs.microsoft.com/en-us/windows/win32/dwm/customframe
|
||||
// https://github.com/JetBrains/JetBrainsRuntime/blob/master/src/java.desktop/windows/native/libawt/windows/awt_Frame.cpp
|
||||
// https://github.com/JetBrains/JetBrainsRuntime/commit/d2820524a1aa211b1c49b30f659b9b4d07a6f96e
|
||||
// https://github.com/JetBrains/JetBrainsRuntime/pull/18
|
||||
// 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
|
||||
//
|
||||
|
||||
/**
|
||||
* Native window border support for Windows 10 when using custom decorations.
|
||||
* <p>
|
||||
* If the application wants to use custom decorations, the Windows 10 title bar is hidden
|
||||
* (including minimize, maximize and close buttons), but not the resize borders (including drop shadow).
|
||||
* Windows 10 window snapping functionality will remain unaffected:
|
||||
* https://support.microsoft.com/en-us/windows/snap-your-windows-885a9b1e-a983-a3b1-16cd-c531795e6241
|
||||
*
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class FlatWindowsNativeWindowBorder
|
||||
implements FlatNativeWindowBorder.Provider
|
||||
{
|
||||
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;
|
||||
|
||||
public static FlatNativeWindowBorder.Provider getInstance() {
|
||||
if( instance == null )
|
||||
instance = new FlatWindowsNativeWindowBorder();
|
||||
return instance;
|
||||
}
|
||||
|
||||
private FlatWindowsNativeWindowBorder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomDecoration( Window window ) {
|
||||
return windowsMap.containsKey( window );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the window whether the application wants use custom decorations.
|
||||
* If {@code true}, the Windows 10 title bar is hidden (including minimize,
|
||||
* maximize and close buttons), but not the resize borders (including drop shadow).
|
||||
*/
|
||||
@Override
|
||||
public void setHasCustomDecoration( Window window, boolean hasCustomDecoration ) {
|
||||
if( hasCustomDecoration )
|
||||
install( window );
|
||||
else
|
||||
uninstall( window );
|
||||
}
|
||||
|
||||
private void install( Window window ) {
|
||||
// requires Windows 10 on x86_64
|
||||
if( !SystemInfo.isWindows_10_orLater || !SystemInfo.isX86_64 )
|
||||
return;
|
||||
|
||||
// only JFrame and JDialog are supported
|
||||
if( !(window instanceof JFrame) && !(window instanceof JDialog) )
|
||||
return;
|
||||
|
||||
// not supported if frame/dialog is undecorated
|
||||
if( (window instanceof Frame && ((Frame)window).isUndecorated()) ||
|
||||
(window instanceof Dialog && ((Dialog)window).isUndecorated()) )
|
||||
return;
|
||||
|
||||
// check whether already installed
|
||||
if( windowsMap.containsKey( window ) )
|
||||
return;
|
||||
|
||||
// install
|
||||
WndProc wndProc = new WndProc( window );
|
||||
windowsMap.put( window, wndProc );
|
||||
}
|
||||
|
||||
private void uninstall( Window window ) {
|
||||
WndProc wndProc = windowsMap.remove( window );
|
||||
if( wndProc != null )
|
||||
wndProc.uninstall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitleBarHeight( Window window, int titleBarHeight ) {
|
||||
WndProc wndProc = windowsMap.get( window );
|
||||
if( wndProc == null )
|
||||
return;
|
||||
|
||||
wndProc.titleBarHeight = titleBarHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitleBarHitTestSpots( Window window, List<Rectangle> hitTestSpots ) {
|
||||
WndProc wndProc = windowsMap.get( window );
|
||||
if( wndProc == null )
|
||||
return;
|
||||
|
||||
wndProc.hitTestSpots = hitTestSpots.toArray( new Rectangle[hitTestSpots.size()] );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitleBarAppIconBounds( Window window, Rectangle appIconBounds ) {
|
||||
WndProc wndProc = windowsMap.get( window );
|
||||
if( wndProc == null )
|
||||
return;
|
||||
|
||||
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
|
||||
implements WindowProc
|
||||
{
|
||||
private static final int GWLP_WNDPROC = -4;
|
||||
|
||||
private static final int
|
||||
WM_NCCALCSIZE = 0x0083,
|
||||
WM_NCHITTEST = 0x0084,
|
||||
WM_NCRBUTTONUP = 0x00A5,
|
||||
WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320;
|
||||
|
||||
// WM_NCHITTEST mouse position codes
|
||||
private static final int
|
||||
HTCLIENT = 1,
|
||||
HTCAPTION = 2,
|
||||
HTSYSMENU = 3,
|
||||
HTTOP = 12;
|
||||
|
||||
private static final int ABS_AUTOHIDE = 0x0000001;
|
||||
private static final int ABM_GETAUTOHIDEBAREX = 0x0000000b;
|
||||
|
||||
private static final int
|
||||
SC_SIZE = 0xF000,
|
||||
SC_MOVE = 0xF010,
|
||||
SC_MINIMIZE = 0xF020,
|
||||
SC_MAXIMIZE = 0xF030,
|
||||
SC_CLOSE = 0xF060,
|
||||
SC_RESTORE = 0xF120;
|
||||
|
||||
private static final int
|
||||
MIIM_STATE = 0x00000001,
|
||||
MFT_STRING = 0x00000000,
|
||||
MF_ENABLED = 0x00000000,
|
||||
MF_DISABLED = 0x00000002,
|
||||
TPM_RETURNCMD = 0x0100;
|
||||
|
||||
private Window window;
|
||||
private final HWND hwnd;
|
||||
private final BaseTSD.LONG_PTR defaultWndProc;
|
||||
|
||||
private int titleBarHeight;
|
||||
private Rectangle[] hitTestSpots;
|
||||
private Rectangle appIconBounds;
|
||||
|
||||
WndProc( Window window ) {
|
||||
this.window = window;
|
||||
|
||||
// get window handle
|
||||
hwnd = new HWND( Native.getComponentPointer( window ) );
|
||||
|
||||
// replace window procedure
|
||||
defaultWndProc = User32Ex.INSTANCE.SetWindowLongPtr( hwnd, GWLP_WNDPROC, this );
|
||||
|
||||
// remove the OS window title bar
|
||||
updateFrame();
|
||||
}
|
||||
|
||||
void uninstall() {
|
||||
// restore original window procedure
|
||||
User32Ex.INSTANCE.SetWindowLongPtr( hwnd, GWLP_WNDPROC, defaultWndProc );
|
||||
|
||||
// show the OS window title bar
|
||||
updateFrame();
|
||||
|
||||
// cleanup
|
||||
window = null;
|
||||
}
|
||||
|
||||
private void updateFrame() {
|
||||
// this sends WM_NCCALCSIZE and removes/shows the window title bar
|
||||
User32.INSTANCE.SetWindowPos( hwnd, hwnd, 0, 0, 0, 0,
|
||||
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER );
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This method is invoked on the AWT-Windows thread (not the AWT-EventQueue thread).
|
||||
*/
|
||||
@Override
|
||||
public LRESULT callback( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) {
|
||||
switch( uMsg ) {
|
||||
case WM_NCCALCSIZE:
|
||||
return WmNcCalcSize( hwnd, uMsg, wParam, lParam );
|
||||
|
||||
case WM_NCHITTEST:
|
||||
return WmNcHitTest( hwnd, uMsg, wParam, lParam );
|
||||
|
||||
case WM_NCRBUTTONUP:
|
||||
if( wParam.longValue() == HTCAPTION || wParam.longValue() == HTSYSMENU )
|
||||
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 );
|
||||
}
|
||||
|
||||
return User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WM_DESTROY
|
||||
*
|
||||
* https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-destroy
|
||||
*/
|
||||
private LRESULT WmDestroy( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) {
|
||||
// call original AWT window procedure because it may fire window closed event in AwtWindow::WmDestroy()
|
||||
LRESULT lResult = User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
|
||||
|
||||
// restore original window procedure
|
||||
User32Ex.INSTANCE.SetWindowLongPtr( hwnd, GWLP_WNDPROC, defaultWndProc );
|
||||
|
||||
// cleanup
|
||||
windowsMap.remove( window );
|
||||
window = null;
|
||||
|
||||
return lResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WM_NCCALCSIZE
|
||||
*
|
||||
* https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
|
||||
*
|
||||
* See also NonClientIslandWindow::_OnNcCalcSize() here:
|
||||
* https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp
|
||||
*/
|
||||
private LRESULT WmNcCalcSize( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) {
|
||||
if( wParam.intValue() != 1 )
|
||||
return User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
|
||||
|
||||
NCCALCSIZE_PARAMS params = new NCCALCSIZE_PARAMS( new Pointer( lParam.longValue() ) );
|
||||
|
||||
// store the original top before the default window proc applies the default frame
|
||||
int originalTop = params.rgrc[0].top;
|
||||
|
||||
// apply the default frame
|
||||
LRESULT lResult = User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
|
||||
if( lResult.longValue() != 0 )
|
||||
return lResult;
|
||||
|
||||
// re-read params from native memory because defaultWndProc changed it
|
||||
params.read();
|
||||
|
||||
// re-apply the original top from before the size of the default frame was applied
|
||||
params.rgrc[0].top = originalTop;
|
||||
|
||||
boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd );
|
||||
if( isMaximized && !isFullscreen() ) {
|
||||
// When a window is maximized, its size is actually a little bit more
|
||||
// than the monitor's work area. The window is positioned and sized in
|
||||
// such a way that the resize handles are outside of the monitor and
|
||||
// then the window is clipped to the monitor so that the resize handle
|
||||
// do not appear because you don't need them (because you can't resize
|
||||
// a window when it's maximized unless you restore it).
|
||||
params.rgrc[0].top += getResizeHandleHeight();
|
||||
|
||||
// check whether taskbar is in the autohide state
|
||||
APPBARDATA autohide = new APPBARDATA();
|
||||
autohide.cbSize = new DWORD( autohide.size() );
|
||||
int state = Shell32.INSTANCE.SHAppBarMessage( new DWORD( ABM_GETSTATE ), autohide ).intValue();
|
||||
if( (state & ABS_AUTOHIDE) != 0 ) {
|
||||
// get monitor info
|
||||
// (using MONITOR_DEFAULTTONEAREST finds right monitor when restoring from minimized)
|
||||
HMONITOR hMonitor = User32.INSTANCE.MonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST );
|
||||
MONITORINFO monitorInfo = new MONITORINFO();
|
||||
User32.INSTANCE.GetMonitorInfo( hMonitor, monitorInfo );
|
||||
|
||||
// If there's a taskbar on any side of the monitor, reduce our size
|
||||
// a little bit on that edge.
|
||||
if( hasAutohideTaskbar( ABE_TOP, monitorInfo.rcMonitor ) )
|
||||
params.rgrc[0].top++;
|
||||
if( hasAutohideTaskbar( ABE_BOTTOM, monitorInfo.rcMonitor ) )
|
||||
params.rgrc[0].bottom--;
|
||||
if( hasAutohideTaskbar( ABE_LEFT, monitorInfo.rcMonitor ) )
|
||||
params.rgrc[0].left++;
|
||||
if( hasAutohideTaskbar( ABE_RIGHT, monitorInfo.rcMonitor ) )
|
||||
params.rgrc[0].right--;
|
||||
}
|
||||
}
|
||||
|
||||
// write changed params back to native memory
|
||||
params.write();
|
||||
|
||||
return lResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WM_NCHITTEST
|
||||
*
|
||||
* https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest
|
||||
*
|
||||
* See also NonClientIslandWindow::_OnNcHitTest() here:
|
||||
* https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp
|
||||
*/
|
||||
private LRESULT WmNcHitTest( HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam ) {
|
||||
// this will handle the left, right and bottom parts of the frame because we didn't change them
|
||||
LRESULT lResult = User32Ex.INSTANCE.CallWindowProc( defaultWndProc, hwnd, uMsg, wParam, lParam );
|
||||
if( lResult.longValue() != HTCLIENT )
|
||||
return lResult;
|
||||
|
||||
// get window rectangle needed to convert mouse x/y from screen to window coordinates
|
||||
RECT rcWindow = new RECT();
|
||||
User32.INSTANCE.GetWindowRect( hwnd, rcWindow );
|
||||
|
||||
// get mouse x/y in window coordinates
|
||||
int x = GET_X_LPARAM( lParam ) - rcWindow.left;
|
||||
int y = GET_Y_LPARAM( lParam ) - rcWindow.top;
|
||||
|
||||
// scale-down mouse x/y
|
||||
Point pt = scaleDown( x, y );
|
||||
int sx = pt.x;
|
||||
int sy = pt.y;
|
||||
|
||||
// return HTSYSMENU if mouse is over application icon
|
||||
// - left-click on HTSYSMENU area shows system menu
|
||||
// - double-left-click sends WM_CLOSE
|
||||
if( appIconBounds != null && appIconBounds.contains( sx, sy ) )
|
||||
return new LRESULT( HTSYSMENU );
|
||||
|
||||
int resizeBorderHeight = getResizeHandleHeight();
|
||||
boolean isOnResizeBorder = (y < resizeBorderHeight) &&
|
||||
(User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE ) & WS_THICKFRAME) != 0;
|
||||
boolean isOnTitleBar = (sy < titleBarHeight);
|
||||
|
||||
if( isOnTitleBar ) {
|
||||
// use a second reference to the array to avoid that it can be changed
|
||||
// in another thread while processing the array
|
||||
Rectangle[] hitTestSpots2 = hitTestSpots;
|
||||
for( Rectangle spot : hitTestSpots2 ) {
|
||||
if( spot.contains( sx, sy ) )
|
||||
return new LRESULT( HTCLIENT );
|
||||
}
|
||||
return new LRESULT( isOnResizeBorder ? HTTOP : HTCAPTION );
|
||||
}
|
||||
|
||||
return new LRESULT( isOnResizeBorder ? HTTOP : HTCLIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the little space at the top of the window used to
|
||||
* resize the window.
|
||||
*
|
||||
* See also NonClientIslandWindow::_GetResizeHandleHeight() here:
|
||||
* https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp
|
||||
*/
|
||||
private int getResizeHandleHeight() {
|
||||
int dpi = User32Ex.INSTANCE.GetDpiForWindow( hwnd );
|
||||
|
||||
// there isn't a SM_CYPADDEDBORDER for the Y axis
|
||||
return User32Ex.INSTANCE.GetSystemMetricsForDpi( SM_CXPADDEDBORDER, dpi )
|
||||
+ User32Ex.INSTANCE.GetSystemMetricsForDpi( SM_CYSIZEFRAME, dpi );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is an autohide taskbar on the given edge.
|
||||
*/
|
||||
private boolean hasAutohideTaskbar( int edge, RECT rcMonitor ) {
|
||||
APPBARDATA data = new APPBARDATA();
|
||||
data.cbSize = new DWORD( data.size() );
|
||||
data.uEdge = new UINT( edge );
|
||||
data.rc = rcMonitor;
|
||||
UINT_PTR hTaskbar = Shell32.INSTANCE.SHAppBarMessage( new DWORD( ABM_GETAUTOHIDEBAREX ), data );
|
||||
return hTaskbar.longValue() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales down in the same way as AWT.
|
||||
* See AwtWin32GraphicsDevice::ScaleDownX() and ::ScaleDownY()
|
||||
*/
|
||||
private Point scaleDown( int x, int y ) {
|
||||
GraphicsConfiguration gc = window.getGraphicsConfiguration();
|
||||
if( gc == null )
|
||||
return new Point( x, y );
|
||||
|
||||
AffineTransform t = gc.getDefaultTransform();
|
||||
return new Point( clipRound( x / t.getScaleX() ), clipRound( y / t.getScaleY() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds in the same way as AWT.
|
||||
* See AwtWin32GraphicsDevice::ClipRound()
|
||||
*/
|
||||
private int clipRound( double value ) {
|
||||
value -= 0.5;
|
||||
if( value < Integer.MIN_VALUE )
|
||||
return Integer.MIN_VALUE;
|
||||
if( value > Integer.MAX_VALUE )
|
||||
return Integer.MAX_VALUE;
|
||||
return (int) Math.ceil( value );
|
||||
}
|
||||
|
||||
private boolean isFullscreen() {
|
||||
GraphicsConfiguration gc = window.getGraphicsConfiguration();
|
||||
if( gc == null )
|
||||
return false;
|
||||
return gc.getDevice().getFullScreenWindow() == window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same implementation as GET_X_LPARAM(lp) macro in windowsx.h.
|
||||
* X-coordinate is in the low-order short and may be negative.
|
||||
*
|
||||
* https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest#remarks
|
||||
*/
|
||||
private int GET_X_LPARAM( LPARAM lParam ) {
|
||||
return (short) (lParam.longValue() & 0xffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same implementation as GET_Y_LPARAM(lp) macro in windowsx.h.
|
||||
* Y-coordinate is in the high-order short and may be negative.
|
||||
*
|
||||
* https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest#remarks
|
||||
*/
|
||||
private int GET_Y_LPARAM( LPARAM lParam ) {
|
||||
return (short) ((lParam.longValue() >> 16) & 0xffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the window's system menu.
|
||||
* The system menu is the menu that opens when the user presses Alt+Space or
|
||||
* right clicks on the title bar
|
||||
*/
|
||||
private void openSystemMenu( HWND hwnd, int x, int y ) {
|
||||
// get system menu
|
||||
HMENU systemMenu = User32Ex.INSTANCE.GetSystemMenu( hwnd, false );
|
||||
|
||||
// update system menu
|
||||
int style = User32.INSTANCE.GetWindowLong( hwnd, GWL_STYLE );
|
||||
boolean isMaximized = User32Ex.INSTANCE.IsZoomed( hwnd );
|
||||
setMenuItemState( systemMenu, SC_RESTORE, isMaximized );
|
||||
setMenuItemState( systemMenu, SC_MOVE, !isMaximized );
|
||||
setMenuItemState( systemMenu, SC_SIZE, (style & WS_THICKFRAME) != 0 && !isMaximized );
|
||||
setMenuItemState( systemMenu, SC_MINIMIZE, (style & WS_MINIMIZEBOX) != 0 );
|
||||
setMenuItemState( systemMenu, SC_MAXIMIZE, (style & WS_MAXIMIZEBOX) != 0 && !isMaximized );
|
||||
setMenuItemState( systemMenu, SC_CLOSE, true );
|
||||
|
||||
// make "Close" item the default to be consistent with the system menu shown
|
||||
// when pressing Alt+Space
|
||||
User32Ex.INSTANCE.SetMenuDefaultItem( systemMenu, SC_CLOSE, 0 );
|
||||
|
||||
// show system menu
|
||||
int ret = User32Ex.INSTANCE.TrackPopupMenu( systemMenu, TPM_RETURNCMD,
|
||||
x, y, 0, hwnd, null ).intValue();
|
||||
if( ret != 0 )
|
||||
User32Ex.INSTANCE.PostMessage( hwnd, WM_SYSCOMMAND, new WPARAM( ret ), null );
|
||||
}
|
||||
|
||||
private void setMenuItemState( HMENU systemMenu, int item, boolean enabled ) {
|
||||
MENUITEMINFO mii = new MENUITEMINFO();
|
||||
mii.cbSize = new UINT( mii.size() );
|
||||
mii.fMask = new UINT( MIIM_STATE );
|
||||
mii.fType = new UINT( MFT_STRING );
|
||||
mii.fState = new UINT( enabled ? MF_ENABLED : MF_DISABLED );
|
||||
User32Ex.INSTANCE.SetMenuItemInfo( systemMenu, item, false, mii );
|
||||
}
|
||||
}
|
||||
|
||||
//---- interface User32Ex -------------------------------------------------
|
||||
|
||||
private interface User32Ex
|
||||
extends User32
|
||||
{
|
||||
User32Ex INSTANCE = Native.load( "user32", User32Ex.class, W32APIOptions.DEFAULT_OPTIONS );
|
||||
|
||||
LONG_PTR SetWindowLongPtr( HWND hWnd, int nIndex, WindowProc wndProc );
|
||||
LONG_PTR SetWindowLongPtr( HWND hWnd, int nIndex, LONG_PTR wndProc );
|
||||
LRESULT CallWindowProc( LONG_PTR lpPrevWndFunc, HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam );
|
||||
|
||||
int GetDpiForWindow( HWND hwnd );
|
||||
int GetSystemMetricsForDpi( int nIndex, int dpi );
|
||||
|
||||
boolean IsZoomed( HWND hWnd );
|
||||
HANDLE GetProp( HWND hWnd, String lpString );
|
||||
|
||||
HMENU GetSystemMenu( HWND hWnd, boolean bRevert );
|
||||
boolean SetMenuItemInfo( HMENU hmenu, int item, boolean fByPositon, MENUITEMINFO lpmii );
|
||||
boolean SetMenuDefaultItem( HMENU hMenu, int uItem, int fByPos );
|
||||
BOOL TrackPopupMenu( HMENU hMenu, int uFlags, int x, int y, int nReserved, HWND hWnd, RECT prcRect );
|
||||
}
|
||||
|
||||
//---- class NCCALCSIZE_PARAMS --------------------------------------------
|
||||
|
||||
@FieldOrder( { "rgrc" } )
|
||||
public static class NCCALCSIZE_PARAMS
|
||||
extends Structure
|
||||
{
|
||||
// real structure contains 3 rectangles, but only first one is needed here
|
||||
public RECT[] rgrc = new RECT[1];
|
||||
// public WINDOWPOS lppos;
|
||||
|
||||
public NCCALCSIZE_PARAMS( Pointer pointer ) {
|
||||
super( pointer );
|
||||
read();
|
||||
}
|
||||
}
|
||||
|
||||
//---- class MENUITEMINFO -------------------------------------------------
|
||||
|
||||
@FieldOrder( { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu",
|
||||
"hbmpChecked", "hbmpUnchecked", "dwItemData", "dwTypeData", "cch", "hbmpItem" } )
|
||||
public static class MENUITEMINFO
|
||||
extends Structure
|
||||
{
|
||||
public UINT cbSize;
|
||||
public UINT fMask;
|
||||
public UINT fType;
|
||||
public UINT fState;
|
||||
public UINT wID;
|
||||
public HMENU hSubMenu;
|
||||
public HBITMAP hbmpChecked;
|
||||
public HBITMAP hbmpUnchecked;
|
||||
public ULONG_PTR dwItemData;
|
||||
public LPWSTR dwTypeData;
|
||||
public UINT cch;
|
||||
public HBITMAP hbmpItem;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation( project( ":flatlaf-core" ) )
|
||||
implementation( project( ":flatlaf-natives-jna" ) )
|
||||
implementation( project( ":flatlaf-extras" ) )
|
||||
implementation( project( ":flatlaf-swingx" ) )
|
||||
implementation( project( ":flatlaf-jide-oss" ) )
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright 2021 FormDev Software GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.formdev.flatlaf.testing;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Dialog.ModalityType;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.util.WeakHashMap;
|
||||
import javax.swing.*;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import com.formdev.flatlaf.extras.FlatInspector;
|
||||
import com.formdev.flatlaf.ui.FlatLineBorder;
|
||||
import com.formdev.flatlaf.ui.FlatNativeWindowBorder;
|
||||
import net.miginfocom.swing.*;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
public class FlatNativeWindowBorderTest
|
||||
extends JPanel
|
||||
{
|
||||
private static JFrame mainFrame;
|
||||
private static WeakHashMap<Window, Object> hiddenWindowsMap = new WeakHashMap<>();
|
||||
private static int nextWindowId = 1;
|
||||
|
||||
private final Window window;
|
||||
private final int windowId;
|
||||
|
||||
public static void main( String[] args ) {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
FlatLightLaf.install();
|
||||
FlatInspector.install( "ctrl shift alt X" );
|
||||
|
||||
JFrame.setDefaultLookAndFeelDecorated( true );
|
||||
JDialog.setDefaultLookAndFeelDecorated( true );
|
||||
|
||||
mainFrame = showFrame();
|
||||
} );
|
||||
}
|
||||
|
||||
private static JFrame showFrame() {
|
||||
JFrame frame = new MyJFrame( "FlatNativeWindowBorderTest" );
|
||||
frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
|
||||
frame.add( new FlatNativeWindowBorderTest( frame ) );
|
||||
|
||||
((JComponent) frame.getContentPane()).registerKeyboardAction( e -> {
|
||||
frame.dispose();
|
||||
}, KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0, false ), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
|
||||
|
||||
frame.pack();
|
||||
frame.setLocationRelativeTo( null );
|
||||
int offset = 20 * Window.getWindows().length;
|
||||
frame.setLocation( frame.getX() + offset, frame.getY() + offset );
|
||||
frame.setVisible( true );
|
||||
return frame;
|
||||
}
|
||||
|
||||
private static void showDialog( Window owner ) {
|
||||
JDialog dialog = new MyJDialog( owner, "FlatNativeWindowBorderTest Dialog", ModalityType.DOCUMENT_MODAL );
|
||||
dialog.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
|
||||
dialog.add( new FlatNativeWindowBorderTest( dialog ) );
|
||||
|
||||
((JComponent) dialog.getContentPane()).registerKeyboardAction( e -> {
|
||||
dialog.dispose();
|
||||
}, KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0, false ), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
|
||||
|
||||
dialog.pack();
|
||||
dialog.setLocationRelativeTo( owner );
|
||||
dialog.setLocation( dialog.getX() + 20, dialog.getY() + 20 );
|
||||
dialog.setVisible( true );
|
||||
}
|
||||
|
||||
private FlatNativeWindowBorderTest( Window window ) {
|
||||
this.window = window;
|
||||
this.windowId = nextWindowId++;
|
||||
|
||||
initComponents();
|
||||
|
||||
if( mainFrame == null )
|
||||
hideWindowButton.setEnabled( false );
|
||||
|
||||
setBorder( new FlatLineBorder( new Insets( 0, 0, 0, 0 ), Color.red ) );
|
||||
setPreferredSize( new Dimension( 800, 600 ) );
|
||||
|
||||
updateInfo();
|
||||
|
||||
ComponentListener componentListener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentMoved( ComponentEvent e ) {
|
||||
updateInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentResized( ComponentEvent e ) {
|
||||
updateInfo();
|
||||
}
|
||||
};
|
||||
window.addComponentListener( componentListener );
|
||||
addComponentListener( componentListener );
|
||||
|
||||
window.addWindowListener( new WindowListener() {
|
||||
@Override
|
||||
public void windowOpened( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowOpened" );
|
||||
}
|
||||
@Override
|
||||
public void windowClosing( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowClosing" );
|
||||
}
|
||||
@Override
|
||||
public void windowClosed( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowClosed" );
|
||||
}
|
||||
@Override
|
||||
public void windowIconified( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowIconified" );
|
||||
}
|
||||
@Override
|
||||
public void windowDeiconified( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowDeiconified" );
|
||||
}
|
||||
@Override
|
||||
public void windowActivated( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowActivated" );
|
||||
}
|
||||
@Override
|
||||
public void windowDeactivated( WindowEvent e ) {
|
||||
System.out.println( windowId + " windowDeactivated" );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private void updateInfo() {
|
||||
Toolkit toolkit = Toolkit.getDefaultToolkit();
|
||||
GraphicsConfiguration gc = window.getGraphicsConfiguration();
|
||||
DisplayMode dm = gc.getDevice().getDisplayMode();
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
Rectangle windowBounds = window.getBounds();
|
||||
Rectangle clientBounds = new Rectangle( isShowing() ? getLocationOnScreen() : getLocation(), getSize() );
|
||||
|
||||
StringBuilder buf = new StringBuilder( 1500 );
|
||||
buf.append( "<html><style>" );
|
||||
buf.append( "td { padding: 0 10 0 0; }" );
|
||||
buf.append( "</style><table>" );
|
||||
|
||||
appendRow( buf, "Window bounds", toString( windowBounds ) );
|
||||
appendRow( buf, "Client bounds", toString( clientBounds ) );
|
||||
appendRow( buf, "Window / Panel gap", toString( diff( windowBounds, clientBounds ) ) );
|
||||
if( window instanceof Frame && (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0 )
|
||||
appendRow( buf, "Screen / Window gap", toString( diff( screenBounds, windowBounds ) ) );
|
||||
|
||||
appendEmptyRow( buf );
|
||||
|
||||
if( window instanceof Frame ) {
|
||||
Rectangle maximizedBounds = ((Frame)window).getMaximizedBounds();
|
||||
if( maximizedBounds != null ) {
|
||||
appendRow( buf, "Maximized bounds", toString( maximizedBounds ) );
|
||||
appendEmptyRow( buf );
|
||||
}
|
||||
}
|
||||
|
||||
appendRow( buf, "Physical screen size", dm.getWidth() + ", " + dm.getHeight() + " (" + dm.getBitDepth() + " Bit)" );
|
||||
appendRow( buf, "Screen bounds", toString( screenBounds ) );
|
||||
appendRow( buf, "Screen insets", toString( toolkit.getScreenInsets( gc ) ) );
|
||||
appendRow( buf, "Scale factor", (int) (gc.getDefaultTransform().getScaleX() * 100) + "%" );
|
||||
|
||||
appendEmptyRow( buf );
|
||||
|
||||
appendRow( buf, "Java version", System.getProperty( "java.version" ) + " / " + System.getProperty( "java.vendor" ) );
|
||||
|
||||
buf.append( "</td></tr>" );
|
||||
buf.append( "</table></html>" );
|
||||
|
||||
info.setText( buf.toString() );
|
||||
}
|
||||
|
||||
private static void appendRow( StringBuilder buf, String key, String value ) {
|
||||
buf.append( "<tr><td>" )
|
||||
.append( key )
|
||||
.append( ":</td><td>" )
|
||||
.append( value )
|
||||
.append( "</td></tr>" );
|
||||
}
|
||||
|
||||
private static void appendEmptyRow( StringBuilder buf ) {
|
||||
buf.append( "<tr><td></td><td></td></tr>" );
|
||||
}
|
||||
|
||||
private static String toString( Rectangle r ) {
|
||||
if( r == null )
|
||||
return "null";
|
||||
return r.x + ", " + r.y + ", " + r.width + ", " + r.height;
|
||||
}
|
||||
|
||||
private static String toString( Insets insets ) {
|
||||
return insets.top + ", " + insets.left + ", " + insets.bottom + ", " + insets.right;
|
||||
}
|
||||
|
||||
private static Rectangle diff( Rectangle r1, Rectangle r2 ) {
|
||||
return new Rectangle(
|
||||
r2.x - r1.x,
|
||||
r2.y - r1.y,
|
||||
(r1.x + r1.width) - (r2.x + r2.width),
|
||||
(r1.y + r1.height) - (r2.y + r2.height) );
|
||||
}
|
||||
|
||||
private void resizableChanged() {
|
||||
if( window instanceof Frame )
|
||||
((Frame)window).setResizable( resizableCheckBox.isSelected() );
|
||||
else if( window instanceof Dialog )
|
||||
((Dialog)window).setResizable( resizableCheckBox.isSelected() );
|
||||
}
|
||||
|
||||
private void undecoratedChanged() {
|
||||
window.dispose();
|
||||
|
||||
if( window instanceof Frame )
|
||||
((Frame)window).setUndecorated( undecoratedCheckBox.isSelected() );
|
||||
else if( window instanceof Dialog )
|
||||
((Dialog)window).setUndecorated( undecoratedCheckBox.isSelected() );
|
||||
|
||||
window.setVisible( true );
|
||||
}
|
||||
|
||||
private void maximizedBoundsChanged() {
|
||||
if( window instanceof Frame ) {
|
||||
((Frame)window).setMaximizedBounds( maximizedBoundsCheckBox.isSelected()
|
||||
? new Rectangle( 50, 100, 1000, 700 )
|
||||
: null );
|
||||
updateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void fullScreenChanged() {
|
||||
boolean fullScreen = fullScreenCheckBox.isSelected();
|
||||
|
||||
GraphicsDevice gd = getGraphicsConfiguration().getDevice();
|
||||
gd.setFullScreenWindow( fullScreen ? window : null );
|
||||
}
|
||||
|
||||
private void nativeChanged() {
|
||||
FlatNativeWindowBorder.setHasCustomDecoration( window, nativeCheckBox.isSelected() );
|
||||
}
|
||||
|
||||
private void revalidateLayout() {
|
||||
window.revalidate();
|
||||
}
|
||||
|
||||
private void replaceRootPane() {
|
||||
JRootPane rootPane = new JRootPane();
|
||||
if( window instanceof RootPaneContainer )
|
||||
rootPane.setWindowDecorationStyle( ((RootPaneContainer)window).getRootPane().getWindowDecorationStyle() );
|
||||
rootPane.getContentPane().add( new FlatNativeWindowBorderTest( window ) );
|
||||
|
||||
if( window instanceof MyJFrame )
|
||||
((MyJFrame)window).setRootPane( rootPane );
|
||||
else if( window instanceof MyJDialog )
|
||||
((MyJDialog)window).setRootPane( rootPane );
|
||||
|
||||
window.revalidate();
|
||||
window.repaint();
|
||||
}
|
||||
|
||||
private void openDialog() {
|
||||
showDialog( window );
|
||||
}
|
||||
|
||||
private void openFrame() {
|
||||
showFrame();
|
||||
}
|
||||
|
||||
private void hideWindow() {
|
||||
window.setVisible( false );
|
||||
hiddenWindowsMap.put( window, null );
|
||||
}
|
||||
|
||||
private void showHiddenWindow() {
|
||||
for( Window w : hiddenWindowsMap.keySet() ) {
|
||||
hiddenWindowsMap.remove( w );
|
||||
w.setVisible( true );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
window.dispose();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||
info = new JLabel();
|
||||
resizableCheckBox = new JCheckBox();
|
||||
maximizedBoundsCheckBox = new JCheckBox();
|
||||
undecoratedCheckBox = new JCheckBox();
|
||||
fullScreenCheckBox = new JCheckBox();
|
||||
nativeCheckBox = new JCheckBox();
|
||||
revalidateButton = new JButton();
|
||||
replaceRootPaneButton = new JButton();
|
||||
openDialogButton = new JButton();
|
||||
openFrameButton = new JButton();
|
||||
hideWindowButton = new JButton();
|
||||
showHiddenWindowButton = new JButton();
|
||||
hSpacer1 = new JPanel(null);
|
||||
closeButton = new JButton();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new MigLayout(
|
||||
"insets dialog,hidemode 3",
|
||||
// columns
|
||||
"[]" +
|
||||
"[]" +
|
||||
"[grow,fill]",
|
||||
// rows
|
||||
"[grow,top]para" +
|
||||
"[]0" +
|
||||
"[]0" +
|
||||
"[]" +
|
||||
"[]"));
|
||||
|
||||
//---- info ----
|
||||
info.setText("text");
|
||||
add(info, "cell 0 0 2 1");
|
||||
|
||||
//---- resizableCheckBox ----
|
||||
resizableCheckBox.setText("resizable");
|
||||
resizableCheckBox.setSelected(true);
|
||||
resizableCheckBox.setMnemonic('R');
|
||||
resizableCheckBox.addActionListener(e -> resizableChanged());
|
||||
add(resizableCheckBox, "cell 0 1");
|
||||
|
||||
//---- maximizedBoundsCheckBox ----
|
||||
maximizedBoundsCheckBox.setText("maximized bounds (50,100, 1000,700)");
|
||||
maximizedBoundsCheckBox.setMnemonic('M');
|
||||
maximizedBoundsCheckBox.addActionListener(e -> maximizedBoundsChanged());
|
||||
add(maximizedBoundsCheckBox, "cell 1 1");
|
||||
|
||||
//---- undecoratedCheckBox ----
|
||||
undecoratedCheckBox.setText("undecorated");
|
||||
undecoratedCheckBox.setMnemonic('U');
|
||||
undecoratedCheckBox.addActionListener(e -> undecoratedChanged());
|
||||
add(undecoratedCheckBox, "cell 0 2");
|
||||
|
||||
//---- fullScreenCheckBox ----
|
||||
fullScreenCheckBox.setText("full screen");
|
||||
fullScreenCheckBox.setMnemonic('F');
|
||||
fullScreenCheckBox.addActionListener(e -> fullScreenChanged());
|
||||
add(fullScreenCheckBox, "cell 1 2");
|
||||
|
||||
//---- nativeCheckBox ----
|
||||
nativeCheckBox.setText("FlatLaf native window decorations");
|
||||
nativeCheckBox.setSelected(true);
|
||||
nativeCheckBox.addActionListener(e -> nativeChanged());
|
||||
add(nativeCheckBox, "cell 0 3 3 1");
|
||||
|
||||
//---- revalidateButton ----
|
||||
revalidateButton.setText("revalidate");
|
||||
revalidateButton.addActionListener(e -> revalidateLayout());
|
||||
add(revalidateButton, "cell 0 3 3 1");
|
||||
|
||||
//---- replaceRootPaneButton ----
|
||||
replaceRootPaneButton.setText("replace rootpane");
|
||||
replaceRootPaneButton.addActionListener(e -> replaceRootPane());
|
||||
add(replaceRootPaneButton, "cell 0 3 3 1");
|
||||
|
||||
//---- openDialogButton ----
|
||||
openDialogButton.setText("Open Dialog");
|
||||
openDialogButton.setMnemonic('D');
|
||||
openDialogButton.addActionListener(e -> openDialog());
|
||||
add(openDialogButton, "cell 0 4 3 1");
|
||||
|
||||
//---- openFrameButton ----
|
||||
openFrameButton.setText("Open Frame");
|
||||
openFrameButton.setMnemonic('A');
|
||||
openFrameButton.addActionListener(e -> openFrame());
|
||||
add(openFrameButton, "cell 0 4 3 1");
|
||||
|
||||
//---- hideWindowButton ----
|
||||
hideWindowButton.setText("Hide");
|
||||
hideWindowButton.addActionListener(e -> hideWindow());
|
||||
add(hideWindowButton, "cell 0 4 3 1");
|
||||
|
||||
//---- showHiddenWindowButton ----
|
||||
showHiddenWindowButton.setText("Show hidden");
|
||||
showHiddenWindowButton.addActionListener(e -> showHiddenWindow());
|
||||
add(showHiddenWindowButton, "cell 0 4 3 1");
|
||||
add(hSpacer1, "cell 0 4 3 1,growx");
|
||||
|
||||
//---- closeButton ----
|
||||
closeButton.setText("Close");
|
||||
closeButton.addActionListener(e -> close());
|
||||
add(closeButton, "cell 0 4 3 1");
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||
private JLabel info;
|
||||
private JCheckBox resizableCheckBox;
|
||||
private JCheckBox maximizedBoundsCheckBox;
|
||||
private JCheckBox undecoratedCheckBox;
|
||||
private JCheckBox fullScreenCheckBox;
|
||||
private JCheckBox nativeCheckBox;
|
||||
private JButton revalidateButton;
|
||||
private JButton replaceRootPaneButton;
|
||||
private JButton openDialogButton;
|
||||
private JButton openFrameButton;
|
||||
private JButton hideWindowButton;
|
||||
private JButton showHiddenWindowButton;
|
||||
private JPanel hSpacer1;
|
||||
private JButton closeButton;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
|
||||
//---- class MyJFrame -----------------------------------------------------
|
||||
|
||||
private static class MyJFrame
|
||||
extends JFrame
|
||||
{
|
||||
MyJFrame( String title ) {
|
||||
super( title );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootPane( JRootPane root ) {
|
||||
super.setRootPane( root );
|
||||
}
|
||||
}
|
||||
|
||||
//---- class MyJDialog ----------------------------------------------------
|
||||
|
||||
private static class MyJDialog
|
||||
extends JDialog
|
||||
{
|
||||
MyJDialog( Window owner, String title, Dialog.ModalityType modalityType ) {
|
||||
super( owner, title, modalityType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootPane( JRootPane root ) {
|
||||
super.setRootPane( root );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
JFDML JFormDesigner: "7.0.3.1.342" Java: "15" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
root: new FormRoot {
|
||||
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||
"$layoutConstraints": "insets dialog,hidemode 3"
|
||||
"$columnConstraints": "[][][grow,fill]"
|
||||
"$rowConstraints": "[grow,top]para[]0[]0[][]"
|
||||
} ) {
|
||||
name: "this"
|
||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||
name: "info"
|
||||
"text": "text"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 0 2 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "resizableCheckBox"
|
||||
"text": "resizable"
|
||||
"selected": true
|
||||
"mnemonic": 82
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "resizableChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "maximizedBoundsCheckBox"
|
||||
"text": "maximized bounds (50,100, 1000,700)"
|
||||
"mnemonic": 77
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "maximizedBoundsChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "undecoratedCheckBox"
|
||||
"text": "undecorated"
|
||||
"mnemonic": 85
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "undecoratedChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 2"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "fullScreenCheckBox"
|
||||
"text": "full screen"
|
||||
"mnemonic": 70
|
||||
auxiliary() {
|
||||
"JavaCodeGenerator.variableLocal": false
|
||||
}
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "fullScreenChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 1 2"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||
name: "nativeCheckBox"
|
||||
"text": "FlatLaf native window decorations"
|
||||
"selected": true
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "nativeChanged", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 3 3 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "revalidateButton"
|
||||
"text": "revalidate"
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "revalidateLayout", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 3 3 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "replaceRootPaneButton"
|
||||
"text": "replace rootpane"
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "replaceRootPane", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 3 3 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "openDialogButton"
|
||||
"text": "Open Dialog"
|
||||
"mnemonic": 68
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDialog", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 3 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "openFrameButton"
|
||||
"text": "Open Frame"
|
||||
"mnemonic": 65
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openFrame", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 3 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "hideWindowButton"
|
||||
"text": "Hide"
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "hideWindow", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 3 1"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "showHiddenWindowButton"
|
||||
"text": "Show hidden"
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showHiddenWindow", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 3 1"
|
||||
} )
|
||||
add( new FormComponent( "com.jformdesigner.designer.wrapper.HSpacer" ) {
|
||||
name: "hSpacer1"
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 3 1,growx"
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JButton" ) {
|
||||
name: "closeButton"
|
||||
"text": "Close"
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "close", false ) )
|
||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||
"value": "cell 0 4 3 1"
|
||||
} )
|
||||
}, new FormLayoutConstraints( null ) {
|
||||
"location": new java.awt.Point( 0, 0 )
|
||||
"size": new java.awt.Dimension( 500, 300 )
|
||||
} )
|
||||
}
|
||||
}
|
||||
@@ -202,6 +202,7 @@ public class FlatWindowDecorationsTest
|
||||
private void openDialog() {
|
||||
Window owner = SwingUtilities.windowForComponent( this );
|
||||
JDialog dialog = new JDialog( owner, "Dialog", ModalityType.APPLICATION_MODAL );
|
||||
dialog.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
|
||||
dialog.add( new FlatWindowDecorationsTest() );
|
||||
dialog.pack();
|
||||
dialog.setLocationRelativeTo( this );
|
||||
|
||||
@@ -24,3 +24,10 @@ include( "flatlaf-intellij-themes" )
|
||||
include( "flatlaf-demo" )
|
||||
include( "flatlaf-testing" )
|
||||
include( "flatlaf-theme-editor" )
|
||||
|
||||
includeProject( "flatlaf-natives-jna", "flatlaf-natives/flatlaf-natives-jna" )
|
||||
|
||||
fun includeProject( projectPath: String, projectDir: String ) {
|
||||
include( projectPath )
|
||||
project( ":$projectPath" ).projectDir = file( projectDir )
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user