Add light theme

Follows the system by default.
This commit is contained in:
piexel committed 2026-10-19 23:01:31 +02:00
1 parent 46f5472081
commit 59626d2434
12 files changed
+394 -50

No files matched your search

@@ -266,6 +266,7 @@ public class IrisSettings {
public boolean useServerLaunchedGuis = true;
public boolean maximumPregenGuiFPS = false;
public boolean colorMode = true;
public String theme = "auto";
}
@Data
@@ -73,6 +73,11 @@ public final class GuiHost {
public void windowClosed(WindowEvent event) {
MANAGED_FRAMES.remove(frame);
}
@Override
public void windowActivated(WindowEvent event) {
StudioTheme.followSystem();
}
});
installMacQuitGuard();
return frame;
@@ -66,8 +66,8 @@ final class MapCanvas extends JPanel {
private static final Color MOB_COLOR = StudioTheme.ERROR;
private static final Color GRID_MINOR = new Color(255, 255, 255, 18);
private static final Color GRID_MAJOR = new Color(255, 255, 255, 38);
private static final Color TILE_EMPTY_A = new Color(0x232427);
private static final Color TILE_EMPTY_B = new Color(0x26272B);
private static final Color TILE_EMPTY_A = new ThemeColor(0xF0F1F3, 0x232427);
private static final Color TILE_EMPTY_B = new ThemeColor(0xEBECEF, 0x26272B);
private static final int CARD_PADDING = 12;
private static final int NOTIFICATION_MILLIS = 2_500;
private static final double DEFAULT_BLOCKS_PER_PIXEL = 4D;
@@ -585,6 +585,13 @@ final class MapCanvas extends JPanel {
canvas.drawImage(currentTileLayer(), 0, 0, null);
}
// The theme changed: the cached layer holds the old theme's background.
@Override
public void updateUI() {
super.updateUI();
tileLayerStale = true;
}
private BufferedImage currentTileLayer() {
int width = Math.max(1, getWidth());
int height = Math.max(1, getHeight());
@@ -178,6 +178,7 @@ public final class MapWindow implements MapCanvas.Listener {
explorerControls.forEach(toolbar::add);
toolbar.add(Box.createHorizontalGlue());
toolbar.add(StudioToolbar.themeButton());
JComponent help = StudioToolbar.button("Shortcuts", "Show every mouse and key shortcut (/)", canvas::toggleHelp);
mapControls.add(help);
toolbar.add(help);
@@ -22,6 +22,7 @@ import com.volmit.iris.core.gui.BlockModel.Face;
import com.volmit.iris.core.gui.BlockModel.TextureMapping;
import com.volmit.iris.core.gui.BlockTextures.BlockTexture;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
@@ -36,8 +37,7 @@ import java.util.List;
* what is already drawn.
*/
final class ObjectRenderer {
private static final int BACKGROUND = StudioTheme.EDITOR_BACKGROUND.getRGB() & 0xFFFFFF;
private static final int GRID_COLOR = 0x34373C;
private static final Color GRID = new ThemeColor(0xE3E4E8, 0x34373C);
private static final int GRID_LINES_PER_SIDE = 25;
private static final double GRID_REACH = 0.7D;
private static final double GRID_BELOW_OBJECT = 0.035D;
@@ -54,6 +54,7 @@ final class ObjectRenderer {
private final double[] depths;
private final boolean[] showsObject;
private final Projection projection;
private final int gridColor = GRID.getRGB() & 0xFFFFFF;
private ObjectRenderer(ObjectMesh mesh, ObjectCamera camera, BufferedImage image, int coveredLeftWidth) {
this.width = image.getWidth();
@@ -71,7 +72,7 @@ final class ObjectRenderer {
static BufferedImage render(ObjectMesh mesh, ObjectCamera camera, int width, int height, int coveredLeftWidth) {
BufferedImage image = new BufferedImage(Math.max(1, width), Math.max(1, height), BufferedImage.TYPE_INT_RGB);
ObjectRenderer renderer = new ObjectRenderer(mesh, camera, image, Math.max(0, Math.min(width - 1, coveredLeftWidth)));
Arrays.fill(renderer.pixels, BACKGROUND);
Arrays.fill(renderer.pixels, StudioTheme.EDITOR_BACKGROUND.getRGB() & 0xFFFFFF);
Arrays.fill(renderer.depths, Double.NEGATIVE_INFINITY);
renderer.drawGrid(mesh.bounds());
for (ObjectMesh.Quad quad : mesh.solidQuads()) {
@@ -131,7 +132,7 @@ final class ObjectRenderer {
double depth = startDepth + (endDepth - startDepth) * progress;
if (depth > depths[index]) {
depths[index] = depth;
pixels[index] = GRID_COLOR;
pixels[index] = gridColor;
}
}
}
@@ -77,6 +77,13 @@ final class ObjectViewport extends JComponent {
});
}
// The theme changed, and the last frame was drawn on the old theme's background.
@Override
public void updateUI() {
super.updateUI();
requestFrame();
}
void show(ObjectMesh nextMesh) {
mesh = nextMesh;
camera = ObjectCamera.START;
@@ -57,7 +57,6 @@ final class PaletteNavigator extends JPanel {
private static final long serialVersionUID = -6402313718430917284L;
private static final String HERE_CARD = "here";
private static final String OVERVIEW_CARD = "overview";
private static final String DETAIL_COLOR = StudioTheme.SECONDARY_TEXT_HTML;
private static final int MESSAGE_WRAP_WIDTH_PIXELS = 190;
private final Listener listener;
@@ -438,13 +437,13 @@ final class PaletteNavigator extends JPanel {
private static String biomeText(BiomeEntry biome, String note) {
String noteText = note == null ? "" : " · " + escape(note);
return "<html>" + escape(biome.name()) + noteText + " <font color='" + DETAIL_COLOR + "'>" + escape(biome.key())
return "<html>" + escape(biome.name()) + noteText + " <font color='" + StudioTheme.SECONDARY_TEXT.toHtml() + "'>" + escape(biome.key())
+ "</font></html>";
}
private static String labeled(String title, String name, String key) {
return "<html><font color='" + DETAIL_COLOR + "'>" + title + "</font>&nbsp; " + escape(name)
+ " <font color='" + DETAIL_COLOR + "'>" + escape(key) + "</font></html>";
return "<html><font color='" + StudioTheme.SECONDARY_TEXT.toHtml() + "'>" + title + "</font>&nbsp; " + escape(name)
+ " <font color='" + StudioTheme.SECONDARY_TEXT.toHtml() + "'>" + escape(key) + "</font></html>";
}
private static String escape(String text) {
@@ -477,7 +476,7 @@ final class PaletteNavigator extends JPanel {
if (detail == null) {
return escape(label);
}
return "<html>" + escape(label) + " <font color='" + DETAIL_COLOR + "'>" + escape(detail) + "</font></html>";
return "<html>" + escape(label) + " <font color='" + StudioTheme.SECONDARY_TEXT.toHtml() + "'>" + escape(detail) + "</font></html>";
}
}
@@ -51,7 +51,7 @@ import java.util.List;
final class StickyFolderHeaders extends LayerUI<JScrollPane> {
private static final long serialVersionUID = -2205474187531254372L;
private static final int SHADOW_HEIGHT = 4;
private static final Color SHADOW = new Color(0, 0, 0, 90);
private static final Color SHADOW = new ThemeColor(new Color(0, 0, 0, 28), new Color(0, 0, 0, 90));
private static final Color NO_SHADOW = new Color(0, 0, 0, 0);
// The folder handle sits one indent to the left of the folder's name, as the tree draws it.
private static final int HANDLE_NUDGE = 1;
@@ -20,62 +20,64 @@ package com.volmit.iris.core.gui;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.util.UIScale;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import javax.swing.BorderFactory;
import javax.swing.UIManager;
import javax.swing.border.Border;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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.
* The colours of IntelliJ's "New UI", light and dark, shared by FlatLaf's controls and everything the studio windows
* draw themselves. The theme follows the system unless the user picked light or dark.
*/
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 EDITOR_BACKGROUND = new ThemeColor(0xFFFFFF, 0x1E1F22);
static final Color PANEL_BACKGROUND = new ThemeColor(0xF7F8FA, 0x2B2D30);
static final Color HOVER_BACKGROUND = new ThemeColor(0xDFE1E5, 0x393B40);
static final Color INACTIVE_SELECTION = new ThemeColor(0xDFE1E5, 0x43454A);
static final Color SELECTION = new ThemeColor(0xD4E2FF, 0x2E436E);
static final Color CONTROL_BORDER = new ThemeColor(0xC9CCD6, 0x4E5157);
static final Color SEPARATOR = new ThemeColor(0xEBECF0, 0x1E1F22);
static final Color TEXT = new ThemeColor(0x000000, 0xDFE1E5);
static final ThemeColor SECONDARY_TEXT = new ThemeColor(0x6C707E, 0x9DA0A8);
static final Color MUTED_TEXT = new ThemeColor(0x818594, 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 Color LINK = new ThemeColor(0x315FBD, 0x548AF7);
static final Color ERROR = new ThemeColor(0xDB3B4B, 0xF75464);
static final Color WARNING = new ThemeColor(0xC27D04, 0xF2C55C);
static final Color SUCCESS = new ThemeColor(0x208A3C, 0x5FB865);
static final Color OVERLAY_BACKGROUND = new ThemeColor(new Color(0xFF, 0xFF, 0xFF, 240), new Color(0x2B, 0x2D, 0x30, 236));
static final Color OVERLAY_BORDER = new ThemeColor(0xDFE1E5, 0x43454A);
static final int OVERLAY_ARC = 10;
private static final boolean DARK_WITHOUT_SYSTEM_PREFERENCE = true;
private static final AtomicBoolean INSTALLED = new AtomicBoolean();
private static final ExecutorService SYSTEM_CHECK = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable, "Iris Theme Check");
thread.setDaemon(true);
return thread;
});
private static volatile boolean dark = DARK_WITHOUT_SYSTEM_PREFERENCE;
private static volatile float pixelScale = 1F;
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"),
private static final Map<String, String> SHARED_DEFAULTS = Map.ofEntries(
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"),
@@ -85,16 +87,49 @@ final class StudioTheme {
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 static final Map<String, String> DARK_DEFAULTS = withShared(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("Button.toolbar.hoverBackground", "#393B40"),
Map.entry("Button.toolbar.pressedBackground", "#43454A"),
Map.entry("Button.toolbar.selectedBackground", "#43454A"),
Map.entry("ToggleButton.toolbar.selectedBackground", "#43454A"),
Map.entry("ScrollBar.track", "#2B2D30"),
Map.entry("SplitPane.background", "#1E1F22"),
Map.entry("Separator.foreground", "#1E1F22"),
Map.entry("ToolTip.background", "#393B40")
));
private static final Map<String, String> LIGHT_DEFAULTS = withShared(Map.ofEntries(
Map.entry("@background", "#F7F8FA"),
Map.entry("@foreground", "#000000"),
Map.entry("@accentColor", "#3574F0"),
Map.entry("@selectionBackground", "#D4E2FF"),
Map.entry("@selectionForeground", "#000000"),
Map.entry("@selectionInactiveBackground", "#DFE1E5"),
Map.entry("@selectionInactiveForeground", "#000000"),
Map.entry("@disabledForeground", "#A8ADBD"),
Map.entry("Component.borderColor", "#C9CCD6"),
Map.entry("Component.focusedBorderColor", "#3574F0"),
Map.entry("Button.toolbar.hoverBackground", "#DFE1E5"),
Map.entry("Button.toolbar.pressedBackground", "#C9CCD6"),
Map.entry("Button.toolbar.selectedBackground", "#DFE1E5"),
Map.entry("ToggleButton.toolbar.selectedBackground", "#DFE1E5"),
Map.entry("ScrollBar.track", "#F7F8FA"),
Map.entry("SplitPane.background", "#EBECF0"),
Map.entry("Separator.foreground", "#EBECF0")
));
private StudioTheme() {
}
@@ -104,8 +139,7 @@ final class StudioTheme {
return;
}
try {
FlatLaf.setGlobalExtraDefaults(INTELLIJ_NEW_UI_DEFAULTS);
FlatDarkLaf.setup();
applyLookAndFeel(darkFor(mode()));
pixelScale = UIScale.getUserScaleFactor();
} catch (Throwable error) {
Iris.reportError(error);
@@ -113,6 +147,71 @@ final class StudioTheme {
}
}
static boolean isDark() {
return dark;
}
static Mode mode() {
return Mode.fromSetting(IrisSettings.get().getGui().getTheme());
}
/**
* Remembers the user's choice and switches every studio window to it.
*/
static void choose(Mode next) {
IrisSettings settings = IrisSettings.get();
settings.getGui().setTheme(next.settingName());
settings.forceSave();
if (next == Mode.AUTO) {
followSystem();
return;
}
switchTo(next == Mode.DARK);
}
// Studio windows call this when they come to the front, so a change made in the system's settings shows up.
static void followSystem() {
if (!INSTALLED.get() || mode() != Mode.AUTO) {
return;
}
SYSTEM_CHECK.execute(() -> {
boolean systemDark = SystemAppearance.prefersDark().orElse(DARK_WITHOUT_SYSTEM_PREFERENCE);
EventQueue.invokeLater(() -> switchTo(systemDark));
});
}
private static boolean darkFor(Mode chosen) {
return switch (chosen) {
case LIGHT -> false;
case DARK -> true;
case AUTO -> SystemAppearance.prefersDark().orElse(DARK_WITHOUT_SYSTEM_PREFERENCE);
};
}
private static void switchTo(boolean nextDark) {
if (nextDark == dark) {
return;
}
applyLookAndFeel(nextDark);
FlatLaf.updateUI();
}
private static void applyLookAndFeel(boolean nextDark) {
dark = nextDark;
FlatLaf.setGlobalExtraDefaults(nextDark ? DARK_DEFAULTS : LIGHT_DEFAULTS);
if (nextDark) {
FlatDarkLaf.setup();
} else {
FlatLightLaf.setup();
}
}
private static Map<String, String> withShared(Map<String, String> themeDefaults) {
Map<String, String> merged = new HashMap<>(SHARED_DEFAULTS);
merged.putAll(themeDefaults);
return Map.copyOf(merged);
}
/**
* FlatLaf grows fonts and its own controls to the screen's DPI; every size the studio sets itself has to grow with them.
*/
@@ -160,4 +259,33 @@ final class StudioTheme {
Font font = UIManager.getFont(key);
return font != null ? font : new Font(Font.SANS_SERIF, fallbackStyle, fallbackSize);
}
enum Mode {
AUTO("Match system"),
LIGHT("Light"),
DARK("Dark");
private final String label;
Mode(String label) {
this.label = label;
}
String label() {
return label;
}
String settingName() {
return name().toLowerCase(Locale.ROOT);
}
static Mode fromSetting(String setting) {
for (Mode mode : values()) {
if (mode.settingName().equalsIgnoreCase(setting)) {
return mode;
}
}
return AUTO;
}
}
}
@@ -22,10 +22,13 @@ import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.icons.FlatSearchIcon;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
@@ -63,12 +66,39 @@ final class StudioToolbar {
}
static JButton button(String text, String tooltip, Runnable action) {
JButton button = toolbarButton(text, tooltip);
button.addActionListener(event -> action.run());
return button;
}
/**
* Opens a menu to pick light, dark or the system's theme.
*/
static JButton themeButton() {
JButton button = toolbarButton("Theme", "Light, dark, or whatever the system uses");
button.addActionListener(event -> themeMenu().show(button, 0, button.getHeight()));
return button;
}
private static JPopupMenu themeMenu() {
JPopupMenu menu = new JPopupMenu();
ButtonGroup choices = new ButtonGroup();
StudioTheme.Mode current = StudioTheme.mode();
for (StudioTheme.Mode mode : StudioTheme.Mode.values()) {
JRadioButtonMenuItem item = new JRadioButtonMenuItem(mode.label(), mode == current);
item.addActionListener(event -> StudioTheme.choose(mode));
choices.add(item);
menu.add(item);
}
return menu;
}
private static JButton toolbarButton(String text, String tooltip) {
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;
}
@@ -0,0 +1,110 @@
/*
* 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.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* Asks the operating system whether its user prefers dark or light apps, through the same settings desktop apps
* follow: the freedesktop portal or GNOME's setting on Linux, the apps theme on Windows, the appearance on macOS.
*/
final class SystemAppearance {
private static final long COMMAND_TIMEOUT_MILLIS = 2000L;
private static final String PORTAL_DARK = "uint32 1";
private static final String PORTAL_LIGHT = "uint32 2";
private SystemAppearance() {
}
/**
* Empty when the system has no preference or could not be asked.
*/
static Optional<Boolean> prefersDark() {
String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
if (os.contains("win")) {
return windowsPrefersDark();
}
if (os.contains("mac")) {
return macPrefersDark();
}
return linuxPrefersDark();
}
private static Optional<Boolean> linuxPrefersDark() {
Optional<String> portal = output(List.of("gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop",
"--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Settings.ReadOne",
"org.freedesktop.appearance", "color-scheme"));
if (portal.filter(answer -> answer.contains(PORTAL_DARK)).isPresent()) {
return Optional.of(true);
}
if (portal.filter(answer -> answer.contains(PORTAL_LIGHT)).isPresent()) {
return Optional.of(false);
}
Optional<String> gnome = output(List.of("gsettings", "get", "org.gnome.desktop.interface", "color-scheme"));
if (gnome.filter(scheme -> scheme.contains("prefer-dark")).isPresent()) {
return Optional.of(true);
}
if (gnome.filter(scheme -> scheme.contains("prefer-light")).isPresent()) {
return Optional.of(false);
}
return output(List.of("gsettings", "get", "org.gnome.desktop.interface", "gtk-theme"))
.map(theme -> theme.toLowerCase(Locale.ROOT).contains("dark"));
}
private static Optional<Boolean> windowsPrefersDark() {
return output(List.of("reg", "query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"/v", "AppsUseLightTheme"))
.filter(answer -> answer.contains("AppsUseLightTheme"))
.map(answer -> answer.trim().endsWith("0x0"));
}
// The setting only exists while macOS is dark, so a failed read means light.
private static Optional<Boolean> macPrefersDark() {
return Optional.of(output(List.of("defaults", "read", "-g", "AppleInterfaceStyle"))
.filter(style -> style.trim().equalsIgnoreCase("dark"))
.isPresent());
}
private static Optional<String> output(List<String> command) {
try {
Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
if (!process.waitFor(COMMAND_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
process.destroyForcibly();
return Optional.empty();
}
if (process.exitValue() != 0) {
return Optional.empty();
}
try (InputStream stream = process.getInputStream()) {
return Optional.of(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
}
} catch (IOException missingCommand) {
return Optional.empty();
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
return Optional.empty();
}
}
}
@@ -0,0 +1,55 @@
/*
* 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.Color;
/**
* A colour with a light and a dark version that always answers with the one of the current theme, like IntelliJ's
* JBColor. Components keep the same colour object across a theme switch and simply paint in the other version.
*/
final class ThemeColor extends Color {
private static final long serialVersionUID = 2286435620386470207L;
private final Color light;
private final Color dark;
ThemeColor(int lightRgb, int darkRgb) {
this(new Color(lightRgb), new Color(darkRgb));
}
ThemeColor(Color light, Color dark) {
super(dark.getRGB(), true);
this.light = light;
this.dark = dark;
}
// Every other colour query of Color, and Java2D's painting, go through this one.
@Override
public int getRGB() {
return current().getRGB();
}
String toHtml() {
return String.format("#%06X", getRGB() & 0xFFFFFF);
}
private Color current() {
return StudioTheme.isDark() ? dark : light;
}
}