diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index be9630a1d..1debaa93b 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -410,6 +410,7 @@ PreferencesOptionPanel.checkbox.windowInfo.ttip = If unchecked, window informati UITheme.Auto = Auto (detect) UITheme.Light = Light (default) UITheme.Dark = Dark +UITheme.DarkContrast = Dark, high-contrast ! Welcome dialog welcome.dlg.title = Welcome to OpenRocket diff --git a/swing/src/net/sf/openrocket/gui/components/BasicSlider.java b/swing/src/net/sf/openrocket/gui/components/BasicSlider.java index 026641768..af43fe687 100644 --- a/swing/src/net/sf/openrocket/gui/components/BasicSlider.java +++ b/swing/src/net/sf/openrocket/gui/components/BasicSlider.java @@ -1,5 +1,8 @@ package net.sf.openrocket.gui.components; +import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.UITheme; + import javax.swing.BoundedRangeModel; import javax.swing.JSlider; import javax.swing.plaf.basic.BasicSliderUI; @@ -27,7 +30,11 @@ public class BasicSlider extends JSlider { setOrientation(orientation); setInverted(inverted); setFocusable(false); - setUI(new BasicSliderUI(this)); + if (UITheme.isLightTheme(GUIUtil.getUITheme())) { + setUI(new BasicSliderUI(this)); + } else { + setUI(new DarkBasicSliderUI(this)); + } } } diff --git a/swing/src/net/sf/openrocket/gui/components/DarkBasicSliderUI.java b/swing/src/net/sf/openrocket/gui/components/DarkBasicSliderUI.java new file mode 100644 index 000000000..b22ee1885 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/components/DarkBasicSliderUI.java @@ -0,0 +1,51 @@ +package net.sf.openrocket.gui.components; + +import javax.swing.JSlider; +import javax.swing.plaf.basic.BasicSliderUI; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; + +/** + * BasicSliderUI for dark theme UI. + */ +public class DarkBasicSliderUI extends BasicSliderUI { + private static final Color trackColor = new Color(159, 159, 159); + private static final Color thumbColor = new Color(82, 82, 82); + private static final Color thumbBorderColor = new Color(166, 166, 166); + + public DarkBasicSliderUI(JSlider b) { + super(b); + } + + @Override + public void paintTrack(Graphics g) { + g.setColor(trackColor); + super.paintTrack(g); + } + + @Override + public void paintThumb(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + Rectangle thumbBounds = thumbRect; + int w = thumbBounds.width; + int h = thumbBounds.height; + + int borderInset = 2; // Adjust this value to change the border thickness + + // Draw the border + g2d.setColor(thumbBorderColor); + g2d.fillRect(thumbBounds.x, thumbBounds.y, w, h); + + // Draw the thumb fill + g2d.setColor(thumbColor); + g2d.fillRect( + thumbBounds.x + borderInset - 1, + thumbBounds.y + borderInset - 1, + w - 2 * borderInset + 1, + h - 2 * borderInset + 1 + ); + } + +} \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java index 404b9b0fc..f3dd1d329 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java @@ -9,6 +9,7 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.UIManager; +import javax.swing.border.Border; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.components.DescriptionArea; @@ -17,6 +18,7 @@ import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.components.URLLabel; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.Icons; +import net.sf.openrocket.gui.util.UITheme; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BuildProperties; @@ -85,6 +87,12 @@ public class AboutDialog extends JDialog { "Enhanced components database for OpenRocket" + href("https://github.com/dbcook/openrocket-database", true, true) + ""; + private static Border border; + + static { + initColors(); + } + private String href(String url, boolean delimiters, boolean leadingSpace) { return (leadingSpace ? " " : "") + (delimiters ? "(" : "") + "" + url + "" + (delimiters ? ")" : ""); } @@ -142,6 +150,7 @@ public class AboutDialog extends JDialog { DescriptionArea info = new DescriptionArea(5); + info.setBorder(border); info.setText(CREDITS); info.setTextFont(UIManager.getFont("Label.font")); panel.add(info, "newline, width 10px, height 250lp, pushy, grow, spanx, wrap para"); @@ -170,4 +179,13 @@ public class AboutDialog extends JDialog { GUIUtil.setDisposableDialogOptions(this, close); } + + private static void initColors() { + updateColors(); + UITheme.Theme.addUIThemeChangeListener(AboutDialog::updateColors); + } + + private static void updateColors() { + border = GUIUtil.getUITheme().getBorder(); + } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java index ddb02dde9..53a19c5d3 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java @@ -47,6 +47,7 @@ class MotorInformationPanel extends JPanel { private static Color NO_COMMENT_COLOR; private static Color WITH_COMMENT_COLOR; private static Color textColor; + private static Color dimTextColor; private static Border border; // Motors in set @@ -103,7 +104,7 @@ class MotorInformationPanel extends JPanel { this.add(totalImpulseLabel, "split"); classificationLabel = new JLabel(); - classificationLabel.setEnabled(false); // Gray out + classificationLabel.setForeground(dimTextColor); this.add(classificationLabel, "gapleft unrel, wrap"); //// Avg. thrust: @@ -194,8 +195,8 @@ class MotorInformationPanel extends JPanel { // Add the data and formatting to the plot XYPlot plot = chart.getXYPlot(); - changeLabelFont(plot.getRangeAxis(), -2); - changeLabelFont(plot.getDomainAxis(), -2); + changeLabelFont(plot.getRangeAxis(), -2, textColor); + changeLabelFont(plot.getDomainAxis(), -2, textColor); //// Thrust curve: TextTitle title = new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont()); @@ -257,6 +258,7 @@ class MotorInformationPanel extends JPanel { NO_COMMENT_COLOR = GUIUtil.getUITheme().getDimTextColor(); WITH_COMMENT_COLOR = GUIUtil.getUITheme().getTextColor(); textColor = GUIUtil.getUITheme().getTextColor(); + dimTextColor = GUIUtil.getUITheme().getDimTextColor(); border = GUIUtil.getUITheme().getBorder(); } @@ -372,10 +374,11 @@ class MotorInformationPanel extends JPanel { comment.setCaretPosition(0); } - void changeLabelFont(ValueAxis axis, float size) { + void changeLabelFont(ValueAxis axis, float size, Color color) { Font font = axis.getTickLabelFont(); font = font.deriveFont(font.getSize2D() + size); axis.setTickLabelFont(font); + axis.setTickLabelPaint(color); } /** diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java index 60f4eff46..fb964c80e 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java @@ -609,7 +609,12 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec public static Color getColor(int index) { - return (Color) CURVE_COLORS[index % CURVE_COLORS.length]; + Color color = (Color) CURVE_COLORS[index % CURVE_COLORS.length]; + if (UITheme.isLightTheme(GUIUtil.getUITheme())) { + return color; + } else { + return color.brighter().brighter(); + } } diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 36f87e2da..78827fdea 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -1404,21 +1404,26 @@ public class SimulationPanel extends JPanel { * Focus on the simulation table and maintain the previous row selection(s). */ public void takeTheSpotlight() { - simulationTable.requestFocusInWindow(); - if (simulationTable.getRowCount() == 0 || simulationTable.getSelectedRows().length > 0) { - return; - } - if (previousSelection == null || previousSelection.length == 0) { - simulationTable.getSelectionModel().setSelectionInterval(0, 0); - } else { - simulationTable.clearSelection(); - for (int row : previousSelection) { - if (row < 0 || row >= simulationTable.getRowCount()) { - continue; + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + simulationTable.requestFocusInWindow(); + if (simulationTable.getRowCount() == 0 || simulationTable.getSelectedRows().length > 0) { + return; } - simulationTable.addRowSelectionInterval(row, row); + if (previousSelection == null || previousSelection.length == 0) { + simulationTable.getSelectionModel().setSelectionInterval(0, 0); + } else { + simulationTable.clearSelection(); + for (int row : previousSelection) { + if (row < 0 || row >= simulationTable.getRowCount()) { + continue; + } + simulationTable.addRowSelectionInterval(row, row); + } + } + updateActions(); } - } - updateActions(); + }); } } diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java index 653d1d80d..f1de09f0e 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java @@ -66,6 +66,7 @@ class SimulationOptionsPanel extends JPanel { JMenu extensionMenuCopyExtension; private static Color textColor; + private static Color dimTextColor; private static Border border; static { @@ -232,6 +233,7 @@ class SimulationOptionsPanel extends JPanel { private static void updateColors() { textColor = GUIUtil.getUITheme().getTextColor(); + dimTextColor = GUIUtil.getUITheme().getDimTextColor(); border = GUIUtil.getUITheme().getBorder(); } @@ -349,7 +351,7 @@ class SimulationOptionsPanel extends JPanel { if (simulation.getSimulationExtensions().isEmpty()) { StyledLabel l = new StyledLabel(trans.get("simedtdlg.SimExt.noExtensions"), Style.ITALIC); - l.setForeground(Color.DARK_GRAY); + l.setForeground(dimTextColor); currentExtensions.add(l, "growx, pad 5 5 5 5, wrap"); } else { for (SimulationExtension e : simulation.getSimulationExtensions()) { diff --git a/swing/src/net/sf/openrocket/gui/util/UITheme.java b/swing/src/net/sf/openrocket/gui/util/UITheme.java index 96cb57ac1..4492b03e3 100644 --- a/swing/src/net/sf/openrocket/gui/util/UITheme.java +++ b/swing/src/net/sf/openrocket/gui/util/UITheme.java @@ -2,6 +2,7 @@ package net.sf.openrocket.gui.util; import com.github.weisj.darklaf.LafManager; import com.github.weisj.darklaf.theme.DarculaTheme; +import com.github.weisj.darklaf.theme.OneDarkTheme; import com.jthemedetecor.OsThemeDetector; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; @@ -157,6 +158,9 @@ public class UITheme { } public enum Themes implements Theme { + /* + Standard light theme + */ LIGHT { private final String displayName = trans.get("UITheme.Light"); @@ -502,8 +506,12 @@ public class UITheme { return "tracker"; } }, + /* + Dark theme + */ DARK { private final String displayName = trans.get("UITheme.Dark"); + @Override public void applyTheme() { final SwingPreferences prefs = (SwingPreferences) Application.getPreferences(); @@ -537,7 +545,7 @@ public class UITheme { @Override public Color getDimTextColor() { - return new Color(162, 162, 162); + return new Color(182, 182, 182); } @Override @@ -845,6 +853,355 @@ public class UITheme { return "tracker_dark"; } }, + /* + High-contrast dark theme + */ + DARK_CONTRAST { + private final String displayName = trans.get("UITheme.DarkContrast"); + + @Override + public void applyTheme() { + final SwingPreferences prefs = (SwingPreferences) Application.getPreferences(); + + LafManager.install(new OneDarkTheme()); + setGlobalFontSize(prefs.getUIFontSize()); + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public Color getBackgroundColor() { + return new Color(43, 45, 51); + } + + @Override + public Color getBorderColor() { + return new Color(163, 163, 163, 204); + } + + @Override + public Color getTextColor() { + return UIManager.getColor("Label.foreground"); + } + + @Override + public Color getDimTextColor() { + return new Color(165, 171, 184); + } + + @Override + public Color getTextSelectionForegroundColor() { + return Color.WHITE; + } + + @Override + public Color getTextSelectionBackgroundColor() { + return new Color(62, 108, 173); + } + + @Override + public Color getWarningColor() { + return new Color(255, 173, 173); + } + + @Override + public Color getDarkWarningColor() { + return new Color(255, 178, 178); + } + + @Override + public Color getRowBackgroundLighterColor() { + return new Color(43, 49, 58); + } + + @Override + public Color getRowBackgroundDarkerColor() { + return new Color(34, 37, 44); + } + + @Override + public Color getFlightDataTextActiveColor() { + return new Color(212, 230, 255); + } + + @Override + public Color getFlightDataTextInactiveColor() { + return new Color(170, 201, 255, 127); + } + + @Override + public Color getMultiCompEditColor() { + return new Color(255, 165, 200); + } + + @Override + public String getDefaultBodyComponentColor() { + return "150,175,255"; + } + @Override + public String getDefaultTubeFinSetColor() { + return "150,184,254"; + } + @Override + public String getDefaultFinSetColor() { + return "150,184,255"; + } + @Override + public String getDefaultLaunchLugColor() { + return "142,153,238"; + } + @Override + public String getDefaultRailButtonColor() { + return "142,153,238"; + } + @Override + public String getDefaultInternalComponentColor() { + return "181,128,151"; + } + @Override + public String getDefaultMassObjectColor() { + return "210,210,210"; + } + @Override + public String getDefaultRecoveryDeviceColor() { + return "220,90,90"; + } + @Override + public String getDefaultPodSetColor() { + return "190,190,235"; + } + @Override + public String getDefaultParallelStageColor() { + return "210,180,195"; + } + + @Override + public Color getMotorBorderColor() { + return new Color(255, 255, 255, 200); + } + + @Override + public Color getMotorFillColor() { + return new Color(0, 0, 0, 70); + } + + @Override + public Color getCGColor() { + return new Color(85, 133, 253); + } + + @Override + public Color getCPColor() { + return new Color(255, 72, 106); + } + + @Override + public Color getURLColor() { + return new Color(171, 185, 255); + } + + @Override + public Color getComponentTreeBackgroundColor() { + return getBackgroundColor(); + } + + @Override + public Color getComponentTreeForegroundColor() { + return getTextColor(); + } + + @Override + public Color getFinPointGridMajorLineColor() { + return new Color(164, 164, 224, 197); + } + + @Override + public Color getFinPointGridMinorLineColor() { + return new Color(134, 134, 201, 69); + } + + @Override + public Color getFinPointPointColor() { + return new Color(242, 121, 121, 255); + } + + @Override + public Color getFinPointSelectedPointColor() { + return new Color(232, 78, 78, 255); + } + + @Override + public Color getFinPointBodyLineColor() { + return Color.WHITE; + } + + @Override + public Icon getMassOverrideIcon() { + return Icons.MASS_OVERRIDE_DARK; + } + + @Override + public Icon getMassOverrideSubcomponentIcon() { + return Icons.MASS_OVERRIDE_SUBCOMPONENT_DARK; + } + + @Override + public Icon getCGOverrideIcon() { + return Icons.CG_OVERRIDE_DARK; + } + + @Override + public Icon getCGOverrideSubcomponentIcon() { + return Icons.CG_OVERRIDE_SUBCOMPONENT_DARK; + } + + @Override + public Icon getCDOverrideIcon() { + return Icons.CD_OVERRIDE_DARK; + } + + @Override + public Icon getCDOverrideSubcomponentIcon() { + return Icons.CD_OVERRIDE_SUBCOMPONENT_DARK; + } + + @Override + public Border getBorder() { + return BorderFactory.createLineBorder(getBorderColor()); + } + + @Override + public void formatScriptTextArea(RSyntaxTextArea textArea) { + try { + org.fife.ui.rsyntaxtextarea.Theme theme = org.fife.ui.rsyntaxtextarea.Theme.load(getClass().getResourceAsStream( + "/org/fife/ui/rsyntaxtextarea/themes/monokai.xml")); + theme.apply(textArea); + } catch (IOException ioe) { + log.warn("Unable to load RSyntaxTextArea theme", ioe); + } + } + + @Override + public String getComponentIconNoseCone() { + return DARK.getComponentIconNoseCone(); + } + @Override + public String getComponentIconBodyTube() { + return DARK.getComponentIconBodyTube(); + } + @Override + public String getComponentIconTransition() { + return DARK.getComponentIconTransition(); + } + + @Override + public String getComponentIconTrapezoidFinSet() { + return DARK.getComponentIconTrapezoidFinSet(); + } + @Override + public String getComponentIconEllipticalFinSet() { + return DARK.getComponentIconEllipticalFinSet(); + } + @Override + public String getComponentIconFreeformFinSet() { + return DARK.getComponentIconFreeformFinSet(); + } + @Override + public String getComponentIconTubeFinSet() { + return DARK.getComponentIconTubeFinSet(); + } + @Override + public String getComponentIconLaunchLug() { + return DARK.getComponentIconLaunchLug(); + } + @Override + public String getComponentIconRailButton() { + return DARK.getComponentIconRailButton(); + } + @Override + public String getComponentIconInnerTube() { + return DARK.getComponentIconInnerTube(); + } + @Override + public String getComponentIconTubeCoupler() { + return DARK.getComponentIconTubeCoupler(); + } + @Override + public String getComponentIconCenteringRing() { + return DARK.getComponentIconCenteringRing(); + } + @Override + public String getComponentIconBulkhead() { + return DARK.getComponentIconBulkhead(); + } + @Override + public String getComponentIconEngineBlock() { + return DARK.getComponentIconEngineBlock(); + } + @Override + public String getComponentIconParachute() { + return DARK.getComponentIconParachute(); + } + @Override + public String getComponentIconStreamer() { + return DARK.getComponentIconStreamer(); + } + @Override + public String getComponentIconShockCord() { + return DARK.getComponentIconShockCord(); + } + @Override + public String getComponentIconMass() { + return DARK.getComponentIconMass(); + } + @Override + public String getComponentIconStage() { + return DARK.getComponentIconStage(); + } + @Override + public String getComponentIconBoosters() { + return DARK.getComponentIconBoosters(); + } + @Override + public String getComponentIconPods() { + return DARK.getComponentIconPods(); + } + @Override + public String getComponentIconMassAltimeter() { + return DARK.getComponentIconMassAltimeter(); + } + + @Override + public String getComponentIconMassBattery() { + return DARK.getComponentIconMassBattery(); + } + @Override + public String getComponentIconMassDeploymentCharge() { + return DARK.getComponentIconMassDeploymentCharge(); + } + @Override + public String getComponentIconMassPayload() { + return DARK.getComponentIconMassPayload(); + } + @Override + public String getComponentIconMassFlightComp() { + return DARK.getComponentIconMassFlightComp(); + } + @Override + public String getComponentIconMassRecoveryHardware() { + return DARK.getComponentIconMassRecoveryHardware(); + } + @Override + public String getComponentIconMassTracker() { + return DARK.getComponentIconMassTracker(); + } + }, + /* + Detect best theme based on operating system theme + */ AUTO { private final String displayName = trans.get("UITheme.Auto"); @@ -853,7 +1210,7 @@ public class UITheme { final OsThemeDetector detector = OsThemeDetector.getDetector(); final boolean isDarkThemeUsed = detector.isDark(); if (isDarkThemeUsed) { - return Themes.DARK; + return Themes.DARK_CONTRAST; } else { return Themes.LIGHT; }