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 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 +
'}';
}

View File

@ -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,6 +251,11 @@ 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));
}
}

View File

@ -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<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_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);

View File

@ -52,11 +52,13 @@ public class EnvironmentTab extends VBox {
);
private @Nullable Dialog<TextView> 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<TextView> 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;

View File

@ -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<TextView> 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;

View File

@ -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(

View File

@ -153,7 +153,13 @@ public final class StylesheetTab extends VBox {
private Dialog<TextView> 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;

View File

@ -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 {