diff --git a/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java b/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java index 2abd9b6bc..3bb925423 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java +++ b/core/src/main/java/com/volmit/iris/core/gui/GuiHost.java @@ -31,8 +31,10 @@ import org.bukkit.event.Listener; import javax.swing.JFrame; import java.awt.Desktop; +import java.awt.Dimension; import java.awt.EventQueue; import java.awt.GraphicsEnvironment; +import java.awt.Rectangle; import java.awt.desktop.QuitResponse; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -73,6 +75,12 @@ public final class GuiHost { return frame; } + // A scaled-up default size can be bigger than a small or low resolution screen. + static Dimension fitToScreen(Dimension preferred) { + Rectangle usable = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); + return new Dimension(Math.min(preferred.width, usable.width), Math.min(preferred.height, usable.height)); + } + public static Engine findActiveEngine() { try { for (World world : new ArrayList<>(Bukkit.getWorlds())) { diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java b/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java index abb06cc53..ef03c1f6b 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java @@ -529,7 +529,7 @@ final class MapCanvas extends JPanel { for (GuiMarker marker : entities) { int screenX = (int) Math.round(worldToScreenX(marker.worldX())); int screenY = (int) Math.round(worldToScreenZ(marker.worldZ())); - canvas.fillOval(screenX - 3, screenY - 3, 6, 6); + fillDot(canvas, screenX, screenY, 3); } } @@ -538,18 +538,23 @@ final class MapCanvas extends JPanel { int screenX = (int) Math.round(worldToScreenX(marker.worldX())); int screenY = (int) Math.round(worldToScreenZ(marker.worldZ())); canvas.setColor(PLAYER_HALO); - canvas.fillOval(screenX - 12, screenY - 12, 24, 24); + fillDot(canvas, screenX, screenY, 12); canvas.setColor(PLAYER_COLOR); - canvas.fillOval(screenX - 5, screenY - 5, 10, 10); + fillDot(canvas, screenX, screenY, 5); canvas.setColor(StudioTheme.TEXT); int labelWidth = canvas.getFontMetrics().stringWidth(marker.label()); - canvas.drawString(marker.label(), screenX - labelWidth / 2, screenY - 15); + canvas.drawString(marker.label(), screenX - labelWidth / 2, screenY - StudioTheme.scale(15)); } if (entitiesVisible && detailedHover) { renderNearestEntity(canvas); } } + private static void fillDot(Graphics2D canvas, int centerX, int centerY, int logicalRadius) { + int radius = StudioTheme.scale(logicalRadius); + canvas.fillOval(centerX - radius, centerY - radius, radius * 2, radius * 2); + } + private void renderNearestEntity(Graphics2D canvas) { Point point = cursor; if (point == null || entities.isEmpty()) { @@ -577,34 +582,45 @@ final class MapCanvas extends JPanel { if (nearest.maxHealth() > 0D) { lines.add("Health " + Form.f(nearest.health(), 1) + " / " + Form.f(nearest.maxHealth(), 1)); } - drawCard(canvas, getWidth() - CARD_PADDING, CARD_PADDING, 1D, 0D, lines); + int padding = StudioTheme.scale(CARD_PADDING); + drawCard(canvas, getWidth() - padding, padding, 1D, 0D, lines); } private void renderLegend(Graphics2D canvas) { if (currentType != RenderType.HEIGHT) { return; } - int width = 244; - int height = 54; - int x = getWidth() - width - CARD_PADDING; - int y = getHeight() - height - CARD_PADDING; + canvas.setFont(StudioTheme.smallFont()); + int padding = StudioTheme.scale(CARD_PADDING); + int gradientHeight = StudioTheme.scale(14); + int labelGap = StudioTheme.scale(4); + int width = StudioTheme.scale(244); + int height = padding * 2 + gradientHeight + labelGap + canvas.getFontMetrics().getHeight(); + int x = getWidth() - width - padding; + int y = getHeight() - height - padding; StudioOverlay.drawCard(canvas, x, y, width, height); - int gradientX = x + CARD_PADDING; - int gradientY = y + CARD_PADDING; - int gradientWidth = width - CARD_PADDING * 2; + int gradientX = x + padding; + int gradientY = y + padding; + int gradientWidth = width - padding * 2; for (int pixel = 0; pixel < gradientWidth; pixel++) { double sampleHeight = heightMaximum * pixel / Math.max(1D, gradientWidth - 1D); canvas.setColor(new Color(IrisRenderer.heightColor(sampleHeight, heightMaximum, fluidHeight))); - canvas.drawLine(gradientX + pixel, gradientY, gradientX + pixel, gradientY + 14); + canvas.drawLine(gradientX + pixel, gradientY, gradientX + pixel, gradientY + gradientHeight); } - canvas.setFont(StudioTheme.smallFont()); canvas.setColor(StudioTheme.SECONDARY_TEXT); - canvas.drawString("0", gradientX, gradientY + 32); + int labelBaseline = gradientY + gradientHeight + labelGap + canvas.getFontMetrics().getAscent(); + String minimum = "0"; String fluid = Integer.toString((int) Math.round(fluidHeight)); - int fluidX = gradientX + (int) Math.round(gradientWidth * fluidHeight / Math.max(1D, heightMaximum)); - canvas.drawString(fluid, Math.max(gradientX, fluidX - canvas.getFontMetrics().stringWidth(fluid) / 2), gradientY + 32); String maximum = Integer.toString((int) Math.round(heightMaximum)); - canvas.drawString(maximum, gradientX + gradientWidth - canvas.getFontMetrics().stringWidth(maximum), gradientY + 32); + int minimumRight = gradientX + canvas.getFontMetrics().stringWidth(minimum); + int maximumX = gradientX + gradientWidth - canvas.getFontMetrics().stringWidth(maximum); + int fluidWidth = canvas.getFontMetrics().stringWidth(fluid); + int fluidX = gradientX + (int) Math.round(gradientWidth * fluidHeight / Math.max(1D, heightMaximum)) - fluidWidth / 2; + canvas.drawString(minimum, gradientX, labelBaseline); + if (fluidX > minimumRight + labelGap && fluidX + fluidWidth < maximumX - labelGap) { + canvas.drawString(fluid, fluidX, labelBaseline); + } + canvas.drawString(maximum, maximumX, labelBaseline); } private void renderHover(Graphics2D canvas) { @@ -624,7 +640,7 @@ final class MapCanvas extends JPanel { } else { lines.add("Hold Shift for more, double-click for palettes"); } - int screenX = (int) Math.round(worldToScreenX(info.worldX())) + 16; + int screenX = (int) Math.round(worldToScreenX(info.worldX())) + StudioTheme.scale(16); int screenY = (int) Math.round(worldToScreenZ(info.worldZ())); drawCard(canvas, screenX, screenY, 0D, 0D, lines); } @@ -659,18 +675,20 @@ final class MapCanvas extends JPanel { for (String description : descriptions) { descriptionWidth = Math.max(descriptionWidth, canvas.getFontMetrics().stringWidth(description)); } - int lineHeight = 22; - int width = keyWidth + descriptionWidth + CARD_PADDING * 3 + 8; - int height = keys.size() * lineHeight + CARD_PADDING * 2; - StudioOverlay.drawCard(canvas, CARD_PADDING, CARD_PADDING, width, height); + int padding = StudioTheme.scale(CARD_PADDING); + int columnGap = StudioTheme.scale(18); + int lineHeight = canvas.getFontMetrics().getHeight() + StudioTheme.scale(4); + int width = keyWidth + columnGap + descriptionWidth + padding * 2; + int height = keys.size() * lineHeight + padding * 2; + StudioOverlay.drawCard(canvas, padding, padding, width, height); for (int index = 0; index < keys.size(); index++) { - int y = CARD_PADDING * 2 + 10 + index * lineHeight; + int y = padding * 2 + canvas.getFontMetrics().getAscent() + index * lineHeight; canvas.setFont(StudioTheme.boldFont()); canvas.setColor(StudioTheme.LINK); - canvas.drawString(keys.get(index), CARD_PADDING * 2, y); + canvas.drawString(keys.get(index), padding * 2, y); canvas.setFont(StudioTheme.font()); canvas.setColor(StudioTheme.SECONDARY_TEXT); - canvas.drawString(descriptions.get(index), CARD_PADDING * 2 + keyWidth + 18, y); + canvas.drawString(descriptions.get(index), padding * 2 + keyWidth + columnGap, y); } } @@ -682,7 +700,8 @@ final class MapCanvas extends JPanel { lines.add("Render threads busy " + progress.activeWorkers()); lines.add("Tile scale " + (frame == null ? "-" : Form.f(frame.renderBlocksPerPixel(), 0)) + " blocks/px"); lines.add(paintCadenceFps + " paints per second"); - drawCard(canvas, CARD_PADDING, getHeight() - CARD_PADDING, 0D, 1D, lines); + int padding = StudioTheme.scale(CARD_PADDING); + drawCard(canvas, padding, getHeight() - padding, 0D, 1D, lines); } private void renderNotifications(Graphics2D canvas) { @@ -692,13 +711,14 @@ final class MapCanvas extends JPanel { String text = String.join(" · ", notifications.keySet()); canvas.setFont(StudioTheme.font()); int textWidth = canvas.getFontMetrics().stringWidth(text); - int width = textWidth + CARD_PADDING * 2; - int height = 34; + int padding = StudioTheme.scale(CARD_PADDING); + int width = textWidth + padding * 2; + int height = canvas.getFontMetrics().getHeight() + padding; int x = (getWidth() - width) / 2; - int y = getHeight() - height - 16; + int y = getHeight() - height - StudioTheme.scale(16); StudioOverlay.drawCard(canvas, x, y, width, height); canvas.setColor(StudioTheme.TEXT); - canvas.drawString(text, x + CARD_PADDING, y + 22); + canvas.drawString(text, x + padding, y + padding / 2 + canvas.getFontMetrics().getAscent()); } private void drawCard(Graphics2D canvas, int anchorX, int anchorY, double pushX, double pushY, List lines) { @@ -709,18 +729,19 @@ final class MapCanvas extends JPanel { canvas.setFont(index == 0 ? StudioTheme.boldFont() : StudioTheme.font()); width = Math.max(width, canvas.getFontMetrics().stringWidth(lines.get(index))); } - width += CARD_PADDING * 2; - int height = lines.size() * lineHeight + CARD_PADDING * 2 - 2; + int padding = StudioTheme.scale(CARD_PADDING); + width += padding * 2; + int height = lines.size() * lineHeight + padding * 2; int x = (int) Math.round(anchorX - width * pushX); int y = (int) Math.round(anchorY - height * pushY); - x = Math.max(CARD_PADDING, Math.min(getWidth() - width - CARD_PADDING, x)); - y = Math.max(CARD_PADDING, Math.min(getHeight() - height - CARD_PADDING, y)); + x = Math.max(padding, Math.min(getWidth() - width - padding, x)); + y = Math.max(padding, Math.min(getHeight() - height - padding, y)); StudioOverlay.drawCard(canvas, x, y, width, height); for (int index = 0; index < lines.size(); index++) { boolean title = index == 0; canvas.setFont(title ? StudioTheme.boldFont() : StudioTheme.font()); canvas.setColor(title ? StudioTheme.TEXT : StudioTheme.SECONDARY_TEXT); - canvas.drawString(lines.get(index), x + CARD_PADDING, y + CARD_PADDING + lineHeight - 3 + index * lineHeight); + canvas.drawString(lines.get(index), x + padding, y + padding + canvas.getFontMetrics().getAscent() + index * lineHeight); } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapViewList.java b/core/src/main/java/com/volmit/iris/core/gui/MapViewList.java index c298376fb..e70eea3e4 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapViewList.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapViewList.java @@ -44,9 +44,9 @@ final class MapViewList extends JList { fillRows(); setModel(rows); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - setFixedCellHeight(ROW_HEIGHT); + setFixedCellHeight(StudioTheme.scale(ROW_HEIGHT)); setCellRenderer(new RowRenderer()); - setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6)); + setBorder(StudioTheme.emptyBorder(4, 6, 4, 6)); addListSelectionListener(event -> { if (event.getValueIsAdjusting() || skipHeaderRow()) { return; @@ -133,13 +133,13 @@ final class MapViewList extends JList { setText(row.group().label().toUpperCase(Locale.ROOT)); setFont(StudioTheme.sectionFont()); setForeground(StudioTheme.MUTED_TEXT); - setBorder(BorderFactory.createEmptyBorder(index == 0 ? 0 : 6, 6, 0, 6)); + setBorder(StudioTheme.emptyBorder(index == 0 ? 0 : 6, 6, 0, 6)); setToolTipText(null); shortcut = null; return this; } setFont(StudioTheme.font()); - setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + setBorder(StudioTheme.emptyBorder(0, 10, 0, 10)); setToolTipText(row.view().description()); shortcut = row.view().shortcutText(); return this; 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 592aadda4..445176988 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 @@ -93,6 +93,7 @@ public final class MapWindow implements MapCanvas.Listener { } private static void open(PaletteExplorerStart start, MapView initialView) { + StudioTheme.install(); MapWindow window = new MapWindow(start); window.frame.setVisible(true); window.canvas.start(); @@ -102,7 +103,7 @@ public final class MapWindow implements MapCanvas.Listener { private void buildFrame() { content.add(canvas, MAP_CARD); statusBars.add(canvas.statusBar(), MAP_CARD); - sidebar.setPreferredSize(new Dimension(SIDEBAR_WIDTH, 0)); + sidebar.setPreferredSize(StudioTheme.scaledSize(SIDEBAR_WIDTH, 0)); sidebar.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, StudioTheme.SEPARATOR)); sidebar.add(viewListScroll, BorderLayout.CENTER); @@ -113,8 +114,8 @@ public final class MapWindow implements MapCanvas.Listener { frame.add(inspectorSlot, BorderLayout.EAST); frame.add(statusBars, BorderLayout.SOUTH); installShortcuts(frame.getRootPane()); - frame.setSize(1440, 880); - frame.setMinimumSize(new Dimension(820, 520)); + frame.setSize(GuiHost.fitToScreen(StudioTheme.scaledSize(1440, 880))); + frame.setMinimumSize(StudioTheme.scaledSize(820, 520)); frame.setLocationRelativeTo(null); frame.addWindowListener(new WindowAdapter() { @Override @@ -127,7 +128,7 @@ public final class MapWindow implements MapCanvas.Listener { private JToolBar buildToolbar() { JToolBar toolbar = StudioToolbar.create(); viewTitle.setFont(StudioTheme.titleFont()); - viewTitle.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 14)); + viewTitle.setBorder(StudioTheme.emptyBorder(0, 6, 0, 14)); toolbar.add(viewTitle); mapControls.add(StudioToolbar.button("Refresh", "Redraw the map with the latest pack files (R)", canvas::refreshContent)); diff --git a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java index 8d9904c18..c19868931 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java +++ b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java @@ -88,7 +88,6 @@ public final class NoiseExplorerGUI extends JPanel { private static final Color CROSSHAIR = new Color(255, 255, 255, 125); private static final int SIDEBAR_WIDTH = 270; private static final int LEGEND_WIDTH = 190; - private static final int LEGEND_HEIGHT = 40; private static final int REFINEMENT_DELAY_MILLIS = 180; private static final long DEFAULT_SEED = 12345L; private static final double TEXTURE_PIXELS_MAX_BLOCKS_PER_PIXEL = 0.25D; @@ -212,6 +211,7 @@ public final class NoiseExplorerGUI extends JPanel { public static void launch() { Engine engine = GuiHost.findActiveEngine(); EventQueue.invokeLater(() -> { + StudioTheme.install(); NoiseExplorerGUI explorer = new NoiseExplorerGUI(ExplorerMode.NOISE, engine, null, null, null, DEFAULT_SEED, null); buildNoiseFrame("Noise Explorer", explorer); }); @@ -221,6 +221,7 @@ public final class NoiseExplorerGUI extends JPanel { Engine engine = GuiHost.findActiveEngine(); String displayName = generatorKey == null || generatorKey.isBlank() ? "Custom Generator" : generatorKey; EventQueue.invokeLater(() -> { + StudioTheme.install(); NoiseExplorerGUI explorer = new NoiseExplorerGUI( ExplorerMode.NOISE, engine, @@ -253,8 +254,8 @@ public final class NoiseExplorerGUI extends JPanel { explorer.resetViewport(); } }); - frame.setSize(1440, 860); - frame.setMinimumSize(new Dimension(760, 500)); + frame.setSize(GuiHost.fitToScreen(StudioTheme.scaledSize(1440, 860))); + frame.setMinimumSize(StudioTheme.scaledSize(760, 500)); frame.setLocationRelativeTo(null); frame.addWindowListener(new WindowAdapter() { @Override @@ -295,7 +296,7 @@ public final class NoiseExplorerGUI extends JPanel { private JToolBar buildNoiseToolbar() { JToolBar toolbar = StudioToolbar.create(); selectedLabel.setFont(StudioTheme.boldFont()); - selectedLabel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 10)); + selectedLabel.setBorder(StudioTheme.emptyBorder(0, 4, 0, 10)); toolbar.add(selectedLabel); toolbar.addSeparator(); toolbar.add(StudioToolbar.label("Seed")); @@ -322,7 +323,7 @@ public final class NoiseExplorerGUI extends JPanel { private JPanel buildNoiseSidebar() { JPanel sidebar = new JPanel(new BorderLayout()); - sidebar.setPreferredSize(new Dimension(SIDEBAR_WIDTH, 0)); + sidebar.setPreferredSize(StudioTheme.scaledSize(SIDEBAR_WIDTH, 0)); sidebar.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, StudioTheme.SEPARATOR)); StudioToolbar.makeSearchField(searchField, "Search noise..."); searchField.getDocument().addDocumentListener(new DocumentListener() { @@ -1186,33 +1187,42 @@ public final class NoiseExplorerGUI extends JPanel { int centerZ = height / 2; canvas.setColor(CROSSHAIR); canvas.setStroke(new BasicStroke(1F)); - canvas.drawLine(centerX - 8, centerZ, centerX + 8, centerZ); - canvas.drawLine(centerX, centerZ - 8, centerX, centerZ + 8); + int armLength = StudioTheme.scale(8); + canvas.drawLine(centerX - armLength, centerZ, centerX + armLength, centerZ); + canvas.drawLine(centerX, centerZ - armLength, centerX, centerZ + armLength); } private void drawLegend(Graphics2D canvas, int width) { NoiseColorScale colorScale = activeColorScale(); - int legendX = Math.max(10, width - LEGEND_WIDTH - 12); - int legendY = 12; + canvas.setFont(StudioTheme.smallFont()); + String minimum = formatNumber(colorScale.minimum()); + String maximum = formatNumber(colorScale.maximum()); + String label = colorScale.label(); + int padding = StudioTheme.scale(9); + int labelGap = StudioTheme.scale(12); + int gradientHeight = StudioTheme.scale(8); + int textWidth = canvas.getFontMetrics().stringWidth(minimum) + canvas.getFontMetrics().stringWidth(label) + + canvas.getFontMetrics().stringWidth(maximum) + labelGap * 2; + int legendWidth = Math.max(StudioTheme.scale(LEGEND_WIDTH), textWidth + padding * 2); + int legendHeight = padding * 2 + gradientHeight + StudioTheme.scale(4) + canvas.getFontMetrics().getHeight(); + int legendX = Math.max(StudioTheme.scale(10), width - legendWidth - StudioTheme.scale(12)); + int legendY = StudioTheme.scale(12); canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - StudioOverlay.drawCard(canvas, legendX, legendY, LEGEND_WIDTH, LEGEND_HEIGHT); - int gradientX = legendX + 9; - int gradientY = legendY + 8; - int gradientWidth = LEGEND_WIDTH - 18; + StudioOverlay.drawCard(canvas, legendX, legendY, legendWidth, legendHeight); + int gradientX = legendX + padding; + int gradientY = legendY + padding; + int gradientWidth = legendWidth - padding * 2; for (int x = 0; x < gradientWidth; x++) { double normalized = x / (double) Math.max(1, gradientWidth - 1); canvas.setColor(colorScale.displayColorNormalized(normalized)); - canvas.drawLine(gradientX + x, gradientY, gradientX + x, gradientY + 8); + canvas.drawLine(gradientX + x, gradientY, gradientX + x, gradientY + gradientHeight); } - canvas.setFont(StudioTheme.smallFont()); + int baseline = gradientY + gradientHeight + StudioTheme.scale(4) + canvas.getFontMetrics().getAscent(); canvas.setColor(StudioTheme.TEXT); - canvas.drawString(formatNumber(colorScale.minimum()), gradientX, legendY + 31); - String maximum = formatNumber(colorScale.maximum()); - int maximumWidth = canvas.getFontMetrics().stringWidth(maximum); - canvas.drawString(maximum, gradientX + gradientWidth - maximumWidth, legendY + 31); - String label = colorScale.label(); + canvas.drawString(minimum, gradientX, baseline); + canvas.drawString(maximum, gradientX + gradientWidth - canvas.getFontMetrics().stringWidth(maximum), baseline); int labelWidth = canvas.getFontMetrics().stringWidth(label); - canvas.drawString(label, legendX + ((LEGEND_WIDTH - labelWidth) / 2), legendY + 31); + canvas.drawString(label, legendX + (legendWidth - labelWidth) / 2, baseline); } private void refreshStatus() { @@ -1513,10 +1523,10 @@ public final class NoiseExplorerGUI extends JPanel { if (header) { setFont(StudioTheme.sectionFont()); setForeground(StudioTheme.MUTED_TEXT); - setBorder(BorderFactory.createEmptyBorder(12, 12, 4, 12)); + setBorder(StudioTheme.emptyBorder(12, 12, 4, 12)); } else { setFont(StudioTheme.font()); - setBorder(BorderFactory.createEmptyBorder(3, 20, 3, 12)); + setBorder(StudioTheme.emptyBorder(3, 20, 3, 12)); } setHorizontalAlignment(SwingConstants.LEFT); return this; diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java index 1d499f240..306ed5809 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java @@ -51,7 +51,7 @@ final class PaletteInspectorPanel extends JPanel { private final JLabel depthLabel = new JLabel("", SwingConstants.CENTER); private final JButton shallowerButton = new JButton("‹"); private final JButton deeperButton = new JButton("›"); - private final JPanel depthRow = new JPanel(new BorderLayout(6, 0)); + private final JPanel depthRow = new JPanel(new BorderLayout(StudioTheme.scale(6), 0)); private final StatisticsView statisticsView = new StatisticsView(); private PaletteLayerScale.View selectedView = PaletteLayerScale.View.BLOCKS; private int depth; @@ -60,7 +60,7 @@ final class PaletteInspectorPanel extends JPanel { PaletteInspectorPanel(Listener listener) { super(new BorderLayout()); this.listener = listener; - setPreferredSize(new Dimension(PANEL_WIDTH, 0)); + setPreferredSize(StudioTheme.scaledSize(PANEL_WIDTH, 0)); setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, StudioTheme.SEPARATOR)); add(buildControls(), BorderLayout.NORTH); add(StudioToolbar.scroll(statisticsView), BorderLayout.CENTER); @@ -91,10 +91,10 @@ final class PaletteInspectorPanel extends JPanel { } private JPanel buildControls() { - JPanel controls = new JPanel(new GridLayout(0, 1, 0, 6)); + JPanel controls = new JPanel(new GridLayout(0, 1, 0, StudioTheme.scale(6))); controls.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder(0, 0, 1, 0, StudioTheme.SEPARATOR), - BorderFactory.createEmptyBorder(12, 14, 12, 14) + StudioTheme.emptyBorder(12, 14, 12, 14) )); titleLabel.setFont(StudioTheme.titleFont()); detailLabel.setForeground(StudioTheme.SECONDARY_TEXT); @@ -110,7 +110,7 @@ final class PaletteInspectorPanel extends JPanel { } private JPanel buildViewButtons() { - JPanel row = new JPanel(new GridLayout(1, 0, 4, 0)); + JPanel row = new JPanel(new GridLayout(1, 0, StudioTheme.scale(4), 0)); row.setOpaque(false); ButtonGroup group = new ButtonGroup(); for (PaletteLayerScale.View view : PaletteLayerScale.View.values()) { @@ -198,11 +198,11 @@ final class PaletteInspectorPanel extends JPanel { private static final int MARGIN = 14; private static final int HISTOGRAM_HEIGHT = 84; private static final int BAND_HEIGHT = 12; - private static final int ROW_HEIGHT = 20; - private static final int SWATCH_SIZE = 14; - private static final int WEIGHT_COLUMN_WIDTH = 48; - private static final int ACTUAL_COLUMN_WIDTH = 70; - private static final int NOTE_LINE_HEIGHT = 15; + private static final int SECTION_GAP = 18; + private static final int TITLE_GAP = 6; + private static final int NOTE_GAP = 4; + private static final String WIDEST_WEIGHT = "100.0%"; + private static final String WIDEST_ACTUAL = "▲ 100.0%"; private PaletteLayerPreview preview; private String textureStatus; @@ -226,19 +226,15 @@ final class PaletteInspectorPanel extends JPanel { @Override public Dimension getPreferredSize() { + int preferredWidth = StudioTheme.scale(PANEL_WIDTH - 20); if (preview == null) { - return new Dimension(PANEL_WIDTH - 20, 0); + return new Dimension(preferredWidth, 0); } - int width = Math.max(getWidth(), PANEL_WIDTH - 20) - MARGIN * 2; - FontMetrics noteMetrics = getFontMetrics(StudioTheme.smallFont()); - int noteLines = 0; - for (String note : notes()) { - noteLines += wrap(note, noteMetrics, width).size() + 1; - } - int height = MARGIN + 18 + HISTOGRAM_HEIGHT + BAND_HEIGHT + 30 - + 22 + ROW_HEIGHT * (preview.entries().size() + 1) - + 16 + noteLines * NOTE_LINE_HEIGHT + MARGIN; - return new Dimension(PANEL_WIDTH - 20, height); + int margin = StudioTheme.scale(MARGIN); + int width = Math.max(getWidth(), preferredWidth) - margin * 2; + int height = margin + valueSpreadHeight() + StudioTheme.scale(SECTION_GAP) + blockTableHeight() + + StudioTheme.scale(SECTION_GAP) + notesHeight(width) + margin; + return new Dimension(preferredWidth, height); } @Override @@ -252,18 +248,58 @@ final class PaletteInspectorPanel extends JPanel { if (preview == null) { return; } - int width = getWidth() - MARGIN * 2; - int y = drawValueSpread(canvas, MARGIN, MARGIN, width); - y = drawBlockTable(canvas, MARGIN, y + 22, width); - drawNotes(canvas, MARGIN, y + 16, width); + int margin = StudioTheme.scale(MARGIN); + int width = getWidth() - margin * 2; + int top = margin; + drawValueSpread(canvas, margin, top, width); + top += valueSpreadHeight() + StudioTheme.scale(SECTION_GAP); + drawBlockTable(canvas, margin, top, width); + top += blockTableHeight() + StudioTheme.scale(SECTION_GAP); + drawNotes(canvas, margin, top, width); } finally { canvas.dispose(); } } - private int drawValueSpread(Graphics2D canvas, int x, int y, int width) { - drawSectionTitle(canvas, "Noise values this layer produces", x, y + 10); - int histogramTop = y + 18; + private int valueSpreadHeight() { + return sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP) + StudioTheme.scale(HISTOGRAM_HEIGHT) + + StudioTheme.scale(2) + StudioTheme.scale(BAND_HEIGHT) + StudioTheme.scale(3) + smallMetrics().getHeight(); + } + + private int blockTableHeight() { + return sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP) + rowHeight() * preview.entries().size(); + } + + private int notesHeight(int width) { + FontMetrics metrics = smallMetrics(); + int height = 0; + for (String note : notes()) { + height += wrap(note, metrics, width).size() * metrics.getHeight() + StudioTheme.scale(NOTE_GAP); + } + return height; + } + + private int rowHeight() { + return textMetrics().getHeight() + StudioTheme.scale(4); + } + + private FontMetrics textMetrics() { + return getFontMetrics(StudioTheme.font()); + } + + private FontMetrics smallMetrics() { + return getFontMetrics(StudioTheme.smallFont()); + } + + private FontMetrics sectionMetrics() { + return getFontMetrics(StudioTheme.sectionFont()); + } + + private void drawValueSpread(Graphics2D canvas, int x, int top, int width) { + drawSectionTitle(canvas, "Noise values this layer produces", x, top + sectionMetrics().getAscent()); + int histogramTop = top + sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP); + int histogramHeight = StudioTheme.scale(HISTOGRAM_HEIGHT); + int bandHeight = StudioTheme.scale(BAND_HEIGHT); long[] histogram = preview.histogram(); long peak = 1L; for (long count : histogram) { @@ -273,11 +309,11 @@ final class PaletteInspectorPanel extends JPanel { for (int bin = 0; bin < histogram.length; bin++) { int barLeft = x + (bin * width) / histogram.length; int barRight = x + ((bin + 1) * width) / histogram.length; - int barHeight = (int) Math.round(HISTOGRAM_HEIGHT * (histogram[bin] / (double) peak)); - canvas.fillRect(barLeft, histogramTop + HISTOGRAM_HEIGHT - barHeight, Math.max(1, barRight - barLeft - 1), barHeight); + int barHeight = (int) Math.round(histogramHeight * (histogram[bin] / (double) peak)); + canvas.fillRect(barLeft, histogramTop + histogramHeight - barHeight, Math.max(1, barRight - barLeft - 1), barHeight); } - int bandTop = histogramTop + HISTOGRAM_HEIGHT + 2; + int bandTop = histogramTop + histogramHeight + StudioTheme.scale(2); for (PaletteLayerPreview.Entry entry : preview.entries()) { if (Double.isNaN(entry.lowestNoise())) { continue; @@ -285,67 +321,73 @@ final class PaletteInspectorPanel extends JPanel { int bandLeft = x + (int) Math.round(entry.lowestNoise() * width); int bandRight = x + (int) Math.round(entry.highestNoise() * width); canvas.setColor(new Color(entry.color())); - canvas.fillRect(bandLeft, bandTop, Math.max(1, bandRight - bandLeft), BAND_HEIGHT); + canvas.fillRect(bandLeft, bandTop, Math.max(1, bandRight - bandLeft), bandHeight); if (entry.highestNoise() < 1D) { canvas.setColor(BAND_DIVIDER); - canvas.drawLine(bandRight, histogramTop, bandRight, bandTop + BAND_HEIGHT - 1); + canvas.drawLine(bandRight, histogramTop, bandRight, bandTop + bandHeight - 1); } } canvas.setFont(StudioTheme.smallFont()); canvas.setColor(StudioTheme.SECONDARY_TEXT); FontMetrics metrics = canvas.getFontMetrics(); - int labelY = bandTop + BAND_HEIGHT + 13; + int labelY = bandTop + bandHeight + StudioTheme.scale(3) + metrics.getAscent(); canvas.drawString("0", x, labelY); canvas.drawString("0.5", x + (width - metrics.stringWidth("0.5")) / 2, labelY); canvas.drawString("1", x + width - metrics.stringWidth("1"), labelY); - return labelY; } - private int drawBlockTable(Graphics2D canvas, int x, int y, int width) { + private void drawBlockTable(Graphics2D canvas, int x, int top, int width) { + int columnGap = StudioTheme.scale(10); int actualRight = x + width; - int weightRight = actualRight - ACTUAL_COLUMN_WIDTH; - drawSectionTitle(canvas, "Blocks", x, y); - drawRightAligned(canvas, "weight", weightRight, y); - drawRightAligned(canvas, "actual", actualRight, y); + int weightRight = actualRight - textMetrics().stringWidth(WIDEST_ACTUAL) - columnGap; + int nameRight = weightRight - textMetrics().stringWidth(WIDEST_WEIGHT) - columnGap; + int titleBaseline = top + sectionMetrics().getAscent(); + drawSectionTitle(canvas, "Blocks", x, titleBaseline); + drawRightAligned(canvas, "weight", weightRight, titleBaseline); + drawRightAligned(canvas, "actual", actualRight, titleBaseline); - int rowY = y + ROW_HEIGHT; + int rowHeight = rowHeight(); + FontMetrics text = textMetrics(); + int swatchSize = text.getAscent() - text.getDescent(); + int rowTop = top + sectionMetrics().getHeight() + StudioTheme.scale(TITLE_GAP); for (PaletteLayerPreview.Entry entry : preview.entries()) { - drawSwatch(canvas, entry, x, rowY - SWATCH_SIZE + 2); + int baseline = rowTop + (rowHeight + text.getAscent() - text.getDescent()) / 2; + drawSwatch(canvas, entry, x, rowTop + (rowHeight - swatchSize) / 2, swatchSize); canvas.setFont(StudioTheme.font()); canvas.setColor(entry.found() ? StudioTheme.TEXT : StudioTheme.ERROR); String name = entry.found() ? shortBlockName(entry.block()) : shortBlockName(entry.block()) + " (not found)"; - int nameLeft = x + SWATCH_SIZE + 7; - canvas.drawString(fit(canvas, name, weightRight - WEIGHT_COLUMN_WIDTH - nameLeft), nameLeft, rowY); + int nameLeft = x + swatchSize + StudioTheme.scale(7); + canvas.drawString(fit(canvas, name, nameRight - nameLeft), nameLeft, baseline); canvas.setColor(StudioTheme.SECONDARY_TEXT); - drawRightAligned(canvas, percent(entry.weightShare()), weightRight, rowY); + drawRightAligned(canvas, percent(entry.weightShare()), weightRight, baseline); canvas.setColor(actualColor(entry)); - drawRightAligned(canvas, actualText(entry), actualRight, rowY); - rowY += ROW_HEIGHT; + drawRightAligned(canvas, actualText(entry), actualRight, baseline); + rowTop += rowHeight; } - return rowY - ROW_HEIGHT; } - private static void drawSwatch(Graphics2D canvas, PaletteLayerPreview.Entry entry, int x, int y) { + private static void drawSwatch(Graphics2D canvas, PaletteLayerPreview.Entry entry, int x, int y, int size) { if (entry.texture() != null) { - canvas.drawImage(entry.texture().toImage(), x, y, SWATCH_SIZE, SWATCH_SIZE, null); + canvas.drawImage(entry.texture().toImage(), x, y, size, size, null); return; } canvas.setColor(new Color(entry.color())); - canvas.fillRoundRect(x, y, SWATCH_SIZE, SWATCH_SIZE, 3, 3); + int arc = StudioTheme.scale(3); + canvas.fillRoundRect(x, y, size, size, arc, arc); } - private void drawNotes(Graphics2D canvas, int x, int y, int width) { + private void drawNotes(Graphics2D canvas, int x, int top, int width) { canvas.setFont(StudioTheme.smallFont()); canvas.setColor(StudioTheme.SECONDARY_TEXT); FontMetrics metrics = canvas.getFontMetrics(); - int lineY = y; + int lineY = top + metrics.getAscent(); for (String note : notes()) { for (String line : wrap(note, metrics, width)) { canvas.drawString(line, x, lineY); - lineY += NOTE_LINE_HEIGHT; + lineY += metrics.getHeight(); } - lineY += 4; + lineY += StudioTheme.scale(NOTE_GAP); } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java index 7a88b2706..f8b360d17 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteNavigator.java @@ -136,8 +136,8 @@ final class PaletteNavigator extends JPanel { private JPanel buildHereCard() { JPanel card = new JPanel(new BorderLayout()); - JPanel location = new JPanel(new GridLayout(0, 1, 0, 3)); - location.setBorder(BorderFactory.createEmptyBorder(9, 12, 5, 12)); + JPanel location = new JPanel(new GridLayout(0, 1, 0, StudioTheme.scale(3))); + location.setBorder(StudioTheme.emptyBorder(9, 12, 5, 12)); location.add(regionLabel); hereList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -175,7 +175,7 @@ final class PaletteNavigator extends JPanel { overviewTree.setRootVisible(false); overviewTree.setShowsRootHandles(true); - overviewTree.setRowHeight(24); + overviewTree.setRowHeight(StudioTheme.scale(24)); overviewTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); overviewTree.setCellRenderer(new OverviewRenderer()); overviewTree.addTreeSelectionListener(event -> { @@ -220,7 +220,7 @@ final class PaletteNavigator extends JPanel { } private void showHereMessage(String message) { - regionLabel.setText("" + message + ""); + regionLabel.setText("" + message + ""); } private void showHereRows(PlayerLocation here) { @@ -423,7 +423,7 @@ final class PaletteNavigator extends JPanel { setForeground(StudioTheme.MUTED_TEXT); } int indent = row.header() ? HEADER_INDENT : row.layer() >= 0 ? LAYER_INDENT : BIOME_INDENT; - setBorder(BorderFactory.createEmptyBorder(row.header() ? 11 : 4, indent, 4, 10)); + setBorder(StudioTheme.emptyBorder(row.header() ? 11 : 4, indent, 4, 10)); return this; } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/StudioOverlay.java b/core/src/main/java/com/volmit/iris/core/gui/StudioOverlay.java index 3f005dd76..39bcea0f6 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/StudioOverlay.java +++ b/core/src/main/java/com/volmit/iris/core/gui/StudioOverlay.java @@ -33,7 +33,7 @@ final class StudioOverlay { Object antialiasing = canvas.getRenderingHint(RenderingHints.KEY_ANTIALIASING); canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); RoundRectangle2D card = new RoundRectangle2D.Double(x + 0.5D, y + 0.5D, width - 1D, height - 1D, - StudioTheme.OVERLAY_ARC, StudioTheme.OVERLAY_ARC); + StudioTheme.scale(StudioTheme.OVERLAY_ARC), StudioTheme.scale(StudioTheme.OVERLAY_ARC)); canvas.setColor(StudioTheme.OVERLAY_BACKGROUND); canvas.fill(card); canvas.setColor(StudioTheme.OVERLAY_BORDER); diff --git a/core/src/main/java/com/volmit/iris/core/gui/StudioStatusBar.java b/core/src/main/java/com/volmit/iris/core/gui/StudioStatusBar.java index 535aa0655..6025a5704 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/StudioStatusBar.java +++ b/core/src/main/java/com/volmit/iris/core/gui/StudioStatusBar.java @@ -45,18 +45,20 @@ final class StudioStatusBar extends JPanel { setBackground(StudioTheme.PANEL_BACKGROUND); setBorder(BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder(1, 0, 0, 0, StudioTheme.SEPARATOR), - BorderFactory.createEmptyBorder(0, 10, 0, 10) + StudioTheme.emptyBorder(0, 10, 0, 10) )); - setPreferredSize(new Dimension(0, HEIGHT)); - progress.setMaximumSize(new Dimension(120, 4)); - progress.setPreferredSize(new Dimension(120, 4)); + setPreferredSize(StudioTheme.scaledSize(0, HEIGHT)); + progress.setMaximumSize(StudioTheme.scaledSize(120, 4)); + progress.setPreferredSize(StudioTheme.scaledSize(120, 4)); progress.setVisible(false); + allowShrinking(summary); + allowShrinking(activity); add(summary); add(Box.createHorizontalGlue()); add(activity); - add(Box.createHorizontalStrut(8)); + add(Box.createHorizontalStrut(StudioTheme.scale(8))); add(progress); - add(Box.createHorizontalStrut(18)); + add(Box.createHorizontalStrut(StudioTheme.scale(18))); add(position); } @@ -86,6 +88,11 @@ final class StudioStatusBar extends JPanel { setTextIfChanged(position, text); } + // Long text gives way with an ellipsis instead of pushing the other parts of the bar out of view. + private static void allowShrinking(JLabel label) { + label.setMinimumSize(new Dimension(0, 0)); + } + private static JLabel statusLabel() { JLabel label = new JLabel(); label.setFont(StudioTheme.smallFont()); diff --git a/core/src/main/java/com/volmit/iris/core/gui/StudioTheme.java b/core/src/main/java/com/volmit/iris/core/gui/StudioTheme.java index 8d2458043..7a71842e9 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/StudioTheme.java +++ b/core/src/main/java/com/volmit/iris/core/gui/StudioTheme.java @@ -20,10 +20,14 @@ package com.volmit.iris.core.gui; import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.UIScale; import com.volmit.iris.Iris; +import javax.swing.BorderFactory; import javax.swing.UIManager; +import javax.swing.border.Border; import java.awt.Color; +import java.awt.Dimension; import java.awt.Font; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; @@ -53,6 +57,7 @@ final class StudioTheme { static final int OVERLAY_ARC = 10; private static final AtomicBoolean INSTALLED = new AtomicBoolean(); + private static volatile float pixelScale = 1F; private static final Map INTELLIJ_NEW_UI_DEFAULTS = Map.ofEntries( Map.entry("@background", "#2B2D30"), Map.entry("@foreground", "#DFE1E5"), @@ -100,12 +105,32 @@ final class StudioTheme { try { FlatLaf.setGlobalExtraDefaults(INTELLIJ_NEW_UI_DEFAULTS); FlatDarkLaf.setup(); + pixelScale = UIScale.getUserScaleFactor(); } catch (Throwable error) { Iris.reportError(error); Iris.warn("Could not apply the Iris studio theme, the studio windows will use Java's default look."); } } + /** + * FlatLaf grows fonts and its own controls to the screen's DPI; every size the studio sets itself has to grow with them. + */ + static int scale(int pixels) { + return Math.round(pixels * pixelScale); + } + + static double scale(double pixels) { + return pixels * pixelScale; + } + + static Dimension scaledSize(int width, int height) { + return new Dimension(scale(width), scale(height)); + } + + static Border emptyBorder(int top, int left, int bottom, int right) { + return BorderFactory.createEmptyBorder(scale(top), scale(left), scale(bottom), scale(right)); + } + static Font font() { return themeFont("defaultFont", Font.PLAIN, 13); } diff --git a/core/src/main/java/com/volmit/iris/core/gui/StudioToolbar.java b/core/src/main/java/com/volmit/iris/core/gui/StudioToolbar.java index 7383024da..9fb9bfc49 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/StudioToolbar.java +++ b/core/src/main/java/com/volmit/iris/core/gui/StudioToolbar.java @@ -50,7 +50,7 @@ final class StudioToolbar { toolbar.setBackground(StudioTheme.PANEL_BACKGROUND); toolbar.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder(0, 0, 1, 0, StudioTheme.SEPARATOR), - BorderFactory.createEmptyBorder(TOOLBAR_HEIGHT_PADDING, 8, TOOLBAR_HEIGHT_PADDING, 8) + StudioTheme.emptyBorder(TOOLBAR_HEIGHT_PADDING, 8, TOOLBAR_HEIGHT_PADDING, 8) )); return toolbar; } @@ -58,7 +58,7 @@ final class StudioToolbar { static JLabel label(String text) { JLabel label = new JLabel(text); label.setForeground(StudioTheme.SECONDARY_TEXT); - label.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 6)); + label.setBorder(StudioTheme.emptyBorder(0, 6, 0, 6)); return label; } @@ -99,7 +99,7 @@ final class StudioToolbar { static JPanel padded(JComponent content, int top, int left, int bottom, int right) { JPanel panel = new JPanel(new BorderLayout()); panel.setOpaque(false); - panel.setBorder(BorderFactory.createEmptyBorder(top, left, bottom, right)); + panel.setBorder(StudioTheme.emptyBorder(top, left, bottom, right)); panel.add(content, BorderLayout.CENTER); return panel; }