From feed204b8834eee40cd02bfe553abea01bbd21ab Mon Sep 17 00:00:00 2001 From: mkpaz Date: Mon, 14 Oct 2024 15:16:04 +0400 Subject: [PATCH] Add dark mode support --- .../main/java/devtoolsfx/gui/Preferences.java | 17 ++++++++++++ .../main/java/devtoolsfx/gui/ToolPane.java | 22 +++++++++++++-- .../java/devtoolsfx/gui/controls/Dialog.java | 23 +++++++++++----- .../devtoolsfx/gui/env/EnvironmentTab.java | 10 ++++++- .../devtoolsfx/gui/eventlog/EventLogTab.java | 9 +++++-- .../gui/preferences/PreferencesTab.java | 12 +++++++++ .../devtoolsfx/gui/style/StylesheetTab.java | 8 +++++- gui/src/main/resources/index.css | 27 ++++++++++++++----- 8 files changed, 110 insertions(+), 18 deletions(-) diff --git a/gui/src/main/java/devtoolsfx/gui/Preferences.java b/gui/src/main/java/devtoolsfx/gui/Preferences.java index f3ea1fa..5093291 100644 --- a/gui/src/main/java/devtoolsfx/gui/Preferences.java +++ b/gui/src/main/java/devtoolsfx/gui/Preferences.java @@ -36,6 +36,7 @@ public class Preferences { protected final BooleanProperty ignoreMouseTransparent = new SimpleBooleanProperty(false); protected final BooleanProperty enableEventLog = new SimpleBooleanProperty(false); // non-UI protected final IntegerProperty maxEventLogSize = new SimpleIntegerProperty(DEFAULT_EVENT_LOG_SIZE); + protected final BooleanProperty darkMode = new SimpleBooleanProperty(false); protected final HostServices hostServices; @@ -206,6 +207,21 @@ public class Preferences { maxEventLogSize.set(size); } + /** + * Activates or deactivates dark mode for the dev tools UI. + */ + public BooleanProperty darkModeProperty() { + return darkMode; + } + + public boolean getDarkMode() { + return darkMode.get(); + } + + public void setDarkMode(boolean darkMode) { + this.darkMode.set(darkMode); + } + @Override public String toString() { return "Preferences{" + @@ -219,6 +235,7 @@ public class Preferences { ", ignoreMouseTransparent=" + ignoreMouseTransparent + ", enableEventLog=" + enableEventLog + ", maxEventLogSize=" + maxEventLogSize + + ", darkMode=" + darkMode + ", hostServices=" + hostServices + '}'; } diff --git a/gui/src/main/java/devtoolsfx/gui/ToolPane.java b/gui/src/main/java/devtoolsfx/gui/ToolPane.java index cc6ab92..7a302db 100644 --- a/gui/src/main/java/devtoolsfx/gui/ToolPane.java +++ b/gui/src/main/java/devtoolsfx/gui/ToolPane.java @@ -5,6 +5,7 @@ import devtoolsfx.connector.ConnectorOptions; import devtoolsfx.connector.Env; import devtoolsfx.connector.HighlightOptions; import devtoolsfx.event.*; +import devtoolsfx.gui.controls.Dialog; import devtoolsfx.gui.controls.TabLine; import devtoolsfx.gui.env.EnvironmentTab; import devtoolsfx.gui.eventlog.EventLogTab; @@ -24,6 +25,7 @@ import javafx.scene.control.Button; import javafx.scene.control.ContentDisplay; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; +import javafx.stage.Window; import javafx.util.Duration; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -42,6 +44,7 @@ public final class ToolPane extends BorderPane { private static final Logger LOGGER = System.getLogger(ToolPane.class.getName()); private static final PseudoClass ACTIVE = PseudoClass.getPseudoClass("active"); + private static final PseudoClass DARK_MODE = PseudoClass.getPseudoClass("dark"); // we can't use close() because we are not in FXThread private final Connector connector; @@ -91,7 +94,8 @@ public final class ToolPane extends BorderPane { createLayout(); initListeners(); - getStylesheets().add(GUI.USER_AGENT_STYLESHEET); + toggleDarkMode(preferences.getDarkMode()); + tabLine.selectTab(InspectorTab.TAB_NAME); startListenToEvents(false); } @@ -247,7 +251,12 @@ public final class ToolPane extends BorderPane { LOGGER.log(Level.WARNING, Formatters.exceptionToString(e)); } - /////////////////////////////////////////////////////////////////////////// + @Override + public String getUserAgentStylesheet() { + return GUI.USER_AGENT_STYLESHEET; + } + +/////////////////////////////////////////////////////////////////////////// // UI construction // /////////////////////////////////////////////////////////////////////////// @@ -283,6 +292,8 @@ public final class ToolPane extends BorderPane { preferences.showBoundsInParentProperty().subscribe(refreshSelectionHandler); preferences.showBaselineProperty().subscribe(refreshSelectionHandler); + preferences.darkModeProperty().addListener((obs, old, val) -> toggleDarkMode(val)); + tabLine.setOnTabSelect(tab -> { switch (tab) { case InspectorTab.TAB_NAME -> inspectorTab.toFront(); @@ -377,4 +388,11 @@ public final class ToolPane extends BorderPane { } } } + + private void toggleDarkMode(boolean darkMode) { + pseudoClassStateChanged(DARK_MODE, darkMode); + Window.getWindows().stream() + .filter(w -> w instanceof Dialog) + .forEach(dialog -> ((Dialog) dialog).toggleDarkMode(darkMode)); + } } diff --git a/gui/src/main/java/devtoolsfx/gui/controls/Dialog.java b/gui/src/main/java/devtoolsfx/gui/controls/Dialog.java index 22e845d..e8cf872 100644 --- a/gui/src/main/java/devtoolsfx/gui/controls/Dialog.java +++ b/gui/src/main/java/devtoolsfx/gui/controls/Dialog.java @@ -1,6 +1,7 @@ package devtoolsfx.gui.controls; import devtoolsfx.gui.GUI; +import javafx.css.PseudoClass; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Modality; @@ -15,32 +16,42 @@ import java.util.Objects; @NullMarked public final class Dialog

extends Stage { + private static final PseudoClass DARK_MODE = PseudoClass.getPseudoClass("dark"); private static final double DEFAULT_WIDTH = 640; private static final double DEFAULT_HEIGHT = 480; private final P root; - public Dialog(P root) { - this(root, "", DEFAULT_WIDTH, DEFAULT_HEIGHT); + public Dialog(P root, boolean darkMode) { + this(root, "", DEFAULT_WIDTH, DEFAULT_HEIGHT, darkMode); } - public Dialog(P root, String title, double width, double height) { + public Dialog(P root, + String title, + double width, + double height, + boolean darkMode) { super(); this.root = Objects.requireNonNull(root, "parent node cannot be null"); - createLayout(root, title, width, height); + createLayout(root, title, width, height, darkMode); } public P getRoot() { return root; } - private void createLayout(P parent, String title, double width, double height) { + public void toggleDarkMode(boolean darkMode) { + getRoot().pseudoClassStateChanged(DARK_MODE, darkMode); + } + + private void createLayout(P parent, String title, double width, double height, boolean darkMode) { setTitle(title); initModality(Modality.NONE); var scene = new Scene(parent); - scene.getStylesheets().add(GUI.USER_AGENT_STYLESHEET); + scene.setUserAgentStylesheet(GUI.USER_AGENT_STYLESHEET); + toggleDarkMode(darkMode); setWidth(width); setHeight(height); diff --git a/gui/src/main/java/devtoolsfx/gui/env/EnvironmentTab.java b/gui/src/main/java/devtoolsfx/gui/env/EnvironmentTab.java index 75f4c88..e3fe38c 100644 --- a/gui/src/main/java/devtoolsfx/gui/env/EnvironmentTab.java +++ b/gui/src/main/java/devtoolsfx/gui/env/EnvironmentTab.java @@ -52,11 +52,13 @@ public class EnvironmentTab extends VBox { ); private @Nullable Dialog textViewDialog = null; + private final ToolPane toolPane; private final Env env; public EnvironmentTab(ToolPane toolPane) { super(); + this.toolPane = toolPane; this.env = toolPane.getConnector().getEnv(); createLayout(); @@ -229,7 +231,13 @@ public class EnvironmentTab extends VBox { private Dialog getOrCreateTextViewDialog() { if (textViewDialog == null) { - textViewDialog = new Dialog<>(new TextView(), "Details", 640, 480); + textViewDialog = new Dialog<>( + new TextView(), + "Details", + 640, + 480, + toolPane.getPreferences().getDarkMode() + ); } return textViewDialog; diff --git a/gui/src/main/java/devtoolsfx/gui/eventlog/EventLogTab.java b/gui/src/main/java/devtoolsfx/gui/eventlog/EventLogTab.java index d4d9b8a..f9c861e 100644 --- a/gui/src/main/java/devtoolsfx/gui/eventlog/EventLogTab.java +++ b/gui/src/main/java/devtoolsfx/gui/eventlog/EventLogTab.java @@ -8,7 +8,6 @@ import devtoolsfx.gui.controls.TextView; import devtoolsfx.gui.util.Formatters; import devtoolsfx.gui.util.GUIHelpers; import javafx.css.PseudoClass; - import javafx.scene.control.*; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; @@ -231,7 +230,13 @@ public final class EventLogTab extends VBox { private Dialog getOrCreateTextViewDialog() { if (textViewDialog == null) { - textViewDialog = new Dialog<>(new TextView(), "Log Entry", 640, 480); + textViewDialog = new Dialog<>( + new TextView(), + "Log Entry", + 640, + 480, + toolPane.getPreferences().getDarkMode() + ); } return textViewDialog; diff --git a/gui/src/main/java/devtoolsfx/gui/preferences/PreferencesTab.java b/gui/src/main/java/devtoolsfx/gui/preferences/PreferencesTab.java index c4c18c9..5ff1ddc 100644 --- a/gui/src/main/java/devtoolsfx/gui/preferences/PreferencesTab.java +++ b/gui/src/main/java/devtoolsfx/gui/preferences/PreferencesTab.java @@ -33,6 +33,7 @@ public class PreferencesTab extends VBox { private void createLayout() { getChildren().setAll( createSceneGraphGroup(), + createAppearanceGroup(), createInspectionGroup(), createEventLogGroup() ); @@ -73,6 +74,17 @@ public class PreferencesTab extends VBox { return createPreferencesGroup("Scene Graph", content); } + private VBox createAppearanceGroup() { + var darkModeToggle = new CheckBox("Dark Mode"); + darkModeToggle.selectedProperty().bindBidirectional( + toolPane.getPreferences().darkModeProperty() + ); + + var content = new FlowPane(darkModeToggle); + + return createPreferencesGroup("Appearance", content); + } + private VBox createInspectionGroup() { var layoutBoundsToggle = new CheckBox("Highlight layout bounds"); layoutBoundsToggle.selectedProperty().bindBidirectional( diff --git a/gui/src/main/java/devtoolsfx/gui/style/StylesheetTab.java b/gui/src/main/java/devtoolsfx/gui/style/StylesheetTab.java index fcf6355..0b42f77 100644 --- a/gui/src/main/java/devtoolsfx/gui/style/StylesheetTab.java +++ b/gui/src/main/java/devtoolsfx/gui/style/StylesheetTab.java @@ -153,7 +153,13 @@ public final class StylesheetTab extends VBox { private Dialog getOrCreateTextViewDialog() { if (textViewDialog == null) { - textViewDialog = new Dialog<>(new TextView(), "Source Code", 640, 480); + textViewDialog = new Dialog<>( + new TextView(), + "Source Code", + 640, + 480, + toolPane.getPreferences().getDarkMode() + ); } return textViewDialog; diff --git a/gui/src/main/resources/index.css b/gui/src/main/resources/index.css index 4232f45..85cbb1e 100644 --- a/gui/src/main/resources/index.css +++ b/gui/src/main/resources/index.css @@ -10,7 +10,7 @@ -palette-color-accent: #0969da; -palette-color-danger: #cf222e; -palette-color-success: #1a7f37; - -palette-color-warning: #fff8c5; + -palette-text-highlight: #fff8c5; /* base */ -fx-background-color: -palette-color-bg; @@ -38,7 +38,7 @@ -color-input-border: -palette-color-border; -color-input-bg-focused: -palette-color-bg; -color-input-border-focused: -palette-color-accent; - -color-input-bg-readonly: -palette-color-bg-hover; + -color-input-bg-readonly: -palette-color-bg; -color-input-bg-highlight: -palette-color-neutral; -color-input-fg-highlight: -palette-color-fg; @@ -56,6 +56,20 @@ -color-button-border-pressed: -palette-color-border; } +.root:dark { + -palette-color-bg: #202124; + -palette-color-bg-hover: #36373a; + -palette-color-fg: #c9d1d9; + -palette-color-fg-muted: #6e7681; + -palette-color-border: #42474e; + -palette-color-neutral: #3d3d3d; + -palette-color-neutral-hover: #313131; + -palette-color-accent: #58a6ff; + -palette-color-danger: #f85149; + -palette-color-success: #1a7f37; + -palette-text-highlight: #bb800940; +} + /****************************************************************************** * JavaFX Controls / Theme ******************************************************************************/ @@ -608,7 +622,7 @@ Text { -fx-padding: 0; } #scene-graph-tree .tree-cell:filtered .label { - -fx-background-color: -palette-color-warning; + -fx-background-color: -palette-text-highlight; } #scene-graph-search-field { @@ -816,6 +830,9 @@ Text { -fx-max-height: 0.9em; -fx-background-color: -palette-color-fg; } +#stylesheet-tab .tree-table-row-cell > .tree-table-cell { + -fx-alignment: BASELINE_LEFT; +} /****************************************************************************** * Environment @@ -912,11 +929,9 @@ Text { .text-view { -fx-padding: 0; } + .text-view .text-area { -fx-font-family: monospaced; - -fx-background-insets: 0; - -fx-background-radius: 0; - -fx-border: 0; } .hyperlink:empty {