![JAR search and dependency download from the Maven repository](/logo.png)
mdlaf.utils.FlatWindowResizer Maven / Gradle / Ivy
package mdlaf.utils;
/*
* 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.
*/
import static java.awt.Cursor.*;
import static javax.swing.SwingConstants.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.function.Supplier;
import javax.swing.*;
/**
* 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 abstract class FlatWindowResizer implements PropertyChangeListener, ComponentListener {
protected static final Integer WINDOW_RESIZER_LAYER = JLayeredPane.DRAG_LAYER + 1;
protected final JComponent resizeComp;
protected final int borderDragThickness = 5;
protected final int cornerDragWidth = 5;
protected final boolean honorFrameMinimumSizeOnResize =
UIManager.getBoolean("RootPane.honorFrameMinimumSizeOnResize");
protected final boolean honorDialogMinimumSizeOnResize =
UIManager.getBoolean("RootPane.honorDialogMinimumSizeOnResize");
protected final DragBorderComponent topDragComp;
protected final DragBorderComponent bottomDragComp;
protected final DragBorderComponent leftDragComp;
protected final DragBorderComponent rightDragComp;
protected FlatWindowResizer(JComponent resizeComp) {
this.resizeComp = resizeComp;
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);
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);
resizeComp.addComponentListener(this);
resizeComp.addPropertyChangeListener("ancestor", this);
if (resizeComp.isDisplayable()) addNotify();
}
protected DragBorderComponent createDragBorderComponent(
int leadingResizeDir, int centerResizeDir, int trailingResizeDir) {
return new DragBorderComponent(leadingResizeDir, centerResizeDir, trailingResizeDir);
}
public void uninstall() {
removeNotify();
resizeComp.removeComponentListener(this);
resizeComp.removePropertyChangeListener("ancestor", this);
Container cont = topDragComp.getParent();
cont.remove(topDragComp);
cont.remove(bottomDragComp);
cont.remove(leftDragComp);
cont.remove(rightDragComp);
}
public void doLayout() {
if (!topDragComp.isVisible()) return;
int x = 0;
int y = 0;
int width = resizeComp.getWidth();
int height = resizeComp.getHeight();
if (width == 0 || height == 0) return;
Insets resizeInsets = getResizeInsets();
int thickness = borderDragThickness;
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;
// 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 = 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() {
updateVisibility();
}
protected void removeNotify() {
updateVisibility();
}
protected void updateVisibility() {
boolean visible = isWindowResizable();
if (visible == topDragComp.isVisible()) return;
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.
rightDragComp.setEnabled(visible);
if (visible) {
rightDragComp.setVisible(true); // necessary because it is initially invisible
doLayout();
} else rightDragComp.setBounds(0, 0, 1, 1);
}
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()) {
case "ancestor":
if (e.getNewValue() != null) addNotify();
else removeNotify();
break;
case "resizable":
updateVisibility();
break;
}
}
// ---- interface ComponentListener ----
@Override
public void componentResized(ComponentEvent e) {
doLayout();
}
@Override
public void componentMoved(ComponentEvent e) {}
@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( FlatUIUtils.isFullScreen( resizeComp ) )
if (false) return false;
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 extends JComponent
implements MouseListener, MouseMotionListener {
private final int leadingResizeDir;
private final int centerResizeDir;
private final int trailingResizeDir;
private int resizeDir = -1;
private int leadingCornerDragWidth;
private int trailingCornerDragWidth;
// offsets of mouse position to window edges
private int dragLeftOffset;
private int dragRightOffset;
private int dragTopOffset;
private int dragBottomOffset;
protected DragBorderComponent(
int leadingResizeDir, int centerResizeDir, int trailingResizeDir) {
this.leadingResizeDir = leadingResizeDir;
this.centerResizeDir = centerResizeDir;
this.trailingResizeDir = trailingResizeDir;
setResizeDir(centerResizeDir);
setVisible(false);
addMouseListener(this);
addMouseMotionListener(this);
}
void setCornerDragWidths(int leading, int trailing) {
leadingCornerDragWidth = leading;
trailingCornerDragWidth = trailing;
}
protected void setResizeDir(int resizeDir) {
if (this.resizeDir == resizeDir) return;
this.resizeDir = resizeDir;
setCursor(getPredefinedCursor(resizeDir));
}
@Override
public Dimension getPreferredSize() {
int thickness = borderDragThickness;
return new Dimension(thickness, thickness);
}
@Override
protected void paintComponent(Graphics g) {
super.paintChildren(g);
// for dialogs: necessary because Dialog.setResizable() does not fire events
// for frames: necessary because GraphicsDevice.setFullScreenWindow() does not fire events
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, width - 1, height - 1 );
debug*/
}
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {
if (!isWindowResizable()) return;
int xOnScreen = e.getXOnScreen();
int yOnScreen = e.getYOnScreen();
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 mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mouseMoved(MouseEvent e) {
boolean topOrBottom =
(centerResizeDir == N_RESIZE_CURSOR || centerResizeDir == S_RESIZE_CURSOR);
int xy = topOrBottom ? e.getX() : e.getY();
int wh = topOrBottom ? getWidth() : getHeight();
setResizeDir(
xy <= leadingCornerDragWidth
? leadingResizeDir
: (xy >= wh - trailingCornerDragWidth ? trailingResizeDir : centerResizeDir));
}
@Override
public void mouseDragged(MouseEvent e) {
if (!isWindowResizable()) return;
int xOnScreen = e.getXOnScreen();
int yOnScreen = e.getYOnScreen();
// Get current window bounds and compute new bounds based 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
// decide at some point that the window should be only on second screen
// and adjusts its bounds.
Rectangle oldBounds = getWindowBounds();
Rectangle newBounds = new Rectangle(oldBounds);
// compute new window bounds
// top
if (resizeDir == N_RESIZE_CURSOR
|| resizeDir == NW_RESIZE_CURSOR
|| resizeDir == NE_RESIZE_CURSOR) {
newBounds.y = yOnScreen - dragTopOffset;
newBounds.height += (oldBounds.y - newBounds.y);
}
// bottom
if (resizeDir == S_RESIZE_CURSOR
|| resizeDir == SW_RESIZE_CURSOR
|| resizeDir == SE_RESIZE_CURSOR)
newBounds.height = (yOnScreen + dragBottomOffset) - newBounds.y;
// left
if (resizeDir == W_RESIZE_CURSOR
|| resizeDir == NW_RESIZE_CURSOR
|| resizeDir == SW_RESIZE_CURSOR) {
newBounds.x = xOnScreen - dragLeftOffset;
newBounds.width += (oldBounds.x - newBounds.x);
}
// right
if (resizeDir == E_RESIZE_CURSOR
|| resizeDir == NE_RESIZE_CURSOR
|| resizeDir == SE_RESIZE_CURSOR)
newBounds.width = (xOnScreen + dragRightOffset) - newBounds.x;
// apply minimum window size
Dimension minimumSize = honorMinimumSizeOnResize() ? getWindowMinimumSize() : null;
if (minimumSize == null) minimumSize = 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;
}
// set window bounds
if (!newBounds.equals(oldBounds)) setWindowBounds(newBounds);
}
}
}