Add dark mode support
This commit is contained in:
parent
e653e8fda1
commit
feed204b88
@ -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 +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
@ -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,7 +251,12 @@ 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user