diff --git a/CHANGELOG.md b/CHANGELOG.md index 87de3294..34b03092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ FlatLaf Change Log ## 1.5-SNAPSHOT +- InternalFrame: Limit internal frame bounds to parent bounds on resize. Also + honor maximum size of internal frame. (issue #362) - Popup: Fixed incorrectly placed drop shadow for medium-weight popups in maximized windows. (issue #358) 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 22821b11..d6be28d1 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 @@ -181,8 +181,12 @@ public abstract class FlatWindowResizer protected abstract boolean isWindowResizable(); protected abstract Rectangle getWindowBounds(); protected abstract void setWindowBounds( Rectangle r ); + protected abstract boolean limitToParentBounds(); + protected abstract Rectangle getParentBounds(); protected abstract boolean honorMinimumSizeOnResize(); + protected abstract boolean honorMaximumSizeOnResize(); protected abstract Dimension getWindowMinimumSize(); + protected abstract Dimension getWindowMaximumSize(); protected void beginResizing( int direction ) {} protected void endResizing() {} @@ -283,6 +287,16 @@ public abstract class FlatWindowResizer } } + @Override + protected boolean limitToParentBounds() { + return false; + } + + @Override + protected Rectangle getParentBounds() { + return null; + } + @Override protected boolean honorMinimumSizeOnResize() { return @@ -290,11 +304,21 @@ public abstract class FlatWindowResizer (honorDialogMinimumSizeOnResize && window instanceof Dialog); } + @Override + protected boolean honorMaximumSizeOnResize() { + return false; + } + @Override protected Dimension getWindowMinimumSize() { return window.getMinimumSize(); } + @Override + protected Dimension getWindowMaximumSize() { + return window.getMaximumSize(); + } + @Override boolean isDialog() { return window instanceof Dialog; @@ -354,16 +378,36 @@ public abstract class FlatWindowResizer desktopManager.get().resizeFrame( getFrame(), r.x, r.y, r.width, r.height ); } + @Override + protected boolean limitToParentBounds() { + return true; + } + + @Override + protected Rectangle getParentBounds() { + return getFrame().getParent().getBounds(); + } + @Override protected boolean honorMinimumSizeOnResize() { return true; } + @Override + protected boolean honorMaximumSizeOnResize() { + return true; + } + @Override protected Dimension getWindowMinimumSize() { return getFrame().getMinimumSize(); } + @Override + protected Dimension getWindowMaximumSize() { + return getFrame().getMaximumSize(); + } + @Override protected void beginResizing( int direction ) { desktopManager.get().beginResizingFrame( getFrame(), direction ); @@ -521,7 +565,7 @@ debug*/ int xOnScreen = e.getXOnScreen(); int yOnScreen = e.getYOnScreen(); - // Get current window bounds and compute new bounds based them. + // Get current window bounds and compute new bounds based on them. // This is necessary because window manager may alter window bounds while resizing. // E.g. when having two monitors with different scale factors and resizing // a window on first screen to the second screen, then the window manager may @@ -535,41 +579,72 @@ debug*/ // top if( resizeDir == N_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR ) { newBounds.y = yOnScreen - dragTopOffset; + if( limitToParentBounds() && newBounds.y < 0 ) + newBounds.y = 0; newBounds.height += (oldBounds.y - newBounds.y); } // bottom - if( resizeDir == S_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR ) + if( resizeDir == S_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR ) { newBounds.height = (yOnScreen + dragBottomOffset) - newBounds.y; + if( limitToParentBounds() ) { + int parentHeight = getParentBounds().height; + if( newBounds.y + newBounds.height > parentHeight ) + newBounds.height = parentHeight - newBounds.y; + } + } // left if( resizeDir == W_RESIZE_CURSOR || resizeDir == NW_RESIZE_CURSOR || resizeDir == SW_RESIZE_CURSOR ) { newBounds.x = xOnScreen - dragLeftOffset; + if( limitToParentBounds() && newBounds.x < 0 ) + newBounds.x = 0; newBounds.width += (oldBounds.x - newBounds.x); } // right - if( resizeDir == E_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR ) + if( resizeDir == E_RESIZE_CURSOR || resizeDir == NE_RESIZE_CURSOR || resizeDir == SE_RESIZE_CURSOR ) { newBounds.width = (xOnScreen + dragRightOffset) - newBounds.x; + if( limitToParentBounds() ) { + int parentWidth = getParentBounds().width; + if( newBounds.x + newBounds.width > parentWidth ) + newBounds.width = parentWidth - newBounds.x; + } + } // apply minimum window size Dimension minimumSize = honorMinimumSizeOnResize() ? getWindowMinimumSize() : null; if( minimumSize == null ) minimumSize = UIScale.scale( new Dimension( 150, 50 ) ); - if( newBounds.width < minimumSize.width ) { - if( newBounds.x != oldBounds.x ) - newBounds.x -= (minimumSize.width - newBounds.width); - newBounds.width = minimumSize.width; - } - if( newBounds.height < minimumSize.height ) { - if( newBounds.y != oldBounds.y ) - newBounds.y -= (minimumSize.height - newBounds.height); - newBounds.height = minimumSize.height; + if( newBounds.width < minimumSize.width ) + changeWidth( oldBounds, newBounds, minimumSize.width ); + if( newBounds.height < minimumSize.height ) + changeHeight( oldBounds, newBounds, minimumSize.height ); + + // apply maximum window size + if( honorMaximumSizeOnResize() ) { + Dimension maximumSize = getWindowMaximumSize(); + if( newBounds.width > maximumSize.width ) + changeWidth( oldBounds, newBounds, maximumSize.width ); + if( newBounds.height > maximumSize.height ) + changeHeight( oldBounds, newBounds, maximumSize.height ); } // set window bounds if( !newBounds.equals( oldBounds ) ) setWindowBounds( newBounds ); } + + private void changeWidth( Rectangle oldBounds, Rectangle newBounds, int width ) { + if( newBounds.x != oldBounds.x ) + newBounds.x -= (width - newBounds.width); + newBounds.width = width; + } + + private void changeHeight( Rectangle oldBounds, Rectangle newBounds, int height ) { + if( newBounds.y != oldBounds.y ) + newBounds.y -= (height - newBounds.height); + newBounds.height = height; + } } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.java index 5ebe5c84..75a328ae 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.java @@ -89,6 +89,15 @@ public class FlatInternalFrameTest }; internalFrame.setContentPane( panel ); + if( minSizeCheckBox.isSelected() ) { + internalFrame.setMinimumSize( new Dimension( 300, 150 ) ); + panel.add( new JLabel( "min 300,150" ) ); + } + if( maxSizeCheckBox.isSelected() ) { + internalFrame.setMaximumSize( new Dimension( 400, 200 ) ); + panel.add( new JLabel( "max 400,200" ) ); + } + if( !palette.getComponentOrientation().isLeftToRight() ) internalFrame.setComponentOrientation( ComponentOrientation.RIGHT_TO_LEFT ); @@ -123,6 +132,8 @@ public class FlatInternalFrameTest maximizableCheckBox = new JCheckBox(); iconCheckBox = new FlatTriStateCheckBox(); menuBarCheckBox = new JCheckBox(); + minSizeCheckBox = new JCheckBox(); + maxSizeCheckBox = new JCheckBox(); titleLabel = new JLabel(); titleField = new JTextField(); createFrameButton = new JButton(); @@ -158,6 +169,8 @@ public class FlatInternalFrameTest "[fill]0" + "[]0" + "[]0" + + "[]0" + + "[]0" + "[]unrel" + "[]unrel")); @@ -189,18 +202,26 @@ public class FlatInternalFrameTest menuBarCheckBox.setText("Menu Bar"); paletteContentPane.add(menuBarCheckBox, "cell 1 2"); + //---- minSizeCheckBox ---- + minSizeCheckBox.setText("Minimum size 300,150"); + paletteContentPane.add(minSizeCheckBox, "cell 0 3 2 1,alignx left,growx 0"); + + //---- maxSizeCheckBox ---- + maxSizeCheckBox.setText("Maximum size 400,200"); + paletteContentPane.add(maxSizeCheckBox, "cell 0 4 2 1,alignx left,growx 0"); + //---- titleLabel ---- titleLabel.setText("Frame title:"); - paletteContentPane.add(titleLabel, "cell 0 3"); - paletteContentPane.add(titleField, "cell 1 3"); + paletteContentPane.add(titleLabel, "cell 0 5"); + paletteContentPane.add(titleField, "cell 1 5"); //---- createFrameButton ---- createFrameButton.setText("Create Frame"); createFrameButton.addActionListener(e -> createInternalFrame()); - paletteContentPane.add(createFrameButton, "cell 1 4,alignx right,growx 0"); + paletteContentPane.add(createFrameButton, "cell 1 6,alignx right,growx 0"); } desktopPane.add(palette, JLayeredPane.PALETTE_LAYER); - palette.setBounds(15, 25, 275, 185); + palette.setBounds(15, 25, 275, 275); } add(desktopPane, "cell 0 0,width 600,height 600"); @@ -234,6 +255,8 @@ public class FlatInternalFrameTest private JCheckBox maximizableCheckBox; private FlatTriStateCheckBox iconCheckBox; private JCheckBox menuBarCheckBox; + private JCheckBox minSizeCheckBox; + private JCheckBox maxSizeCheckBox; private JLabel titleLabel; private JTextField titleField; private JButton createFrameButton; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.jfd index 34cb827d..c35083f0 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatInternalFrameTest.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.3.1.342" Java: "16" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.4.0.360" Java: "16" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -14,7 +14,7 @@ new FormModel { add( new FormContainer( "javax.swing.JInternalFrame", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[fill]0[]0[]0[]unrel[]unrel" + "$rowConstraints": "[fill]0[]0[]0[]0[]0[]unrel[]unrel" } ) { name: "palette" "visible": true @@ -62,29 +62,41 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 2" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "minSizeCheckBox" + "text": "Minimum size 300,150" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3 2 1,alignx left,growx 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "maxSizeCheckBox" + "text": "Maximum size 400,200" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 4 2 1,alignx left,growx 0" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "titleLabel" "text": "Frame title:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 3" + "value": "cell 0 5" } ) add( new FormComponent( "javax.swing.JTextField" ) { name: "titleField" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 3" + "value": "cell 1 5" } ) add( new FormComponent( "javax.swing.JButton" ) { name: "createFrameButton" "text": "Create Frame" addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "createInternalFrame", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 4,alignx right,growx 0" + "value": "cell 1 6,alignx right,growx 0" } ) }, new FormLayoutConstraints( null ) { "x": 15 "y": 25 "width": 275 - "height": 185 + "height": 275 "layer": 100 } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {