Window decorations: support resizing window (issues #47 and #82)

This commit is contained in:
Karl Tauber
2020-06-06 12:20:33 +02:00
parent 1fffc67d13
commit 8e77eb0519
6 changed files with 331 additions and 0 deletions

View File

@@ -55,6 +55,7 @@ public class FlatRootPaneUI
private JRootPane rootPane;
private FlatTitlePane titlePane;
private LayoutManager oldLayout;
private FlatWindowResizer windowResizer;
public static ComponentUI createUI( JComponent c ) {
return new FlatRootPaneUI();
@@ -111,12 +112,21 @@ public class FlatRootPaneUI
// install layout
oldLayout = rootPane.getLayout();
rootPane.setLayout( new FlatRootLayout() );
// install window resizer
if( !JBRCustomDecorations.isSupported() )
windowResizer = new FlatWindowResizer( rootPane );
}
private void uninstallClientDecorations() {
LookAndFeel.uninstallBorder( rootPane );
setTitlePane( null );
if( windowResizer != null ) {
windowResizer.uninstall();
windowResizer = null;
}
if( oldLayout != null ) {
rootPane.setLayout( oldLayout );
oldLayout = null;

View File

@@ -0,0 +1,310 @@
/*
* Copyright 2020 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 static java.awt.Cursor.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.UIManager;
import com.formdev.flatlaf.util.UIScale;
/**
* Resizes frames and dialogs.
*
* @author Karl Tauber
*/
class FlatWindowResizer
extends JComponent
implements PropertyChangeListener, ComponentListener
{
private final static Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1;
private final JRootPane rootPane;
private final int borderDragThickness = FlatUIUtils.getUIInt( "RootPane.borderDragThickness", 5 );
private final int cornerDragWidth = FlatUIUtils.getUIInt( "RootPane.cornerDragWidth", 16 );
private final boolean honorMinimumSizeOnResize = UIManager.getBoolean( "RootPane.honorMinimumSizeOnResize" );
private Window window;
FlatWindowResizer( JRootPane rootPane ) {
this.rootPane = rootPane;
setLayout( new BorderLayout() );
add( new DragBorderComponent( NW_RESIZE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR ), BorderLayout.NORTH );
add( new DragBorderComponent( SW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR ), BorderLayout.SOUTH );
add( new DragBorderComponent( NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR ), BorderLayout.WEST );
add( new DragBorderComponent( NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR ), BorderLayout.EAST );
rootPane.addComponentListener( this );
rootPane.getLayeredPane().add( this, WINDOW_RESIZER_LAYER );
if( rootPane.isDisplayable() )
setBounds( 0, 0, rootPane.getWidth(), rootPane.getHeight() );
}
void uninstall() {
rootPane.removeComponentListener( this );
rootPane.getLayeredPane().remove( this );
}
@Override
public void addNotify() {
super.addNotify();
Container parent = rootPane.getParent();
window = (parent instanceof Window) ? (Window) parent : null;
if( window instanceof Frame )
window.addPropertyChangeListener( "resizable", this );
updateVisibility();
}
@Override
public void removeNotify() {
super.removeNotify();
if( window instanceof Frame )
window.removePropertyChangeListener( "resizable", this );
window = null;
updateVisibility();
}
@Override
protected void paintChildren( Graphics g ) {
super.paintChildren( g );
// this is necessary because Dialog.setResizable() does not fire events
if( window instanceof Dialog )
updateVisibility();
}
private void updateVisibility() {
boolean visible = isWindowResizable();
if( visible == getComponent( 0 ).isVisible() )
return;
for( Component c : getComponents() )
c.setVisible( visible );
}
private boolean isWindowResizable() {
if( window instanceof Frame )
return ((Frame)window).isResizable();
if( window instanceof Dialog )
return ((Dialog)window).isResizable();
return false;
}
@Override
public void propertyChange( PropertyChangeEvent e ) {
updateVisibility();
}
@Override
public void componentResized( ComponentEvent e ) {
setBounds( 0, 0, rootPane.getWidth(), rootPane.getHeight() );
validate();
}
@Override public void componentMoved( ComponentEvent e ) {}
@Override public void componentShown( ComponentEvent e ) {}
@Override public void componentHidden( ComponentEvent e ) {}
//---- class DragBorderComponent ------------------------------------------
private class DragBorderComponent
extends JComponent
implements MouseListener, MouseMotionListener
{
private final int leadingResizeDir;
private final int centerResizeDir;
private final int trailingResizeDir;
private int resizeDir = -1;
private int dragStartMouseX;
private int dragStartMouseY;
private Rectangle dragStartWindowBounds;
DragBorderComponent( int leadingResizeDir, int centerResizeDir, int trailingResizeDir ) {
this.leadingResizeDir = leadingResizeDir;
this.centerResizeDir = centerResizeDir;
this.trailingResizeDir = trailingResizeDir;
setResizeDir( centerResizeDir );
setVisible( false );
addMouseListener( this );
addMouseMotionListener( this );
}
private void setResizeDir( int resizeDir ) {
if( this.resizeDir == resizeDir )
return;
this.resizeDir = resizeDir;
setCursor( getPredefinedCursor( resizeDir ) );
}
@Override
public Dimension getPreferredSize() {
int thickness = UIScale.scale( borderDragThickness );
return new Dimension( thickness, thickness );
}
/*debug
@Override
protected void paintComponent( Graphics g ) {
g.setColor( java.awt.Color.red );
g.drawRect( 0, 0, getWidth() - 1, getHeight() - 1 );
}
debug*/
@Override
public void mouseClicked( MouseEvent e ) {
}
@Override
public void mousePressed( MouseEvent e ) {
if( window == null )
return;
dragStartMouseX = e.getXOnScreen();
dragStartMouseY = e.getYOnScreen();
dragStartWindowBounds = window.getBounds();
}
@Override
public void mouseReleased( MouseEvent e ) {
dragStartWindowBounds = null;
}
@Override
public void mouseEntered( MouseEvent e ) {
}
@Override
public void mouseExited( MouseEvent e ) {
}
@Override
public void mouseMoved( MouseEvent e ) {
boolean topBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR);
int xy = topBottom ? e.getX() : e.getY();
int wh = topBottom ? getWidth() : getHeight();
int cornerWH = UIScale.scale( cornerDragWidth - (topBottom ? 0 : borderDragThickness) );
setResizeDir( xy <= cornerWH
? leadingResizeDir
: (xy >= wh - cornerWH
? trailingResizeDir
: centerResizeDir) );
}
@Override
public void mouseDragged( MouseEvent e ) {
if( dragStartWindowBounds == null )
return;
if( !isWindowResizable() )
return;
int mouseDeltaX = e.getXOnScreen() - dragStartMouseX;
int mouseDeltaY = e.getYOnScreen() - dragStartMouseY;
int deltaX = 0;
int deltaY = 0;
int deltaWidth = 0;
int deltaHeight = 0;
// north
if( resizeDir == N_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR ) {
deltaY = mouseDeltaY;
deltaHeight = -mouseDeltaY;
}
// south
if( resizeDir == S_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR )
deltaHeight = mouseDeltaY;
// west
if( resizeDir == W_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR ) {
deltaX = mouseDeltaX;
deltaWidth = -mouseDeltaX;
}
// east
if( resizeDir == E_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR )
deltaWidth = mouseDeltaX;
// compute new window bounds
Rectangle newBounds = new Rectangle( dragStartWindowBounds );
newBounds.x += deltaX;
newBounds.y += deltaY;
newBounds.width += deltaWidth;
newBounds.height += deltaHeight;
// apply minimum window size
Dimension minimumSize = honorMinimumSizeOnResize ? window.getMinimumSize() : null;
if( minimumSize == null )
minimumSize = UIScale.scale( new Dimension( 150, 50 ) );
if( newBounds.width < minimumSize.width ) {
if( deltaX != 0 )
newBounds.x -= (minimumSize.width - newBounds.width);
newBounds.width = minimumSize.width;
}
if( newBounds.height < minimumSize.height ) {
if( deltaY != 0 )
newBounds.y -= (minimumSize.height - newBounds.height);
newBounds.height = minimumSize.height;
}
// set window bounds
if( !newBounds.equals( dragStartWindowBounds ) ) {
window.setBounds( newBounds );
// immediately layout drag border components
FlatWindowResizer.this.setBounds( 0, 0, newBounds.width, newBounds.height );
FlatWindowResizer.this.validate();
if( Toolkit.getDefaultToolkit().isDynamicLayoutActive() ) {
window.validate();
rootPane.repaint();
}
}
}
}
}

View File

@@ -426,6 +426,9 @@ RadioButtonMenuItem.background=@menuBackground
#---- RootPane ----
RootPane.border=com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder
RootPane.borderDragThickness=5
RootPane.cornerDragWidth=16
RootPane.honorMinimumSizeOnResize=true
#---- ScrollBar ----

View File

@@ -114,6 +114,8 @@ public class FlatWindowDecorationsTest
Window window = SwingUtilities.windowForComponent( this );
if( window instanceof Frame )
((Frame)window).setResizable( resizableCheckBox.isSelected() );
else if( window instanceof Dialog )
((Dialog)window).setResizable( resizableCheckBox.isSelected() );
}
private void menuItemActionPerformed(ActionEvent e) {

View File

@@ -744,6 +744,8 @@ Resizable.resizeBorder [lazy] 4,4,4,4 false com.formdev.flatlaf.ui.F
#---- RootPane ----
RootPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder [UI]
RootPane.borderDragThickness 5
RootPane.cornerDragWidth 16
RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[0] ENTER
[1] press
@@ -753,6 +755,7 @@ RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[5] press
[6] ctrl released ENTER
[7] release
RootPane.honorMinimumSizeOnResize true
RootPaneUI com.formdev.flatlaf.ui.FlatRootPaneUI

View File

@@ -746,6 +746,8 @@ Resizable.resizeBorder [lazy] 4,4,4,4 false com.formdev.flatlaf.ui.F
#---- RootPane ----
RootPane.border [lazy] 1,1,1,1 false com.formdev.flatlaf.ui.FlatRootPaneUI$FlatWindowBorder [UI]
RootPane.borderDragThickness 5
RootPane.cornerDragWidth 16
RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[0] ENTER
[1] press
@@ -755,6 +757,7 @@ RootPane.defaultButtonWindowKeyBindings length=8 [Ljava.lang.Object;
[5] press
[6] ctrl released ENTER
[7] release
RootPane.honorMinimumSizeOnResize true
RootPaneUI com.formdev.flatlaf.ui.FlatRootPaneUI