diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index fd8bbc143..10daf4f2e 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -406,7 +406,8 @@ PreferencesOptionPanel.checkbox.windowInfo = Export window information (position PreferencesOptionPanel.checkbox.windowInfo.ttip = If unchecked, window information (position, size\u2026) will not be exported. ! UI Themes -UITheme.Light = Light (Default) +UITheme.Auto = Auto (detect) +UITheme.Light = Light (default) UITheme.Dark = Dark ! Welcome dialog diff --git a/swing/.classpath b/swing/.classpath index 5aa1c307e..763ac63b0 100644 --- a/swing/.classpath +++ b/swing/.classpath @@ -11,6 +11,13 @@ + + + + + + + diff --git a/swing/OpenRocket Swing.iml b/swing/OpenRocket Swing.iml index 55f1c84b1..fd79a03a1 100644 --- a/swing/OpenRocket Swing.iml +++ b/swing/OpenRocket Swing.iml @@ -5,6 +5,13 @@ + $ + + + + + + @@ -109,9 +116,63 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/swing/build.xml b/swing/build.xml index 80ef81afe..af8a1d3f5 100644 --- a/swing/build.xml +++ b/swing/build.xml @@ -116,6 +116,13 @@ + + + + + + + diff --git a/swing/lib/annotations-24.0.1.jar b/swing/lib/annotations-24.0.1.jar new file mode 100644 index 000000000..a4198f711 Binary files /dev/null and b/swing/lib/annotations-24.0.1.jar differ diff --git a/swing/lib/jSystemThemeDetector-3.8.jar b/swing/lib/jSystemThemeDetector-3.8.jar new file mode 100644 index 000000000..a8ac11f54 Binary files /dev/null and b/swing/lib/jSystemThemeDetector-3.8.jar differ diff --git a/swing/lib/jfa-1.2.0.jar b/swing/lib/jfa-1.2.0.jar new file mode 100644 index 000000000..2efd423eb Binary files /dev/null and b/swing/lib/jfa-1.2.0.jar differ diff --git a/swing/lib/jna-5.13.0.jar b/swing/lib/jna-5.13.0.jar new file mode 100644 index 000000000..3d49c8188 Binary files /dev/null and b/swing/lib/jna-5.13.0.jar differ diff --git a/swing/lib/jna-platform-5.13.0.jar b/swing/lib/jna-platform-5.13.0.jar new file mode 100644 index 000000000..816a567cc Binary files /dev/null and b/swing/lib/jna-platform-5.13.0.jar differ diff --git a/swing/lib/oshi-core-6.4.4.jar b/swing/lib/oshi-core-6.4.4.jar new file mode 100644 index 000000000..2e8b714f9 Binary files /dev/null and b/swing/lib/oshi-core-6.4.4.jar differ diff --git a/swing/lib/versioncompare-1.4.1.jar b/swing/lib/versioncompare-1.4.1.jar new file mode 100644 index 000000000..14ab48bd6 Binary files /dev/null and b/swing/lib/versioncompare-1.4.1.jar differ diff --git a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java index 711dc0f1d..404b9b0fc 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java @@ -75,9 +75,10 @@ public class AboutDialog extends JDialog { "Simple Logging Facade for Java" + href("http://www.slf4j.org", true, true) + "
" + "Java library for parsing and rendering CommonMark" + href("https://github.com/commonmark/commonmark-java", true, true) + "
" + "RSyntaxTextArea" + href("http://bobbylight.github.io/RSyntaxTextArea", true, true) + "
" + - "Darklaf (dark theme)" + href("https://github.com/weisJ/darklaf", true, true) + "
" + + "Darklaf (dark theme)" + href("https://github.com/weisJ/darklaf", true, true) + "
" + + "jSystemThemeDetector" + href("https://github.com/Dansoftowner/jSystemThemeDetector", true, true) + "
" + "Obj" + href("https://github.com/javagl/Obj", true, true) + "
" + - "
" + + "
" + "OpenRocket gratefully acknowledges our use of the following databases:
" + "
" + "Rocket Motor Data" + href("https://www.thrustcurve.org", true, true) + "
" + diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java index 49674d686..4e461b983 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorFilterPanel.java @@ -1,5 +1,6 @@ package net.sf.openrocket.gui.dialogs.motor.thrustcurve; +import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; @@ -16,6 +17,7 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; +import javax.swing.border.Border; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -144,26 +146,25 @@ public abstract class MotorFilterPanel extends JPanel { // Manufacturer selection JPanel sub = new JPanel(new MigLayout("fill")); - TitledBorder border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.MANUFACTURER")); + Border templateBorder = GUIUtil.getUITheme().getBorder(); + TitledBorder border = BorderFactory.createTitledBorder(templateBorder); + border.setTitle(trans.get("TCurveMotorCol.MANUFACTURER")); GUIUtil.changeFontStyle(border, Font.BOLD); sub.setBorder(border); this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); - List manufacturers = new ArrayList(); - for (Manufacturer m : allManufacturers) { - manufacturers.add(m); - } + List manufacturers = new ArrayList<>(allManufacturers); - Collections.sort(manufacturers, new Comparator() { - @Override - public int compare(Manufacturer o1, Manufacturer o2) { - return o1.getSimpleName().compareTo( o2.getSimpleName()); - } + manufacturers.sort(new Comparator() { + @Override + public int compare(Manufacturer o1, Manufacturer o2) { + return o1.getSimpleName().compareTo(o2.getSimpleName()); + } - }); + }); - manufacturerCheckList = new CheckList.Builder().build(); + manufacturerCheckList = new CheckList.Builder().build(); manufacturerCheckList.setData(manufacturers); manufacturerCheckList.setUncheckedItems(unselectedManusFromPreferences); @@ -184,7 +185,12 @@ public abstract class MotorFilterPanel extends JPanel { } }); - sub.add(new JScrollPane(manufacturerCheckList.getList()), "grow, pushy, wrap"); + JScrollPane scrollPane = new JScrollPane(manufacturerCheckList.getList()); + Border border1 = GUIUtil.getUITheme().getBorder(); + if (border1 != null) { + scrollPane.setBorder(border1); + } + sub.add(scrollPane, "grow, pushy, wrap"); JButton clearMotors = new SelectColorButton(trans.get("TCMotorSelPan.btn.checkNone")); clearMotors.addActionListener( new ActionListener() { @@ -213,7 +219,8 @@ public abstract class MotorFilterPanel extends JPanel { // Total Impulse selection { sub = new JPanel(new MigLayout("fill")); - border = BorderFactory.createTitledBorder(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); + border = BorderFactory.createTitledBorder(templateBorder); + border.setTitle(trans.get("TCurveMotorCol.TOTAL_IMPULSE")); GUIUtil.changeFontStyle(border, Font.BOLD); sub.setBorder(border); @@ -240,7 +247,8 @@ public abstract class MotorFilterPanel extends JPanel { // Motor Dimensions sub = new JPanel(new MigLayout("fill")); - TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(trans.get("TCMotorSelPan.MotorSize")); + TitledBorder diameterTitleBorder = BorderFactory.createTitledBorder(templateBorder); + diameterTitleBorder.setTitle(trans.get("TCMotorSelPan.MotorSize")); GUIUtil.changeFontStyle(diameterTitleBorder, Font.BOLD); sub.setBorder(diameterTitleBorder); 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 de63acf0e..741b61ecd 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 @@ -159,6 +159,7 @@ class MotorInformationPanel extends JPanel { comment = new JTextArea(5, 5); + comment.setBorder(GUIUtil.getUITheme().getBorder()); GUIUtil.changeFontSize(comment, -2); withCommentFont = comment.getFont(); noCommentFont = withCommentFont.deriveFont(Font.ITALIC); 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 eebbbe927..2cc104581 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 @@ -45,6 +45,7 @@ import javax.swing.event.RowSorterListener; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; +import net.sf.openrocket.gui.util.UITheme; import net.sf.openrocket.util.StateChangeListener; import org.jfree.chart.ChartColor; import org.slf4j.Logger; @@ -332,11 +333,10 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec // Number of motors { - nrOfMotorsLabel = new JLabel(); + nrOfMotorsLabel = new StyledLabel(-2f, StyledLabel.Style.ITALIC); nrOfMotorsLabel.setToolTipText(trans.get("TCMotorSelPan.lbl.ttip.nrOfMotors")); updateNrOfMotors(); - nrOfMotorsLabel.setForeground(Color.darkGray); - nrOfMotorsLabel.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 11)); + nrOfMotorsLabel.setForeground(GUIUtil.getUITheme().getDimTextColor()); panel.add(nrOfMotorsLabel, "gapleft para, spanx, wrap"); sorter.addRowSorterListener(new RowSorterListener() { @Override diff --git a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java index 41cac74ba..9396c1ec9 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java @@ -21,7 +21,6 @@ import net.sf.openrocket.util.Color; import com.jogamp.opengl.util.texture.Texture; public class RealisticRenderer extends RocketRenderer { - private final float[] colorClear = { 0, 0, 0, 0 }; private final float[] colorWhite = { 1, 1, 1, 1 }; private final float[] color = new float[4]; @@ -165,7 +164,7 @@ public class RealisticRenderer extends RocketRenderer { gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, toEdgeMode(t.getEdgeMode())); - gl.glTexParameterfv(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BORDER_COLOR, colorClear, 0); + gl.glTexParameterfv(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BORDER_COLOR, convertedColor, 0); gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, colorWhite, 0); gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, colorWhite, 0); diff --git a/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java b/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java index 342946b6f..f9fa90151 100644 --- a/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java +++ b/swing/src/net/sf/openrocket/gui/print/FinMarkingGuide.java @@ -6,6 +6,8 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.awt.image.BufferedImage; @@ -28,6 +30,7 @@ import net.sf.openrocket.rocketcomponent.RailButton; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.MathUtil; /** * This is the core Swing representation of a fin marking guide. It can handle multiple fin and/or tube fin sets @@ -239,13 +242,16 @@ public class FinMarkingGuide extends JPanel { private void paintFinMarkingGuide(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - g2.setColor(Color.BLACK); + + final Color lineColor = Color.BLACK; + + g2.setColor(lineColor); g2.setStroke(thinStroke); int x = MARGIN; int y = MARGIN; int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH); + int length; int column = 0; @@ -255,8 +261,8 @@ public class FinMarkingGuide extends JPanel { List componentList = markingGuideItems.get(next); //Don't draw the lug if there are no fins. if (hasFins(componentList)) { - - drawMarkingGuide(g2, x, y, (int) Math.ceil(circumferenceInPoints), width); + length = (int) Math.ceil(circumferenceInPoints); + drawMarkingGuide(g2, x, y, length, width); double radialOrigin = findRadialOrigin(componentList); @@ -284,13 +290,121 @@ public class FinMarkingGuide extends JPanel { while (angle > TWO_PI) { angle -= TWO_PI; } - - int offset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints); - - drawDoubleArrowLine(g2, x, offset, x + width, offset); - // if (hasMultipleComponents) { - g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2); - // } + + final int yFinCenter = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints); + final int yStart; + final int yEnd; + + // Account for canted fins + /* + The arrow will be rotated around the aft base end of the fin. + This is because the aft end will most likely be at the aft end of the body tube. + If we were to rotate around the fore end, there's a good chance that the marking guide + extends beyond the body tube aft end and thus you cannot draw the arrow. + */ + final double cantAngle = fins.getCantAngle(); + final boolean isCanted = !MathUtil.equals(cantAngle, 0); + if (isCanted) { + // We want to end the arrow at the aft end of the fin, so we need add an offset to + // the end to account for the y-shift of the aft end of the fin due to the cant. + final double finBaseHalfWidth = PrintUnit.METERS.toPoints(fins.getLength()) / 2; + final int yFinForeEndOffset = - (int) Math.round(finBaseHalfWidth * Math.sin(cantAngle)); + yStart = yFinCenter + yFinForeEndOffset; + + // Calculate y offset of end point + int yOffset = (int) Math.round(width * Math.tan(cantAngle)); + yEnd = yStart + yOffset; + } else { + yStart = yFinCenter; + yEnd = yFinCenter; + } + + // Draw double arrow + drawDoubleArrowLine(g2, x, yStart, x + width, yEnd, cantAngle); + + // Draw horizontal dotted line where fin aft end is, vertical dotted line where the fore end is + // and cross at the fin center + if (isCanted) { + //// -- Aft end dashed line + // Dashed stroke settings + float originalLineWidth = thinStroke.getLineWidth(); + float[] dashPattern = {10, 10}; // 10 pixel dash, 10 pixel space + Stroke dashedStroke = new BasicStroke( + originalLineWidth, + thinStroke.getEndCap(), + thinStroke.getLineJoin(), + thinStroke.getMiterLimit(), + dashPattern, + 0 + ); + + // Set color and stroke + g2.setColor(new Color(200, 200, 200)); + g2.setStroke(dashedStroke); + + // Draw aft end horizontal dashed line + // We draw from right to left to ensure that the side where the side does not touch + // with an arrow point (fore end) has the dashed line touching the marking guide edge + // (is useful for marking the fin position) + g2.drawLine(x + width, yStart, x, yStart); + + //// -- Fore end dashed line + // Dashed stroke settings + dashPattern = new float[] {4, 6}; // 4 pixel dash, 6 pixel space + dashedStroke = new BasicStroke( + originalLineWidth * 0.7f, + thinStroke.getEndCap(), + thinStroke.getLineJoin(), + thinStroke.getMiterLimit(), + dashPattern, + 0 + ); + + // Set color and stroke + g2.setColor(new Color(220, 220, 220)); + g2.setStroke(dashedStroke); + + // Draw fore end vertical dashed line + final int finBaseWidth = (int) PrintUnit.METERS.toPoints(fins.getLength()); + if (finBaseWidth < width) { + g2.drawLine(x + finBaseWidth, y, x + finBaseWidth, y + length); + } + + // Reset stroke + g2.setStroke(thinStroke); + + //// -- Cross + final double finBaseHalfWidth = PrintUnit.METERS.toPoints(fins.getLength()) / 2; + + // The cant also has an x-shift. We want the aft end to be perfectly flush with the + // left of the marking guide, so apply an x-shift to fin center position + int xFinCenter = x + (int) Math.round(finBaseHalfWidth); + int xFinCenterOffset = - (int) Math.round(finBaseHalfWidth * (1 - Math.cos(cantAngle))); + xFinCenter += xFinCenterOffset; + + // Draw a cross where the center of the fin should be + int crossSize = 3; + g2.drawLine(xFinCenter-crossSize, yFinCenter-crossSize, xFinCenter+crossSize, yFinCenter+crossSize); + g2.drawLine(xFinCenter-crossSize, yFinCenter+crossSize, xFinCenter+crossSize, yFinCenter-crossSize); + + // Reset color + g2.setColor(lineColor); + } + + // Draw fin name + final int xText = x + (width / 3); + int yText = yStart - 2; + if (isCanted) { + int yTextOffset = (int) Math.round((xText - x) * Math.tan(cantAngle)); + yText += yTextOffset; + + AffineTransform orig = g2.getTransform(); + g2.rotate(cantAngle, xText, yText); // Rotate text for canted fins + g2.drawString(externalComponent.getName(), xText, yText); + g2.setTransform(orig); // Stop rotation + } else { + g2.drawString(externalComponent.getName(), xText, yText); + } } } // END If FinSet instance @@ -318,9 +432,7 @@ public class FinMarkingGuide extends JPanel { int offset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints); drawDoubleArrowLine(g2, x, offset, x + width, offset); - // if (hasMultipleComponents) { g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2); - // } } } // END If TubeFinSet instance @@ -354,9 +466,9 @@ public class FinMarkingGuide extends JPanel { // END If RailButton instance } // Only if the tube has a lug or button or multiple fin and/or tube fin sets does the orientation of - // the marking guide matter. So print 'Front'. + // the marking guide matter. So print 'Fore end'. if (hasMultipleComponents) { - drawFrontIndication(g2, x, y, 0, (int) circumferenceInPoints, width); + drawFrontIndication(g2, x, y + length, width); } // At most, two marking guides horizontally. After that, move down and back to the left margin. @@ -505,36 +617,131 @@ public class FinMarkingGuide extends JPanel { g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length); //Lower right g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length); + + drawFrontIndication(g2, x, y + length, width); } /** - * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug and/or + * Draw a tab indicating the fore end of the rocket. This is necessary when a launch lug and/or * rail button exists to give proper orientation of the guide (assuming that the lug and/or button is - * asymmetrically positioned with respect to a fin). + * asymmetrically positioned with respect to a fin). Also necessary for canted fins. * * @param g2 the graphics context * @param x the starting x coordinate * @param y the starting y coordinate - * @param spacing the space between fin lines - * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube - * circumference * @param width the width of the marking guide in print units; somewhat arbitrary */ - private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) { - //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be - //readable, without going to complex string layout prediction logic. - int rotateX = x + width - 16; - int rotateY = y + (int) (spacing * 1.5) + 20; - if (rotateY > y + length + 14) { - rotateY = y + length / 2 - 10; - } - g2.translate(rotateX, rotateY); - g2.rotate(Math.PI / 2); - g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0); - g2.rotate(-Math.PI / 2); - g2.translate(-rotateX, -rotateY); + private void drawFrontIndication(Graphics2D g2, int x, int y, int width) { + // Draw a tab at the bottom of the marking guide to indicate the fore end of the rocket + int tabWidth = (int) Math.round(width * 0.8); + int tabSpacing = (width - tabWidth) / 2; + int tabHeight = 20; + float strokeWidth = 1.0f; + float strokeOffset = thinStroke.getLineWidth() / 2; // Offset to not draw over the marking guide stroke + + Stroke origStroke = g2.getStroke(); + Stroke stroke = new BasicStroke(strokeWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL); + g2.setStroke(stroke); + + // Draw the tab outline + Path2D tab = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4); + tab.moveTo(x, y + strokeOffset); + tab.lineTo(x + width, y + strokeOffset); + tab.lineTo(x + width - tabSpacing, y + tabHeight); + tab.lineTo(x + tabSpacing, y + tabHeight); + tab.closePath(); + g2.draw(tab); + + // Reset the stroke + g2.setStroke(origStroke); + + // Fill in the tab + Color color = g2.getColor(); + g2.setColor(new Color(220, 220, 220)); + g2.fill(tab); + g2.setColor(color); + + // Draw an arrow to the left and the text "Fore" + final int arrowXStart = x + width - tabSpacing - 5; + final int arrowWidth = 50; + final int arrowY = y + (tabHeight / 2); + final int textY = arrowY + g2.getFontMetrics().getHeight() / 2 - 3; + drawRightArrowLine(g2, arrowXStart - arrowWidth, arrowY, arrowXStart, arrowY); + String frontText = trans.get("FinMarkingGuide.lbl.Front"); + final int textWidth = g2.getFontMetrics().stringWidth(frontText); + g2.drawString(frontText, arrowXStart - arrowWidth - textWidth - 3, textY); } - + + /** + * Draw a horizontal line with arrows on only the right endpoint. + * + * @param g2 the graphics context + * @param x1 the starting x coordinate + * @param y1 the starting y coordinate + * @param x2 the ending x coordinate + * @param y2 the ending y coordinate + */ + void drawRightArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) { + drawArrowLine(g2, x1, y1, x2, y2, 0, false, true); + } + + /** + * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment. + * + * @param g2 the graphics context + * @param x1 the starting x coordinate + * @param y1 the starting y coordinate + * @param x2 the ending x coordinate + * @param y2 the ending y coordinate + * @param angle angle to rotate the arrow header to + * @param leftArrow true if the left arrow should be drawn + * @param rightArrow true if the right arrow should be drawn + */ + void drawArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2, double angle, boolean leftArrow, boolean rightArrow) { + int len = x2 - x1; + + // Draw line + int xOffset = (int) Math.round(ARROW_SIZE * Math.abs(Math.cos(angle))); + int yOffset = (int) Math.round(ARROW_SIZE * Math.sin(angle)); + g2.drawLine(x1 + xOffset, y1 + yOffset, x1 + len - xOffset, y2 - yOffset); + + // Rotate for the right arrow + AffineTransform orig = g2.getTransform(); + g2.rotate(angle, x1 + len, y2); + + // Draw right arrow + if (rightArrow) { + g2.fillPolygon(new int[]{x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len}, + new int[]{y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2}, 4); + } + + // Rotate for the left arrow + g2.setTransform(orig); + g2.rotate(angle, x1, y1); + + // Draw left arrow + if (leftArrow) { + g2.fillPolygon(new int[]{x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1}, + new int[]{y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1}, 4); + } + + g2.setTransform(orig); + } + + /** + * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment. + * + * @param g2 the graphics context + * @param x1 the starting x coordinate + * @param y1 the starting y coordinate + * @param x2 the ending x coordinate + * @param y2 the ending y coordinate + * @param angle angle to rotate the arrow header to + */ + void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2, double angle) { + drawArrowLine(g2, x1, y1, x2, y2, angle, true, true); + } + /** * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment. * @@ -545,13 +752,6 @@ public class FinMarkingGuide extends JPanel { * @param y2 the ending y coordinate */ void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) { - int len = x2 - x1; - - g2.drawLine(x1, y1, x1 + len, y2); - g2.fillPolygon(new int[] { x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len }, - new int[] { y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2 }, 4); - - g2.fillPolygon(new int[] { x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1 }, - new int[] { y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1 }, 4); + drawDoubleArrowLine(g2, x1, y1, x2, y2, 0); } } diff --git a/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java b/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java index d6f1600de..401efb169 100644 --- a/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java +++ b/swing/src/net/sf/openrocket/gui/util/CheckListRenderer.java @@ -33,10 +33,8 @@ package net.sf.openrocket.gui.util; import java.awt.Color; import java.awt.Component; -import java.awt.Rectangle; import java.io.Serializable; -import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; import javax.swing.JCheckBox; import javax.swing.JList; @@ -49,7 +47,7 @@ import javax.swing.border.EmptyBorder; public class CheckListRenderer extends JCheckBox implements ListCellRenderer, Serializable { private static final Border NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); - private static final Border SAFE_NO_FOCUS_BORDER = NO_FOCUS_BORDER; // may change in the feature + private static final Border SAFE_NO_FOCUS_BORDER = NO_FOCUS_BORDER; // may change in the future /** * Constructs a default renderer object for an item in a list. @@ -162,66 +160,4 @@ public class CheckListRenderer extends JCheckBox implements ListCellRenderer, Se } } - // Methods below are overridden for performance reasons. - - @Override - public void validate() { - } - - @Override - public void invalidate() { - } - - @Override - public void repaint() { - } - - @Override - public void revalidate() { - } - - @Override - public void repaint(long tm, int x, int y, int width, int height) { - } - - @Override - public void repaint(Rectangle r) { - } - - @Override - public void firePropertyChange(String propertyName, byte oldValue, byte newValue) { - } - - @Override - public void firePropertyChange(String propertyName, char oldValue, char newValue) { - } - - @Override - public void firePropertyChange(String propertyName, short oldValue, short newValue) { - } - - @Override - public void firePropertyChange(String propertyName, int oldValue, int newValue) { - } - - @Override - public void firePropertyChange(String propertyName, long oldValue, long newValue) { - } - - @Override - public void firePropertyChange(String propertyName, float oldValue, float newValue) { - } - - @Override - public void firePropertyChange(String propertyName, double oldValue, double newValue) { - } - - @Override - public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { - } - - @SuppressWarnings("serial") - public static class UIResource extends DefaultListCellRenderer implements javax.swing.plaf.UIResource { - } - } \ No newline at end of file diff --git a/swing/src/net/sf/openrocket/gui/util/UITheme.java b/swing/src/net/sf/openrocket/gui/util/UITheme.java index 43805d870..ecc1a2d1a 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.jthemedetecor.OsThemeDetector; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; @@ -10,14 +11,17 @@ import org.slf4j.LoggerFactory; import javax.swing.BorderFactory; import javax.swing.Icon; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import java.awt.Color; import java.awt.Font; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; public class UITheme { private static final Translator trans = Application.getTranslator(); @@ -527,6 +531,243 @@ public class UITheme { log.warn("Unable to load RSyntaxTextArea theme", ioe); } } + }, + AUTO { + private final String displayName = trans.get("UITheme.Auto"); + + private Theme getCurrentTheme() { + try { + final OsThemeDetector detector = OsThemeDetector.getDetector(); + final boolean isDarkThemeUsed = detector.isDark(); + if (isDarkThemeUsed) { + return Themes.DARK; + } else { + return Themes.LIGHT; + } + } catch (Exception ignore) {} + + return Themes.LIGHT; + } + + @Override + public void applyTheme() { + getCurrentTheme().applyTheme(); + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public Color getBackgroundColor() { + return getCurrentTheme().getBackgroundColor(); + } + + @Override + public Color getBorderColor() { + return getCurrentTheme().getBorderColor(); + } + + @Override + public Color getTextColor() { + return getCurrentTheme().getTextColor(); + } + + @Override + public Color getDimTextColor() { + return getCurrentTheme().getDimTextColor(); + } + + @Override + public Color getTextSelectionForegroundColor() { + return getCurrentTheme().getTextSelectionForegroundColor(); + } + + @Override + public Color getTextSelectionBackgroundColor() { + return getCurrentTheme().getTextSelectionBackgroundColor(); + } + + @Override + public Color getWarningColor() { + return getCurrentTheme().getWarningColor(); + } + + @Override + public Color getDarkWarningColor() { + return getCurrentTheme().getDarkWarningColor(); + } + + @Override + public Color getRowBackgroundLighterColor() { + return getCurrentTheme().getRowBackgroundLighterColor(); + } + + @Override + public Color getRowBackgroundDarkerColor() { + return getCurrentTheme().getRowBackgroundDarkerColor(); + } + + @Override + public Color getFlightDataTextActiveColor() { + return getCurrentTheme().getFlightDataTextActiveColor(); + } + + @Override + public Color getFlightDataTextInactiveColor() { + return getCurrentTheme().getFlightDataTextInactiveColor(); + } + + @Override + public String getDefaultBodyComponentColor() { + return getCurrentTheme().getDefaultBodyComponentColor(); + } + + @Override + public String getDefaultTubeFinSetColor() { + return getCurrentTheme().getDefaultTubeFinSetColor(); + } + + @Override + public String getDefaultFinSetColor() { + return getCurrentTheme().getDefaultFinSetColor(); + } + + @Override + public String getDefaultLaunchLugColor() { + return getCurrentTheme().getDefaultLaunchLugColor(); + } + + @Override + public String getDefaultRailButtonColor() { + return getCurrentTheme().getDefaultRailButtonColor(); + } + + @Override + public String getDefaultInternalComponentColor() { + return getCurrentTheme().getDefaultInternalComponentColor(); + } + + @Override + public String getDefaultMassObjectColor() { + return getCurrentTheme().getDefaultMassObjectColor(); + } + + @Override + public String getDefaultRecoveryDeviceColor() { + return getCurrentTheme().getDefaultRecoveryDeviceColor(); + } + + @Override + public String getDefaultPodSetColor() { + return getCurrentTheme().getDefaultPodSetColor(); + } + + @Override + public String getDefaultParallelStageColor() { + return getCurrentTheme().getDefaultParallelStageColor(); + } + + @Override + public Color getMotorBorderColor() { + return getCurrentTheme().getMotorBorderColor(); + } + + @Override + public Color getMotorFillColor() { + return getCurrentTheme().getMotorFillColor(); + } + + @Override + public Color getCGColor() { + return getCurrentTheme().getCGColor(); + } + + @Override + public Color getCPColor() { + return getCurrentTheme().getCPColor(); + } + + @Override + public Color getURLColor() { + return getCurrentTheme().getURLColor(); + } + + @Override + public Color getComponentTreeBackgroundColor() { + return getCurrentTheme().getComponentTreeBackgroundColor(); + } + + @Override + public Color getComponentTreeForegroundColor() { + return getCurrentTheme().getComponentTreeForegroundColor(); + } + + @Override + public Color getFinPointGridMajorLineColor() { + return getCurrentTheme().getFinPointGridMajorLineColor(); + } + + @Override + public Color getFinPointGridMinorLineColor() { + return getCurrentTheme().getFinPointGridMinorLineColor(); + } + + @Override + public Color getFinPointPointColor() { + return getCurrentTheme().getFinPointPointColor(); + } + + @Override + public Color getFinPointSelectedPointColor() { + return getCurrentTheme().getFinPointSelectedPointColor(); + } + + @Override + public Color getFinPointBodyLineColor() { + return getCurrentTheme().getFinPointBodyLineColor(); + } + + @Override + public Icon getMassOverrideIcon() { + return getCurrentTheme().getMassOverrideIcon(); + } + + @Override + public Icon getMassOverrideSubcomponentIcon() { + return getCurrentTheme().getMassOverrideSubcomponentIcon(); + } + + @Override + public Icon getCGOverrideIcon() { + return getCurrentTheme().getCGOverrideIcon(); + } + + @Override + public Icon getCGOverrideSubcomponentIcon() { + return getCurrentTheme().getCGOverrideSubcomponentIcon(); + } + + @Override + public Icon getCDOverrideIcon() { + return getCurrentTheme().getCDOverrideIcon(); + } + + @Override + public Icon getCDOverrideSubcomponentIcon() { + return getCurrentTheme().getCDOverrideSubcomponentIcon(); + } + + @Override + public Border getBorder() { + return getCurrentTheme().getBorder(); + } + + @Override + public void formatScriptTextArea(RSyntaxTextArea textArea) { + getCurrentTheme().formatScriptTextArea(textArea); + } } }