Native window decorations: initial implementation (using JNA; will be replaced with JNI later)

This commit is contained in:
Karl Tauber
2021-02-21 17:51:19 +01:00
parent fa7dd3bdc4
commit b9a2e3ceac
17 changed files with 1475 additions and 120 deletions

View File

@@ -27,6 +27,7 @@ repositories {
dependencies {
implementation( project( ":flatlaf-core" ) )
implementation( project( ":flatlaf-native-jna" ) )
implementation( project( ":flatlaf-extras" ) )
implementation( project( ":flatlaf-swingx" ) )
implementation( project( ":flatlaf-jide-oss" ) )

View File

@@ -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.nativejna.windows.FlatWindowsNativeWindowBorder;
import com.formdev.flatlaf.ui.FlatLineBorder;
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() {
FlatWindowsNativeWindowBorder.getInstance().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 );
}
}
}

View File

@@ -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 )
} )
}
}

View File

@@ -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 );