Add dark mode support

This commit is contained in:
mkpaz 2024-10-14 15:16:04 +04:00
parent e653e8fda1
commit feed204b88
8 changed files with 110 additions and 18 deletions

View File

@ -36,6 +36,7 @@ public class Preferences {
protected final BooleanProperty ignoreMouseTransparent = new SimpleBooleanProperty(false); protected final BooleanProperty ignoreMouseTransparent = new SimpleBooleanProperty(false);
protected final BooleanProperty enableEventLog = new SimpleBooleanProperty(false); // non-UI protected final BooleanProperty enableEventLog = new SimpleBooleanProperty(false); // non-UI
protected final IntegerProperty maxEventLogSize = new SimpleIntegerProperty(DEFAULT_EVENT_LOG_SIZE); protected final IntegerProperty maxEventLogSize = new SimpleIntegerProperty(DEFAULT_EVENT_LOG_SIZE);
protected final BooleanProperty darkMode = new SimpleBooleanProperty(false);
protected final HostServices hostServices; protected final HostServices hostServices;
@ -206,6 +207,21 @@ public class Preferences {
maxEventLogSize.set(size); 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 @Override
public String toString() { public String toString() {
return "Preferences{" + return "Preferences{" +
@ -219,6 +235,7 @@ public class Preferences {
", ignoreMouseTransparent=" + ignoreMouseTransparent + ", ignoreMouseTransparent=" + ignoreMouseTransparent +
", enableEventLog=" + enableEventLog + ", enableEventLog=" + enableEventLog +
", maxEventLogSize=" + maxEventLogSize + ", maxEventLogSize=" + maxEventLogSize +
", darkMode=" + darkMode +
", hostServices=" + hostServices + ", hostServices=" + hostServices +
'}'; '}';
} }

View File

@ -5,6 +5,7 @@ import devtoolsfx.connector.ConnectorOptions;
import devtoolsfx.connector.Env; import devtoolsfx.connector.Env;
import devtoolsfx.connector.HighlightOptions; import devtoolsfx.connector.HighlightOptions;
import devtoolsfx.event.*; import devtoolsfx.event.*;
import devtoolsfx.gui.controls.Dialog;
import devtoolsfx.gui.controls.TabLine; import devtoolsfx.gui.controls.TabLine;
import devtoolsfx.gui.env.EnvironmentTab; import devtoolsfx.gui.env.EnvironmentTab;
import devtoolsfx.gui.eventlog.EventLogTab; import devtoolsfx.gui.eventlog.EventLogTab;
@ -24,6 +25,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay; import javafx.scene.control.ContentDisplay;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.stage.Window;
import javafx.util.Duration; import javafx.util.Duration;
import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable; 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 Logger LOGGER = System.getLogger(ToolPane.class.getName());
private static final PseudoClass ACTIVE = PseudoClass.getPseudoClass("active"); 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 // we can't use close() because we are not in FXThread
private final Connector connector; private final Connector connector;
@ -91,7 +94,8 @@ public final class ToolPane extends BorderPane {
createLayout(); createLayout();
initListeners(); initListeners();
getStylesheets().add(GUI.USER_AGENT_STYLESHEET); toggleDarkMode(preferences.getDarkMode());
tabLine.selectTab(InspectorTab.TAB_NAME); tabLine.selectTab(InspectorTab.TAB_NAME);
startListenToEvents(false); startListenToEvents(false);
} }
@ -247,6 +251,11 @@ public final class ToolPane extends BorderPane {
LOGGER.log(Level.WARNING, Formatters.exceptionToString(e)); LOGGER.log(Level.WARNING, Formatters.exceptionToString(e));
} }
@Override
public String getUserAgentStylesheet() {
return GUI.USER_AGENT_STYLESHEET;
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// UI construction // // UI construction //
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -283,6 +292,8 @@ public final class ToolPane extends BorderPane {
preferences.showBoundsInParentProperty().subscribe(refreshSelectionHandler); preferences.showBoundsInParentProperty().subscribe(refreshSelectionHandler);
preferences.showBaselineProperty().subscribe(refreshSelectionHandler); preferences.showBaselineProperty().subscribe(refreshSelectionHandler);
preferences.darkModeProperty().addListener((obs, old, val) -> toggleDarkMode(val));
tabLine.setOnTabSelect(tab -> { tabLine.setOnTabSelect(tab -> {
switch (tab) { switch (tab) {
case InspectorTab.TAB_NAME -> inspectorTab.toFront(); 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));
}
} }

View File

@ -1,6 +1,7 @@
package devtoolsfx.gui.controls; package devtoolsfx.gui.controls;
import devtoolsfx.gui.GUI; import devtoolsfx.gui.GUI;
import javafx.css.PseudoClass;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Modality; import javafx.stage.Modality;
@ -15,32 +16,42 @@ import java.util.Objects;
@NullMarked @NullMarked
public final class Dialog<P extends Parent> extends Stage { public final class Dialog<P extends Parent> extends Stage {
private static final PseudoClass DARK_MODE = PseudoClass.getPseudoClass("dark");
private static final double DEFAULT_WIDTH = 640; private static final double DEFAULT_WIDTH = 640;
private static final double DEFAULT_HEIGHT = 480; private static final double DEFAULT_HEIGHT = 480;
private final P root; private final P root;
public Dialog(P root) { public Dialog(P root, boolean darkMode) {
this(root, "", DEFAULT_WIDTH, DEFAULT_HEIGHT); 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(); super();
this.root = Objects.requireNonNull(root, "parent node cannot be null"); this.root = Objects.requireNonNull(root, "parent node cannot be null");
createLayout(root, title, width, height); createLayout(root, title, width, height, darkMode);
} }
public P getRoot() { public P getRoot() {
return root; 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); setTitle(title);
initModality(Modality.NONE); initModality(Modality.NONE);
var scene = new Scene(parent); var scene = new Scene(parent);
scene.getStylesheets().add(GUI.USER_AGENT_STYLESHEET); scene.setUserAgentStylesheet(GUI.USER_AGENT_STYLESHEET);
toggleDarkMode(darkMode);
setWidth(width); setWidth(width);
setHeight(height); setHeight(height);

View File

@ -52,11 +52,13 @@ public class EnvironmentTab extends VBox {
); );
private @Nullable Dialog<TextView> textViewDialog = null; private @Nullable Dialog<TextView> textViewDialog = null;
private final ToolPane toolPane;
private final Env env; private final Env env;
public EnvironmentTab(ToolPane toolPane) { public EnvironmentTab(ToolPane toolPane) {
super(); super();
this.toolPane = toolPane;
this.env = toolPane.getConnector().getEnv(); this.env = toolPane.getConnector().getEnv();
createLayout(); createLayout();
@ -229,7 +231,13 @@ public class EnvironmentTab extends VBox {
private Dialog<TextView> getOrCreateTextViewDialog() { private Dialog<TextView> getOrCreateTextViewDialog() {
if (textViewDialog == null) { if (textViewDialog == null) {
textViewDialog = new Dialog<>(new TextView(), "Details", 640, 480); textViewDialog = new Dialog<>(
new TextView(),
"Details",
640,
480,
toolPane.getPreferences().getDarkMode()
);
} }
return textViewDialog; return textViewDialog;

View File

@ -8,7 +8,6 @@ import devtoolsfx.gui.controls.TextView;
import devtoolsfx.gui.util.Formatters; import devtoolsfx.gui.util.Formatters;
import devtoolsfx.gui.util.GUIHelpers; import devtoolsfx.gui.util.GUIHelpers;
import javafx.css.PseudoClass; import javafx.css.PseudoClass;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCodeCombination;
@ -231,7 +230,13 @@ public final class EventLogTab extends VBox {
private Dialog<TextView> getOrCreateTextViewDialog() { private Dialog<TextView> getOrCreateTextViewDialog() {
if (textViewDialog == null) { 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; return textViewDialog;

View File

@ -33,6 +33,7 @@ public class PreferencesTab extends VBox {
private void createLayout() { private void createLayout() {
getChildren().setAll( getChildren().setAll(
createSceneGraphGroup(), createSceneGraphGroup(),
createAppearanceGroup(),
createInspectionGroup(), createInspectionGroup(),
createEventLogGroup() createEventLogGroup()
); );
@ -73,6 +74,17 @@ public class PreferencesTab extends VBox {
return createPreferencesGroup("Scene Graph", content); 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() { private VBox createInspectionGroup() {
var layoutBoundsToggle = new CheckBox("Highlight layout bounds"); var layoutBoundsToggle = new CheckBox("Highlight layout bounds");
layoutBoundsToggle.selectedProperty().bindBidirectional( layoutBoundsToggle.selectedProperty().bindBidirectional(

View File

@ -153,7 +153,13 @@ public final class StylesheetTab extends VBox {
private Dialog<TextView> getOrCreateTextViewDialog() { private Dialog<TextView> getOrCreateTextViewDialog() {
if (textViewDialog == null) { 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; return textViewDialog;

View File

@ -10,7 +10,7 @@
-palette-color-accent: #0969da; -palette-color-accent: #0969da;
-palette-color-danger: #cf222e; -palette-color-danger: #cf222e;
-palette-color-success: #1a7f37; -palette-color-success: #1a7f37;
-palette-color-warning: #fff8c5; -palette-text-highlight: #fff8c5;
/* base */ /* base */
-fx-background-color: -palette-color-bg; -fx-background-color: -palette-color-bg;
@ -38,7 +38,7 @@
-color-input-border: -palette-color-border; -color-input-border: -palette-color-border;
-color-input-bg-focused: -palette-color-bg; -color-input-bg-focused: -palette-color-bg;
-color-input-border-focused: -palette-color-accent; -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-bg-highlight: -palette-color-neutral;
-color-input-fg-highlight: -palette-color-fg; -color-input-fg-highlight: -palette-color-fg;
@ -56,6 +56,20 @@
-color-button-border-pressed: -palette-color-border; -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 * JavaFX Controls / Theme
******************************************************************************/ ******************************************************************************/
@ -608,7 +622,7 @@ Text {
-fx-padding: 0; -fx-padding: 0;
} }
#scene-graph-tree .tree-cell:filtered .label { #scene-graph-tree .tree-cell:filtered .label {
-fx-background-color: -palette-color-warning; -fx-background-color: -palette-text-highlight;
} }
#scene-graph-search-field { #scene-graph-search-field {
@ -816,6 +830,9 @@ Text {
-fx-max-height: 0.9em; -fx-max-height: 0.9em;
-fx-background-color: -palette-color-fg; -fx-background-color: -palette-color-fg;
} }
#stylesheet-tab .tree-table-row-cell > .tree-table-cell {
-fx-alignment: BASELINE_LEFT;
}
/****************************************************************************** /******************************************************************************
* Environment * Environment
@ -912,11 +929,9 @@ Text {
.text-view { .text-view {
-fx-padding: 0; -fx-padding: 0;
} }
.text-view .text-area { .text-view .text-area {
-fx-font-family: monospaced; -fx-font-family: monospaced;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-border: 0;
} }
.hyperlink:empty { .hyperlink:empty {