From 0a762081940d30cfef844b3ca88532a8d865403f Mon Sep 17 00:00:00 2001 From: piexel Date: Mon, 19 Oct 2026 18:21:27 +0200 Subject: [PATCH] Float the sidebar island over the map The map now fills the whole window area and the sidebar island floats over it, with the map showing through the gap around the island. Clicks in that gap reach the map. Dragging the island's right edge resizes it, always leaving the map some room. Model used: Claude Opus 5.5 --- .../volmit/iris/core/gui/IslandOverlay.java | 105 ++++++++++++++++++ .../com/volmit/iris/core/gui/IslandPanel.java | 29 ++++- .../com/volmit/iris/core/gui/MapWindow.java | 18 +-- 3 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/core/gui/IslandOverlay.java diff --git a/core/src/main/java/com/volmit/iris/core/gui/IslandOverlay.java b/core/src/main/java/com/volmit/iris/core/gui/IslandOverlay.java new file mode 100644 index 000000000..732346ea1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/gui/IslandOverlay.java @@ -0,0 +1,105 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + + +package com.volmit.iris.core.gui; + +import javax.swing.JComponent; +import javax.swing.JLayeredPane; +import java.awt.Cursor; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * Lays an island over content that fills the whole area behind it. Dragging the island's right edge resizes it, + * always leaving the content some room beside it. + */ +final class IslandOverlay extends JLayeredPane { + private static final long serialVersionUID = -4182209135360718342L; + + private final IslandPanel island; + private final JComponent background; + private final int minimumIslandWidth; + private final int minimumUncoveredWidth; + private int islandWidth; + + IslandOverlay(IslandPanel island, JComponent background, int islandWidth, int minimumIslandWidth, int minimumUncoveredWidth) { + this.island = island; + this.background = background; + this.islandWidth = islandWidth; + this.minimumIslandWidth = minimumIslandWidth; + this.minimumUncoveredWidth = minimumUncoveredWidth; + add(background, JLayeredPane.DEFAULT_LAYER); + add(island, JLayeredPane.PALETTE_LAYER); + installResizing(); + } + + @Override + public void doLayout() { + background.setBounds(0, 0, getWidth(), getHeight()); + island.setBounds(0, 0, clampedIslandWidth(), getHeight()); + } + + private int clampedIslandWidth() { + int widest = Math.max(minimumIslandWidth, getWidth() - minimumUncoveredWidth); + return Math.clamp(islandWidth, minimumIslandWidth, widest); + } + + private void installResizing() { + MouseAdapter resizer = new MouseAdapter() { + private boolean dragging; + + @Override + public void mouseMoved(MouseEvent event) { + island.setCursor(island.isOnResizeEdge(event.getX(), event.getY()) + ? Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR) + : Cursor.getDefaultCursor()); + } + + @Override + public void mouseExited(MouseEvent event) { + if (!dragging) { + island.setCursor(Cursor.getDefaultCursor()); + } + } + + @Override + public void mousePressed(MouseEvent event) { + dragging = island.isOnResizeEdge(event.getX(), event.getY()); + } + + @Override + public void mouseDragged(MouseEvent event) { + if (!dragging) { + return; + } + islandWidth = event.getX(); + islandWidth = clampedIslandWidth(); + revalidate(); + repaint(); + } + + @Override + public void mouseReleased(MouseEvent event) { + dragging = false; + } + }; + island.addMouseListener(resizer); + island.addMouseMotionListener(resizer); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/gui/IslandPanel.java b/core/src/main/java/com/volmit/iris/core/gui/IslandPanel.java index bd45b588b..9230cc771 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/IslandPanel.java +++ b/core/src/main/java/com/volmit/iris/core/gui/IslandPanel.java @@ -28,8 +28,9 @@ import java.awt.RenderingHints; import java.awt.geom.RoundRectangle2D; /** - * A rounded panel set apart from the window edges, like the tool windows of IntelliJ's islands theme. Its right edge - * meets a split pane divider, which provides the gap there. + * A rounded panel floating over the window content with a gap all around it, like the tool windows of IntelliJ's + * islands theme. The gap belongs to the content behind it, except for the strip along the right edge, which grabs the + * island to resize it. */ final class IslandPanel extends JPanel { private static final long serialVersionUID = 7313084027614358519L; @@ -40,21 +41,37 @@ final class IslandPanel extends JPanel { IslandPanel(JComponent content) { super(new BorderLayout()); setOpaque(false); - setBorder(StudioTheme.emptyBorder(GAP + INNER_PADDING, GAP + INNER_PADDING, GAP + INNER_PADDING, INNER_PADDING)); + int inset = GAP + INNER_PADDING; + setBorder(StudioTheme.emptyBorder(inset, inset, inset, inset)); add(content, BorderLayout.CENTER); } + boolean isOnResizeEdge(int x, int y) { + int edgeWidth = StudioTheme.scale(GAP + INNER_PADDING); + return x >= getWidth() - edgeWidth && x < getWidth() && y >= 0 && y < getHeight(); + } + + // Clicks in the gap reach the content behind the island instead of the island itself. + @Override + public boolean contains(int x, int y) { + return island().contains(x, y) || isOnResizeEdge(x, y); + } + @Override protected void paintComponent(Graphics graphics) { Graphics2D canvas = (Graphics2D) graphics.create(); try { canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); canvas.setColor(StudioTheme.PANEL_BACKGROUND); - int gap = StudioTheme.scale(GAP); - int arc = StudioTheme.scale(CORNER_ARC); - canvas.fill(new RoundRectangle2D.Double(gap, gap, getWidth() - gap, getHeight() - gap * 2D, arc, arc)); + canvas.fill(island()); } finally { canvas.dispose(); } } + + private RoundRectangle2D island() { + double gap = StudioTheme.scale(GAP); + double arc = StudioTheme.scale(CORNER_ARC); + return new RoundRectangle2D.Double(gap, gap, getWidth() - gap * 2D, getHeight() - gap * 2D, arc, arc); + } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java b/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java index bd0f01c83..cd3f2cf03 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java @@ -55,7 +55,6 @@ public final class MapWindow implements MapCanvas.Listener { private static final int SIDEBAR_WIDTH = 270; private static final int SIDEBAR_MINIMUM_WIDTH = 200; private static final int CONTENT_MINIMUM_WIDTH = 320; - private static final int SIDEBAR_DIVIDER_WIDTH = 6; private static final double PALETTE_RECENTER_DISTANCE_BLOCKS = 1D; private static final double VIEW_LIST_SHARE_WITH_PALETTES = 0.34D; @@ -117,7 +116,8 @@ public final class MapWindow implements MapCanvas.Listener { frame.setLayout(new BorderLayout()); frame.add(buildToolbar(), BorderLayout.NORTH); - frame.add(buildSidebarSplit(), BorderLayout.CENTER); + frame.add(new IslandOverlay(new IslandPanel(sidebar), content, StudioTheme.scale(SIDEBAR_WIDTH), + StudioTheme.scale(SIDEBAR_MINIMUM_WIDTH), StudioTheme.scale(CONTENT_MINIMUM_WIDTH)), BorderLayout.CENTER); frame.add(inspectorSlot, BorderLayout.EAST); frame.add(statusBars, BorderLayout.SOUTH); installShortcuts(frame.getRootPane()); @@ -226,20 +226,6 @@ public final class MapWindow implements MapCanvas.Listener { } } - // Dragging the gap beside the sidebar island makes it wider, so long palette names fit. - private JSplitPane buildSidebarSplit() { - IslandPanel island = new IslandPanel(sidebar); - island.setMinimumSize(StudioTheme.scaledSize(SIDEBAR_MINIMUM_WIDTH, 0)); - content.setMinimumSize(StudioTheme.scaledSize(CONTENT_MINIMUM_WIDTH, 0)); - JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, island, content); - split.setBorder(BorderFactory.createEmptyBorder()); - split.setBackground(StudioTheme.EDITOR_BACKGROUND); - split.setDividerSize(StudioTheme.scale(SIDEBAR_DIVIDER_WIDTH)); - split.setResizeWeight(0D); - split.setDividerLocation(StudioTheme.scale(SIDEBAR_WIDTH)); - return split; - } - // The palettes need the room more than the view list, which scrolls once it is squeezed. private int viewListHeightBesidePalettes() { int fullHeight = viewList.getPreferredSize().height + 4;