mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Give the studio windows an IntelliJ style dark theme with FlatLaf
FlatLaf is loaded at runtime through slimjar like the other libraries. Every studio window now opens with FlatLaf's dark look tuned to the IntelliJ New UI colours, and shares a small kit of controls: a flat toolbar, toolbar buttons and tabs, search boxes, a status bar and the rounded cards drawn over maps. Model used: Claude Opus 5.5
This commit is contained in:
1 parent
eb1a244114
commit
8e71c14f65
7 files changed
+401
No files matched your search
@@ -98,6 +98,7 @@ dependencies {
|
||||
slim(libs.byteBuddy.agent)
|
||||
slim(libs.dom4j)
|
||||
slim(libs.jaxen)
|
||||
slim(libs.flatlaf)
|
||||
|
||||
// Script Engine
|
||||
slim(libs.kotlin.stdlib)
|
||||
|
||||
@@ -59,6 +59,7 @@ public final class GuiHost {
|
||||
|
||||
public static JFrame createFrame(String title) {
|
||||
XwaylandWindowManagerHint.apply();
|
||||
StudioTheme.install();
|
||||
JFrame frame = new JFrame(title);
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
MANAGED_FRAMES.add(frame);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
/**
|
||||
* Draws the floating cards (legends, hover details, hints) on top of a map, styled like IntelliJ's popups.
|
||||
*/
|
||||
final class StudioOverlay {
|
||||
private StudioOverlay() {
|
||||
}
|
||||
|
||||
static void drawCard(Graphics2D canvas, int x, int y, int width, int height) {
|
||||
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);
|
||||
canvas.setColor(StudioTheme.OVERLAY_BACKGROUND);
|
||||
canvas.fill(card);
|
||||
canvas.setColor(StudioTheme.OVERLAY_BORDER);
|
||||
canvas.draw(card);
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
import java.awt.Dimension;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An IntelliJ style status bar: what is shown on the left, work in progress in the middle and where the pointer is on the right.
|
||||
*/
|
||||
final class StudioStatusBar extends JPanel {
|
||||
private static final long serialVersionUID = 6302468216735125611L;
|
||||
private static final int HEIGHT = 26;
|
||||
private static final int PROGRESS_STEPS = 1_000;
|
||||
|
||||
private final JLabel summary = statusLabel();
|
||||
private final JLabel activity = statusLabel();
|
||||
private final JProgressBar progress = new JProgressBar(0, PROGRESS_STEPS);
|
||||
private final JLabel position = statusLabel();
|
||||
|
||||
StudioStatusBar() {
|
||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||
setBackground(StudioTheme.PANEL_BACKGROUND);
|
||||
setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(1, 0, 0, 0, StudioTheme.SEPARATOR),
|
||||
BorderFactory.createEmptyBorder(0, 10, 0, 10)
|
||||
));
|
||||
setPreferredSize(new Dimension(0, HEIGHT));
|
||||
progress.setMaximumSize(new Dimension(120, 4));
|
||||
progress.setPreferredSize(new Dimension(120, 4));
|
||||
progress.setVisible(false);
|
||||
add(summary);
|
||||
add(Box.createHorizontalGlue());
|
||||
add(activity);
|
||||
add(Box.createHorizontalStrut(8));
|
||||
add(progress);
|
||||
add(Box.createHorizontalStrut(18));
|
||||
add(position);
|
||||
}
|
||||
|
||||
void showSummary(String text) {
|
||||
setTextIfChanged(summary, text);
|
||||
summary.setForeground(StudioTheme.SECONDARY_TEXT);
|
||||
}
|
||||
|
||||
void showError(String text) {
|
||||
setTextIfChanged(summary, text);
|
||||
summary.setForeground(StudioTheme.ERROR);
|
||||
}
|
||||
|
||||
void showActivity(String text) {
|
||||
setTextIfChanged(activity, text);
|
||||
}
|
||||
|
||||
void showProgress(double completion) {
|
||||
boolean working = completion < 1D;
|
||||
progress.setVisible(working);
|
||||
if (working) {
|
||||
progress.setValue((int) Math.round(Math.max(0D, completion) * PROGRESS_STEPS));
|
||||
}
|
||||
}
|
||||
|
||||
void showPosition(String text) {
|
||||
setTextIfChanged(position, text);
|
||||
}
|
||||
|
||||
private static JLabel statusLabel() {
|
||||
JLabel label = new JLabel();
|
||||
label.setFont(StudioTheme.smallFont());
|
||||
label.setForeground(StudioTheme.SECONDARY_TEXT);
|
||||
return label;
|
||||
}
|
||||
|
||||
// Status text changes on every mouse move, and an unchanged label shouldn't trigger a relayout.
|
||||
private static void setTextIfChanged(JLabel label, String text) {
|
||||
String next = text == null ? "" : text;
|
||||
if (!Objects.equals(label.getText(), next)) {
|
||||
label.setText(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import com.formdev.flatlaf.FlatDarkLaf;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.volmit.iris.Iris;
|
||||
|
||||
import javax.swing.UIManager;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* The colours of IntelliJ's dark "New UI", shared by FlatLaf's controls and everything the studio windows draw themselves.
|
||||
*/
|
||||
final class StudioTheme {
|
||||
static final Color EDITOR_BACKGROUND = new Color(0x1E1F22);
|
||||
static final Color PANEL_BACKGROUND = new Color(0x2B2D30);
|
||||
static final Color HOVER_BACKGROUND = new Color(0x393B40);
|
||||
static final Color INACTIVE_SELECTION = new Color(0x43454A);
|
||||
static final Color SELECTION = new Color(0x2E436E);
|
||||
static final Color CONTROL_BORDER = new Color(0x4E5157);
|
||||
static final Color SEPARATOR = new Color(0x1E1F22);
|
||||
static final Color TEXT = new Color(0xDFE1E5);
|
||||
static final Color SECONDARY_TEXT = new Color(0x9DA0A8);
|
||||
static final Color MUTED_TEXT = new Color(0x6F737A);
|
||||
static final Color ACCENT = new Color(0x3574F0);
|
||||
static final Color LINK = new Color(0x548AF7);
|
||||
static final Color ERROR = new Color(0xF75464);
|
||||
static final Color WARNING = new Color(0xF2C55C);
|
||||
static final Color SUCCESS = new Color(0x5FB865);
|
||||
static final Color OVERLAY_BACKGROUND = new Color(0x2B, 0x2D, 0x30, 236);
|
||||
static final Color OVERLAY_BORDER = new Color(0x43454A);
|
||||
static final String SECONDARY_TEXT_HTML = "#9DA0A8";
|
||||
static final int OVERLAY_ARC = 10;
|
||||
|
||||
private static final AtomicBoolean INSTALLED = new AtomicBoolean();
|
||||
private static final Map<String, String> INTELLIJ_NEW_UI_DEFAULTS = Map.ofEntries(
|
||||
Map.entry("@background", "#2B2D30"),
|
||||
Map.entry("@foreground", "#DFE1E5"),
|
||||
Map.entry("@accentColor", "#3574F0"),
|
||||
Map.entry("@selectionBackground", "#2E436E"),
|
||||
Map.entry("@selectionInactiveBackground", "#43454A"),
|
||||
Map.entry("@disabledForeground", "#6F737A"),
|
||||
Map.entry("Component.borderColor", "#4E5157"),
|
||||
Map.entry("Component.focusedBorderColor", "#3574F0"),
|
||||
Map.entry("Component.focusWidth", "1"),
|
||||
Map.entry("Component.innerFocusWidth", "0"),
|
||||
Map.entry("Component.arc", "8"),
|
||||
Map.entry("Button.arc", "8"),
|
||||
Map.entry("TextComponent.arc", "8"),
|
||||
Map.entry("Button.toolbar.hoverBackground", "#393B40"),
|
||||
Map.entry("Button.toolbar.pressedBackground", "#43454A"),
|
||||
Map.entry("Button.toolbar.selectedBackground", "#43454A"),
|
||||
Map.entry("ToggleButton.toolbar.selectedBackground", "#43454A"),
|
||||
Map.entry("List.selectionArc", "6"),
|
||||
Map.entry("List.selectionInsets", "0,6,0,6"),
|
||||
Map.entry("Tree.selectionArc", "6"),
|
||||
Map.entry("Tree.selectionInsets", "0,6,0,6"),
|
||||
Map.entry("ScrollBar.width", "10"),
|
||||
Map.entry("ScrollBar.thumbArc", "999"),
|
||||
Map.entry("ScrollBar.thumbInsets", "2,2,2,2"),
|
||||
Map.entry("ScrollBar.showButtons", "false"),
|
||||
Map.entry("ScrollBar.track", "#2B2D30"),
|
||||
Map.entry("ScrollPane.border", "0,0,0,0"),
|
||||
Map.entry("SplitPane.dividerSize", "1"),
|
||||
Map.entry("SplitPaneDivider.style", "plain"),
|
||||
Map.entry("SplitPane.background", "#1E1F22"),
|
||||
Map.entry("Separator.foreground", "#1E1F22"),
|
||||
Map.entry("ToolTip.background", "#393B40"),
|
||||
Map.entry("ProgressBar.arc", "999"),
|
||||
Map.entry("ProgressBar.horizontalSize", "120,4")
|
||||
);
|
||||
|
||||
private StudioTheme() {
|
||||
}
|
||||
|
||||
static void install() {
|
||||
if (!INSTALLED.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FlatLaf.setGlobalExtraDefaults(INTELLIJ_NEW_UI_DEFAULTS);
|
||||
FlatDarkLaf.setup();
|
||||
} catch (Throwable error) {
|
||||
Iris.reportError(error);
|
||||
Iris.warn("Could not apply the Iris studio theme, the studio windows will use Java's default look.");
|
||||
}
|
||||
}
|
||||
|
||||
static Font font() {
|
||||
return themeFont("defaultFont", Font.PLAIN, 13);
|
||||
}
|
||||
|
||||
static Font smallFont() {
|
||||
return themeFont("small.font", Font.PLAIN, 11);
|
||||
}
|
||||
|
||||
static Font boldFont() {
|
||||
return font().deriveFont(Font.BOLD);
|
||||
}
|
||||
|
||||
static Font titleFont() {
|
||||
return font().deriveFont(Font.BOLD, font().getSize2D() + 1F);
|
||||
}
|
||||
|
||||
static Font sectionFont() {
|
||||
return smallFont().deriveFont(Font.BOLD);
|
||||
}
|
||||
|
||||
static Font monospacedFont() {
|
||||
return themeFont("monospaced.font", Font.PLAIN, 12);
|
||||
}
|
||||
|
||||
private static Font themeFont(String key, int fallbackStyle, int fallbackSize) {
|
||||
Font font = UIManager.getFont(key);
|
||||
return font != null ? font : new Font(Font.SANS_SERIF, fallbackStyle, fallbackSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import com.formdev.flatlaf.FlatClientProperties;
|
||||
import com.formdev.flatlaf.icons.FlatSearchIcon;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Insets;
|
||||
|
||||
/**
|
||||
* Builds the IntelliJ style controls the studio windows share: a flat main toolbar, borderless toolbar buttons and search boxes.
|
||||
*/
|
||||
final class StudioToolbar {
|
||||
private static final int TOOLBAR_HEIGHT_PADDING = 5;
|
||||
|
||||
private StudioToolbar() {
|
||||
}
|
||||
|
||||
static JToolBar create() {
|
||||
JToolBar toolbar = new JToolBar();
|
||||
toolbar.setFloatable(false);
|
||||
toolbar.setRollover(true);
|
||||
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)
|
||||
));
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
static JLabel label(String text) {
|
||||
JLabel label = new JLabel(text);
|
||||
label.setForeground(StudioTheme.SECONDARY_TEXT);
|
||||
label.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 6));
|
||||
return label;
|
||||
}
|
||||
|
||||
static JButton button(String text, String tooltip, Runnable action) {
|
||||
JButton button = new JButton(text);
|
||||
button.setToolTipText(tooltip);
|
||||
button.setFocusable(false);
|
||||
button.setMargin(new Insets(4, 8, 4, 8));
|
||||
button.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_TOOLBAR_BUTTON);
|
||||
button.addActionListener(event -> action.run());
|
||||
return button;
|
||||
}
|
||||
|
||||
static JToggleButton toggle(String text, String tooltip) {
|
||||
JToggleButton toggle = new JToggleButton(text);
|
||||
toggle.setToolTipText(tooltip);
|
||||
toggle.setFocusable(false);
|
||||
toggle.setMargin(new Insets(4, 8, 4, 8));
|
||||
toggle.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_TOOLBAR_BUTTON);
|
||||
return toggle;
|
||||
}
|
||||
|
||||
// An underlined tab like IntelliJ's tool window tabs.
|
||||
static JToggleButton tab(String text) {
|
||||
JToggleButton tab = new JToggleButton(text);
|
||||
tab.setFocusable(false);
|
||||
tab.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_TAB);
|
||||
return tab;
|
||||
}
|
||||
|
||||
static void makeSearchField(JTextField field, String placeholder) {
|
||||
field.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, placeholder);
|
||||
field.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new FlatSearchIcon());
|
||||
field.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
|
||||
field.setToolTipText(placeholder);
|
||||
}
|
||||
|
||||
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.add(content, BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
static JScrollPane scroll(JComponent content) {
|
||||
JScrollPane scrollPane = new JScrollPane(content);
|
||||
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.getVerticalScrollBar().setUnitIncrement(18);
|
||||
return scrollPane;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user