From 24ff8ab8bb45fb7afcc6ece63bb543ba2edbd4ac Mon Sep 17 00:00:00 2001 From: Stian Grenborgen Date: Sun, 21 Jan 2024 11:36:09 +0100 Subject: [PATCH] The colony panel now has a non-rectangular shape when resizing. This avoids stealing mouse events that should be handled by the map instead (since a large part of the border is transparent). --- .../client/gui/panel/FreeColImageBorder.java | 34 ++++ .../gui/plaf/FreeColInternalFrameUI.java | 164 ++++++++++++++++++ .../client/gui/plaf/FreeColLookAndFeel.java | 1 + 3 files changed, 199 insertions(+) create mode 100644 src/net/sf/freecol/client/gui/plaf/FreeColInternalFrameUI.java diff --git a/src/net/sf/freecol/client/gui/panel/FreeColImageBorder.java b/src/net/sf/freecol/client/gui/panel/FreeColImageBorder.java index eb63aa4d9..0f4f790f4 100644 --- a/src/net/sf/freecol/client/gui/panel/FreeColImageBorder.java +++ b/src/net/sf/freecol/client/gui/panel/FreeColImageBorder.java @@ -27,6 +27,7 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; +import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; @@ -274,6 +275,39 @@ public class FreeColImageBorder extends AbstractBorder { ensureInitialized(); return getBorderInsets(c, null); } + + /** + * Returns spaces that are open on this border. + * + * @param c The component having the border. + * @return A list of areas that can be considered out-of-bounds for this border. + */ + public List getOpenSpace(Component c) { + ensureInitialized(); + + final List openSpace = new ArrayList<>(); + + /* + * For now, only the top part of the border is handled. Feel free to extend this method. + */ + + if (topStartImage != null && topStartImage.getHeight() < max(getHeight(topImage), getHeight(topEndImage))) { + final int openSpaceHeight = max(getHeight(topImage), getHeight(topEndImage)) - topStartImage.getHeight(); + openSpace.add(new Rectangle(0, 0, topStartImage.getWidth(), openSpaceHeight)); + } + if (topImage != null && topImage.getHeight() < max(getHeight(topStartImage), getHeight(topEndImage))) { + final int openSpaceHeight = max(getHeight(topStartImage), getHeight(topEndImage)) - topImage.getHeight(); + final int x = getWidth(topStartImage); + final int width = c.getWidth() - getWidth(topStartImage) - getWidth(topEndImage); + openSpace.add(new Rectangle(x, 0, width, openSpaceHeight)); + } + if (topEndImage != null && topEndImage.getHeight() < max(getHeight(topStartImage), getHeight(topImage))) { + final int openSpaceHeight = max(getHeight(topStartImage), getHeight(topImage)) - topEndImage.getHeight(); + openSpace.add(new Rectangle(c.getWidth() - topEndImage.getWidth(), 0, topEndImage.getWidth(), openSpaceHeight)); + } + + return openSpace; + } /** * Gets the insets of this border around the given component. diff --git a/src/net/sf/freecol/client/gui/plaf/FreeColInternalFrameUI.java b/src/net/sf/freecol/client/gui/plaf/FreeColInternalFrameUI.java new file mode 100644 index 000000000..19621d4e8 --- /dev/null +++ b/src/net/sf/freecol/client/gui/plaf/FreeColInternalFrameUI.java @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2002-2024 The FreeCol Team + * + * This file is part of FreeCol. + * + * FreeCol is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * FreeCol is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FreeCol. If not, see . + */ + +package net.sf.freecol.client.gui.plaf; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Rectangle; +import java.awt.event.MouseEvent; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JInternalFrame; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.event.MouseInputAdapter; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.metal.MetalInternalFrameUI; + +import net.sf.freecol.client.gui.panel.FreeColImageBorder; + +public class FreeColInternalFrameUI extends MetalInternalFrameUI { + + public FreeColInternalFrameUI(JInternalFrame b) { + super(b); + } + + public static ComponentUI createUI(JComponent b) { + return new FreeColInternalFrameUI((JInternalFrame)b); + } + + @Override + protected MouseInputAdapter createBorderListener(JInternalFrame w) { + return new FreeColBorderListener(); + } + + + /** + * Handles areas on the border that should be handled as out-of-bounds for + * the component. This allows a border to have a non-rectangular shape. + * + * Events on areas that are considered out-of-bounds are delegated to the + * internal frame's parent. + */ + private final class FreeColBorderListener extends BorderListener { + + private boolean resizing = false; + + @Override + public void mousePressed(MouseEvent e) { + if (isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + resizing = true; + super.mousePressed(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (!resizing && isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + resizing = false; + super.mouseReleased(e); + } + } + + @Override + public void mouseDragged(MouseEvent e) { + if (!resizing && isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + super.mouseDragged(e); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + if (!resizing && isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + super.mouseMoved(e); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (!resizing && isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + super.mouseClicked(e); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + if (!resizing && isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + super.mouseEntered(e); + } + } + + @Override + public void mouseExited(MouseEvent e) { + if (!resizing && isOutOfBounds(e)) { + handleOutOfBounds(e); + } else { + super.mouseExited(e); + } + } + + private void handleOutOfBounds(MouseEvent e) { + resetCursor(); + + if (frame.getParent() == null) { + return; + } + + dispatchTo(e, frame.getParent()); + } + + private void resetCursor() { + Cursor lastCursor = frame.getLastCursor(); + if (lastCursor == null) { + lastCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); + } + frame.setCursor(lastCursor); + } + + private boolean isOutOfBounds(MouseEvent e) { + final Border border = frame.getBorder(); + if (!(border instanceof FreeColImageBorder) ) { + return false; + } + + final List bounds = ((FreeColImageBorder) border).getOpenSpace(frame); + return bounds.stream().anyMatch(b -> b.contains(e.getPoint())); + } + + private void dispatchTo(MouseEvent e, Component target) { + final Component source = (Component) e.getSource(); + MouseEvent targetEvent = SwingUtilities.convertMouseEvent(source, e, target); + target.dispatchEvent(targetEvent); + } + } +} diff --git a/src/net/sf/freecol/client/gui/plaf/FreeColLookAndFeel.java b/src/net/sf/freecol/client/gui/plaf/FreeColLookAndFeel.java index 57fa4b7ef..ef71062a9 100644 --- a/src/net/sf/freecol/client/gui/plaf/FreeColLookAndFeel.java +++ b/src/net/sf/freecol/client/gui/plaf/FreeColLookAndFeel.java @@ -119,6 +119,7 @@ public class FreeColLookAndFeel extends MetalLookAndFeel { FreeColCheckBoxUI.class, FreeColComboBoxUI.class, FreeColFileChooserUI.class, + FreeColInternalFrameUI.class, FreeColLabelUI.class, FreeColListUI.class, FreeColMenuBarUI.class,