diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b556f4..1e972e2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ FlatLaf Change Log - Demo: Improved "SplitPane & Tabs" and "Data Components" tabs. - Menu items "File > Open" and "File > Save As" now show file choosers. +- InternalFrame: Support draggable border for resizing frame inside of the + visible frame border. (issue #121) #### Fixed bugs diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java index ac753d97..efc3bfaf 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatInternalFrameUI.java @@ -84,6 +84,8 @@ import javax.swing.plaf.basic.BasicInternalFrameUI; public class FlatInternalFrameUI extends BasicInternalFrameUI { + protected FlatWindowResizer windowResizer; + public static ComponentUI createUI( JComponent c ) { return new FlatInternalFrameUI( (JInternalFrame) c ); } @@ -97,6 +99,18 @@ public class FlatInternalFrameUI super.installUI( c ); LookAndFeel.installProperty( frame, "opaque", false ); + + windowResizer = createWindowResizer(); + } + + @Override + public void uninstallUI( JComponent c ) { + super.uninstallUI( c ); + + if( windowResizer != null ) { + windowResizer.uninstall(); + windowResizer = null; + } } @Override @@ -104,6 +118,10 @@ public class FlatInternalFrameUI return new FlatInternalFrameTitlePane( w ); } + protected FlatWindowResizer createWindowResizer() { + return new FlatWindowResizer.InternalFrameResizer( frame, this::getDesktopManager ); + } + //---- class FlatInternalFrameBorder -------------------------------------- public static class FlatInternalFrameBorder diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java index 048d3ac6..49b687f9 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatRootPaneUI.java @@ -172,7 +172,7 @@ public class FlatRootPaneUI } protected FlatWindowResizer createWindowResizer() { - return new FlatWindowResizer( rootPane ); + return new FlatWindowResizer.WindowResizer( rootPane ); } protected FlatTitlePane createTitlePane() { diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java index 46f0c1cc..cd1c3784 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatWindowResizer.java @@ -17,11 +17,13 @@ package com.formdev.flatlaf.ui; import static java.awt.Cursor.*; +import static javax.swing.SwingConstants.*; import java.awt.Container; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; +import java.awt.Insets; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; @@ -34,54 +36,59 @@ import java.awt.event.WindowEvent; import java.awt.event.WindowStateListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.function.Supplier; +import javax.swing.DesktopManager; import javax.swing.JComponent; +import javax.swing.JInternalFrame; import javax.swing.JLayeredPane; import javax.swing.JRootPane; import javax.swing.UIManager; import com.formdev.flatlaf.util.UIScale; /** - * Resizes frames and dialogs. + * Resizes frames, dialogs or internal frames. + *

+ * Could also be used to implement resize support for any Swing component + * by creating a new subclass. * * @author Karl Tauber */ -public class FlatWindowResizer - implements PropertyChangeListener, WindowStateListener, ComponentListener +public abstract class FlatWindowResizer + implements PropertyChangeListener, ComponentListener { protected final static Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1; - protected final JRootPane rootPane; + protected final JComponent resizeComp; protected final int borderDragThickness = FlatUIUtils.getUIInt( "RootPane.borderDragThickness", 5 ); protected final int cornerDragWidth = FlatUIUtils.getUIInt( "RootPane.cornerDragWidth", 16 ); protected final boolean honorFrameMinimumSizeOnResize = UIManager.getBoolean( "RootPane.honorFrameMinimumSizeOnResize" ); protected final boolean honorDialogMinimumSizeOnResize = UIManager.getBoolean( "RootPane.honorDialogMinimumSizeOnResize" ); - protected final JComponent north; - protected final JComponent south; - protected final JComponent west; - protected final JComponent east; + protected final DragBorderComponent topDragComp; + protected final DragBorderComponent bottomDragComp; + protected final DragBorderComponent leftDragComp; + protected final DragBorderComponent rightDragComp; - protected Window window; + protected FlatWindowResizer( JComponent resizeComp ) { + this.resizeComp = resizeComp; - public FlatWindowResizer( JRootPane rootPane ) { - this.rootPane = rootPane; + topDragComp = createDragBorderComponent( NW_RESIZE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR ); + bottomDragComp = createDragBorderComponent( SW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR ); + leftDragComp = createDragBorderComponent( NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR ); + rightDragComp = createDragBorderComponent( NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR ); - north = createDragBorderComponent( NW_RESIZE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR ); - south = createDragBorderComponent( SW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR ); - west = createDragBorderComponent( NW_RESIZE_CURSOR, W_RESIZE_CURSOR, SW_RESIZE_CURSOR ); - east = createDragBorderComponent( NE_RESIZE_CURSOR, E_RESIZE_CURSOR, SE_RESIZE_CURSOR ); + Container cont = (resizeComp instanceof JRootPane) ? ((JRootPane)resizeComp).getLayeredPane() : resizeComp; + Object cons = (cont instanceof JLayeredPane) ? WINDOW_RESIZER_LAYER : null; + cont.add( topDragComp, cons, 0 ); + cont.add( bottomDragComp, cons, 1 ); + cont.add( leftDragComp, cons, 2 ); + cont.add( rightDragComp, cons, 3 ); - JLayeredPane layeredPane = rootPane.getLayeredPane(); - layeredPane.add( north, WINDOW_RESIZER_LAYER ); - layeredPane.add( south, WINDOW_RESIZER_LAYER ); - layeredPane.add( west, WINDOW_RESIZER_LAYER ); - layeredPane.add( east, WINDOW_RESIZER_LAYER ); + resizeComp.addComponentListener( this ); + resizeComp.addPropertyChangeListener( "ancestor", this ); - rootPane.addComponentListener( this ); - rootPane.addPropertyChangeListener( "ancestor", this ); - - if( rootPane.isDisplayable() ) + if( resizeComp.isDisplayable() ) addNotify(); } @@ -92,85 +99,96 @@ public class FlatWindowResizer public void uninstall() { removeNotify(); - rootPane.removeComponentListener( this ); - rootPane.removePropertyChangeListener( "ancestor", this ); + resizeComp.removeComponentListener( this ); + resizeComp.removePropertyChangeListener( "ancestor", this ); - JLayeredPane layeredPane = rootPane.getLayeredPane(); - layeredPane.remove( north ); - layeredPane.remove( south ); - layeredPane.remove( west ); - layeredPane.remove( east ); + Container cont = topDragComp.getParent(); + cont.remove( topDragComp ); + cont.remove( bottomDragComp ); + cont.remove( leftDragComp ); + cont.remove( rightDragComp ); } public void doLayout() { - if( !north.isVisible() ) + if( !topDragComp.isVisible() ) return; int x = 0; int y = 0; - int width = rootPane.getWidth(); - int height = rootPane.getHeight(); + int width = resizeComp.getWidth(); + int height = resizeComp.getHeight(); if( width == 0 || height == 0 ) return; + Insets resizeInsets = getResizeInsets(); int thickness = UIScale.scale( borderDragThickness ); - int y2 = y + thickness; - int height2 = height - (thickness * 2); + int topThickness = Math.max( resizeInsets.top, thickness ); + int bottomThickness = Math.max( resizeInsets.bottom, thickness ); + int leftThickness = Math.max( resizeInsets.left, thickness ); + int rightThickness = Math.max( resizeInsets.right, thickness ); + int y2 = y + topThickness; + int height2 = height - topThickness - bottomThickness; - north.setBounds( x, y, width, thickness ); - south.setBounds( x, y + height - thickness, width, thickness ); - west.setBounds( x, y2, thickness, height2 ); - east.setBounds( x + width - thickness, y2, thickness, height2 ); + // set bounds of drag components + topDragComp.setBounds( x, y, width, topThickness ); + bottomDragComp.setBounds( x, y + height - bottomThickness, width, bottomThickness ); + leftDragComp.setBounds( x, y2, leftThickness, height2 ); + rightDragComp.setBounds( x + width - rightThickness, y2, rightThickness, height2 ); + + // set corner drag widths + int cornerDelta = UIScale.scale( cornerDragWidth - borderDragThickness ); + topDragComp.setCornerDragWidths( leftThickness + cornerDelta, rightThickness + cornerDelta ); + bottomDragComp.setCornerDragWidths( leftThickness + cornerDelta, rightThickness + cornerDelta ); + leftDragComp.setCornerDragWidths( cornerDelta, cornerDelta ); + rightDragComp.setCornerDragWidths( cornerDelta, cornerDelta ); + } + + protected Insets getResizeInsets() { + return new Insets( 0, 0, 0, 0 ); } protected void addNotify() { - Container parent = rootPane.getParent(); - window = (parent instanceof Window) ? (Window) parent : null; - if( window instanceof Frame ) { - window.addPropertyChangeListener( "resizable", this ); - window.addWindowStateListener( this ); - } - updateVisibility(); } protected void removeNotify() { - if( window instanceof Frame ) { - window.removePropertyChangeListener( "resizable", this ); - window.removeWindowStateListener( this ); - } - window = null; - updateVisibility(); } protected void updateVisibility() { boolean visible = isWindowResizable(); - if( visible == north.isVisible() ) + if( visible == topDragComp.isVisible() ) return; - north.setVisible( visible ); - south.setVisible( visible ); - west.setVisible( visible ); + topDragComp.setVisible( visible ); + bottomDragComp.setVisible( visible ); + leftDragComp.setVisible( visible ); // The east component is not hidden, instead its bounds are set to 0,0,1,1 and // it is disabled. This is necessary so that DragBorderComponent.paintComponent() is invoked. - east.setEnabled( visible ); + rightDragComp.setEnabled( visible ); if( visible ) { - east.setVisible( true ); // necessary because it is initially invisible + rightDragComp.setVisible( true ); // necessary because it is initially invisible doLayout(); } else - east.setBounds( 0, 0, 1, 1 ); + rightDragComp.setBounds( 0, 0, 1, 1 ); } - protected boolean isWindowResizable() { - if( window instanceof Frame ) - return ((Frame)window).isResizable() && (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) == 0; - if( window instanceof Dialog ) - return ((Dialog)window).isResizable(); + boolean isDialog() { return false; } + protected abstract boolean isWindowResizable(); + protected abstract Rectangle getWindowBounds(); + protected abstract void setWindowBounds( Rectangle r ); + protected abstract boolean honorMinimumSizeOnResize(); + protected abstract Dimension getWindowMinimumSize(); + + protected void beginResizing( int direction ) {} + protected void endResizing() {} + + //---- interface PropertyChangeListener ---- + @Override public void propertyChange( PropertyChangeEvent e ) { switch( e.getPropertyName() ) { @@ -187,10 +205,7 @@ public class FlatWindowResizer } } - @Override - public void windowStateChanged( WindowEvent e ) { - updateVisibility(); - } + //---- interface ComponentListener ---- @Override public void componentResized( ComponentEvent e ) { @@ -201,6 +216,163 @@ public class FlatWindowResizer @Override public void componentShown( ComponentEvent e ) {} @Override public void componentHidden( ComponentEvent e ) {} + //---- class WindowResizer ------------------------------------------------ + + /** + * Resizes frames and dialogs. + */ + public static class WindowResizer + extends FlatWindowResizer + implements WindowStateListener + { + protected Window window; + + public WindowResizer( JRootPane rootPane ) { + super( rootPane ); + } + + @Override + protected void addNotify() { + Container parent = resizeComp.getParent(); + window = (parent instanceof Window) ? (Window) parent : null; + if( window instanceof Frame ) { + window.addPropertyChangeListener( "resizable", this ); + window.addWindowStateListener( this ); + } + + super.addNotify(); + } + + @Override + protected void removeNotify() { + if( window instanceof Frame ) { + window.removePropertyChangeListener( "resizable", this ); + window.removeWindowStateListener( this ); + } + window = null; + + super.removeNotify(); + } + + @Override + protected boolean isWindowResizable() { + if( window instanceof Frame ) + return ((Frame)window).isResizable() && (((Frame)window).getExtendedState() & Frame.MAXIMIZED_BOTH) == 0; + if( window instanceof Dialog ) + return ((Dialog)window).isResizable(); + return false; + } + + @Override + protected Rectangle getWindowBounds() { + return window.getBounds(); + } + + @Override + protected void setWindowBounds( Rectangle r ) { + window.setBounds( r ); + + // immediately layout drag border components + doLayout(); + + if( Toolkit.getDefaultToolkit().isDynamicLayoutActive() ) { + window.validate(); + resizeComp.repaint(); + } + } + + @Override + protected boolean honorMinimumSizeOnResize() { + return + (honorFrameMinimumSizeOnResize && window instanceof Frame) || + (honorDialogMinimumSizeOnResize && window instanceof Dialog); + } + + @Override + protected Dimension getWindowMinimumSize() { + return window.getMinimumSize(); + } + + @Override + boolean isDialog() { + return window instanceof Dialog; + } + + @Override + public void windowStateChanged( WindowEvent e ) { + updateVisibility(); + } + } + + //---- class InternalFrameResizer ----------------------------------------- + + /** + * Resizes internal frames. + */ + public static class InternalFrameResizer + extends FlatWindowResizer + { + protected final Supplier desktopManager; + + public InternalFrameResizer( JInternalFrame frame, Supplier desktopManager ) { + super( frame ); + this.desktopManager = desktopManager; + + frame.addPropertyChangeListener( "resizable", this ); + } + + @Override + public void uninstall() { + getFrame().removePropertyChangeListener( "resizable", this ); + + super.uninstall(); + } + + private JInternalFrame getFrame() { + return (JInternalFrame) resizeComp; + } + + @Override + protected Insets getResizeInsets() { + return getFrame().getInsets(); + } + + @Override + protected boolean isWindowResizable() { + return getFrame().isResizable(); + } + + @Override + protected Rectangle getWindowBounds() { + return getFrame().getBounds(); + } + + @Override + protected void setWindowBounds( Rectangle r ) { + desktopManager.get().resizeFrame( getFrame(), r.x, r.y, r.width, r.height ); + } + + @Override + protected boolean honorMinimumSizeOnResize() { + return true; + } + + @Override + protected Dimension getWindowMinimumSize() { + return getFrame().getMinimumSize(); + } + + @Override + protected void beginResizing( int direction ) { + desktopManager.get().beginResizingFrame( getFrame(), direction ); + } + + @Override + protected void endResizing() { + desktopManager.get().endResizingFrame( getFrame() ); + } + } + //---- class DragBorderComponent ------------------------------------------ protected class DragBorderComponent @@ -213,6 +385,9 @@ public class FlatWindowResizer private int resizeDir = -1; + private int leadingCornerDragWidth; + private int trailingCornerDragWidth; + // offsets of mouse position to window edges private int dragLeftOffset; private int dragRightOffset; @@ -231,6 +406,11 @@ public class FlatWindowResizer addMouseMotionListener( this ); } + void setCornerDragWidths( int leading, int trailing ) { + leadingCornerDragWidth = leading; + trailingCornerDragWidth = trailing; + } + protected void setResizeDir( int resizeDir ) { if( this.resizeDir == resizeDir ) return; @@ -250,12 +430,25 @@ public class FlatWindowResizer super.paintChildren( g ); // this is necessary because Dialog.setResizable() does not fire events - if( window instanceof Dialog ) + if( isDialog() ) updateVisibility(); /*debug + int width = getWidth(); + int height = getHeight(); + + g.setColor( java.awt.Color.blue ); + boolean topOrBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR); + if( topOrBottom ) { + g.drawLine( leadingCornerDragWidth, 0, leadingCornerDragWidth, height ); + g.drawLine( width - trailingCornerDragWidth, 0, width - trailingCornerDragWidth, height ); + } else { + g.drawLine( 0, leadingCornerDragWidth, width, leadingCornerDragWidth ); + g.drawLine( 0, height - trailingCornerDragWidth, width, height - trailingCornerDragWidth ); + } + g.setColor( java.awt.Color.red ); - g.drawRect( 0, 0, getWidth() - 1, getHeight() - 1 ); + g.drawRect( 0, 0, width - 1, height - 1 ); debug*/ } @@ -265,34 +458,55 @@ debug*/ @Override public void mousePressed( MouseEvent e ) { - if( window == null ) + if( !isWindowResizable() ) return; int xOnScreen = e.getXOnScreen(); int yOnScreen = e.getYOnScreen(); - Rectangle windowBounds = window.getBounds(); + Rectangle windowBounds = getWindowBounds(); // compute offsets of mouse position to window edges dragLeftOffset = xOnScreen - windowBounds.x; dragTopOffset = yOnScreen - windowBounds.y; dragRightOffset = windowBounds.x + windowBounds.width - xOnScreen; dragBottomOffset = windowBounds.y + windowBounds.height - yOnScreen; + + int direction = 0; + switch( resizeDir ) { + case N_RESIZE_CURSOR: direction = NORTH; break; + case S_RESIZE_CURSOR: direction = SOUTH; break; + case W_RESIZE_CURSOR: direction = WEST; break; + case E_RESIZE_CURSOR: direction = EAST; break; + case NW_RESIZE_CURSOR: direction = NORTH_WEST; break; + case NE_RESIZE_CURSOR: direction = NORTH_EAST; break; + case SW_RESIZE_CURSOR: direction = SOUTH_WEST; break; + case SE_RESIZE_CURSOR: direction = SOUTH_EAST; break; + } + beginResizing( direction ); + } + + @Override + public void mouseReleased( MouseEvent e ) { + if( !isWindowResizable() ) + return; + + dragLeftOffset = dragRightOffset = dragTopOffset = dragBottomOffset = 0; + + endResizing(); } - @Override public void mouseReleased( MouseEvent e ) {} @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) ); + boolean topOrBottom = (centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR); + int xy = topOrBottom ? e.getX() : e.getY(); + int wh = topOrBottom ? getWidth() : getHeight(); - setResizeDir( xy <= cornerWH + setResizeDir( xy <= leadingCornerDragWidth ? leadingResizeDir - : (xy >= wh - cornerWH + : (xy >= wh - trailingCornerDragWidth ? trailingResizeDir : centerResizeDir) ); } @@ -311,7 +525,7 @@ debug*/ // a window on first screen to the second screen, then the window manager may // decide at some point that the window should be only on second screen // and adjusts its bounds. - Rectangle oldBounds = window.getBounds(); + Rectangle oldBounds = getWindowBounds(); Rectangle newBounds = new Rectangle( oldBounds ); // compute new window bounds @@ -337,10 +551,7 @@ debug*/ newBounds.width = (xOnScreen + dragRightOffset) - newBounds.x; // apply minimum window size - boolean honorMinimumSizeOnResize = - (honorFrameMinimumSizeOnResize && window instanceof Frame) || - (honorDialogMinimumSizeOnResize && window instanceof Dialog); - Dimension minimumSize = honorMinimumSizeOnResize ? window.getMinimumSize() : null; + Dimension minimumSize = honorMinimumSizeOnResize() ? getWindowMinimumSize() : null; if( minimumSize == null ) minimumSize = UIScale.scale( new Dimension( 150, 50 ) ); if( newBounds.width < minimumSize.width ) { @@ -355,17 +566,8 @@ debug*/ } // set window bounds - if( !newBounds.equals( oldBounds ) ) { - window.setBounds( newBounds ); - - // immediately layout drag border components - FlatWindowResizer.this.doLayout(); - - if( Toolkit.getDefaultToolkit().isDynamicLayoutActive() ) { - window.validate(); - rootPane.repaint(); - } - } + if( !newBounds.equals( oldBounds ) ) + setWindowBounds( newBounds ); } } }