InternalFrame: support draggable border for resizing frame inside of the visible frame border (issue #121)

This commit is contained in:
Karl Tauber
2020-09-04 22:59:09 +02:00
parent 98b9df06fe
commit c44905ea5e
4 changed files with 319 additions and 97 deletions

View File

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

View File

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

View File

@@ -172,7 +172,7 @@ public class FlatRootPaneUI
}
protected FlatWindowResizer createWindowResizer() {
return new FlatWindowResizer( rootPane );
return new FlatWindowResizer.WindowResizer( rootPane );
}
protected FlatTitlePane createTitlePane() {

View File

@@ -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.
* <p>
* 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> desktopManager;
public InternalFrameResizer( JInternalFrame frame, Supplier<DesktopManager> 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 );
}
}
}