Size the studio windows for high DPI screens

FlatLaf grows fonts and its own controls to the screen's DPI, but the
sizes the studio set itself stayed fixed, so on a 192 DPI screen text
was cut off and overlapped. Every padding, row height, column, card and
legend now grows with the same scale or follows the font, the windows
open at a scaled size that still fits the screen, and the theme is
installed before any window builds its controls. Long status text now
shortens with an ellipsis instead of pushing the rest of the bar away.

Model used: Claude Opus 5.5
This commit is contained in:
piexel committed 2026-10-19 11:05:55 +02:00
1 parent 80e7e37791
commit b1445430bb
11 files changed
+251 -137

No files matched your search

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