diff --git a/app/build.gradle b/app/build.gradle index b6ceb5f34..6493dae33 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,7 @@ apply from: "$rootDir/gradle/gradle_scripts/lombok.gradle" apply from: "$projectDir/gradle_scripts/github-api.gradle" apply from: "$projectDir/gradle_scripts/flexmark.gradle" apply from: "$rootDir/gradle/gradle_scripts/picocli.gradle" +apply from: "$rootDir/gradle/gradle_scripts/versioncompare.gradle" configurations { implementation.extendsFrom(dep) @@ -38,8 +39,8 @@ dependencies { compileOnly 'org.junit.jupiter:junit-jupiter-api:5.9.0' compileOnly 'org.junit.jupiter:junit-jupiter-params:5.9.0' - implementation 'net.java.dev.jna:jna-jpms:5.12.1' - implementation 'net.java.dev.jna:jna-platform-jpms:5.12.1' + implementation 'net.java.dev.jna:jna-jpms:5.13.0' + implementation 'net.java.dev.jna:jna-platform-jpms:5.13.0' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0" implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.13.0" implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.13.0" @@ -56,7 +57,14 @@ dependencies { implementation 'com.jfoenix:jfoenix:9.0.10' implementation 'org.controlsfx:controlsfx:11.1.1' implementation 'net.synedra:validatorfx:0.3.1' - implementation 'io.github.mkpaz:atlantafx-base:1.2.0' + implementation name: 'atlantafx-base-1.2.1' + implementation name: 'atlantafx-styles-1.2.1' + implementation name: 'jSystemThemeDetector-3.8' + implementation group: 'com.github.oshi', name: 'oshi-core-java11', version: '6.4.2' + implementation 'org.jetbrains:annotations:24.0.1' + implementation ('de.jangassen:jfa:1.2.0') { + exclude group: 'net.java.dev.jna', module: 'jna' + } } apply from: "$rootDir/gradle/gradle_scripts/junit.gradle" diff --git a/app/src/main/java/io/xpipe/app/core/AppStyle.java b/app/src/main/java/io/xpipe/app/core/AppStyle.java index 10eb8e2f2..f7cef72f1 100644 --- a/app/src/main/java/io/xpipe/app/core/AppStyle.java +++ b/app/src/main/java/io/xpipe/app/core/AppStyle.java @@ -1,17 +1,9 @@ package io.xpipe.app.core; -import atlantafx.base.theme.NordDark; -import atlantafx.base.theme.NordLight; -import atlantafx.base.theme.PrimerDark; -import atlantafx.base.theme.PrimerLight; -import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.prefs.AppPrefs; -import javafx.application.Application; import javafx.scene.Scene; -import lombok.AllArgsConstructor; -import lombok.Getter; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -36,9 +28,6 @@ public class AppStyle { loadStylesheets(); if (AppPrefs.get() != null) { - AppPrefs.get().theme.addListener((c, o, n) -> { - changeTheme(o, n); - }); AppPrefs.get().useSystemFont.addListener((c, o, n) -> { changeFontUsage(n); }); @@ -78,12 +67,6 @@ public class AppStyle { } } - private static void changeTheme(Theme oldTheme, Theme newTheme) { - scenes.forEach(scene -> { - Application.setUserAgentStylesheet(newTheme.getTheme().getUserAgentStylesheet()); - }); - } - private static void changeFontUsage(boolean use) { if (!use) { scenes.forEach(scene -> { @@ -106,10 +89,6 @@ public class AppStyle { } public static void addStylesheets(Scene scene) { - var t = AppPrefs.get() != null ? AppPrefs.get().theme.getValue() : Theme.LIGHT; - Application.setUserAgentStylesheet(t.getTheme().getUserAgentStylesheet()); - TrackEvent.debug("Set theme " + t.getId() + " for scene"); - if (AppPrefs.get() != null && !AppPrefs.get().useSystemFont.get()) { scene.getStylesheets().add(FONT_CONTENTS); } @@ -122,21 +101,4 @@ public class AppStyle { scenes.add(scene); } - @AllArgsConstructor - @Getter - public enum Theme implements PrefsChoiceValue { - LIGHT("light", new PrimerLight()), - DARK("dark", new PrimerDark()), - NORD_LIGHT("nordLight", new NordLight()), - NORD_DARK("nordDark", new NordDark()); - // DARK("dark"); - - private final String id; - private final atlantafx.base.theme.Theme theme; - - @Override - public String toTranslatedString() { - return theme.getName(); - } - } } diff --git a/app/src/main/java/io/xpipe/app/core/AppTheme.java b/app/src/main/java/io/xpipe/app/core/AppTheme.java new file mode 100644 index 000000000..17c60776e --- /dev/null +++ b/app/src/main/java/io/xpipe/app/core/AppTheme.java @@ -0,0 +1,103 @@ +package io.xpipe.app.core; + +import atlantafx.base.theme.*; +import com.jthemedetecor.OsThemeDetector; +import io.xpipe.app.ext.PrefsChoiceValue; +import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.issue.TrackEvent; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.core.process.OsType; +import javafx.application.Application; +import lombok.AllArgsConstructor; +import lombok.Getter; + +public class AppTheme { + + public static void init() { + if (AppPrefs.get() == null) { + return; + } + + OsThemeDetector detector = OsThemeDetector.getDetector(); + if (AppPrefs.get().theme.getValue() == null) { + try { + setDefault(detector.isDark()); + } catch (Throwable ex) { + ErrorEvent.fromThrowable(ex).omit().handle(); + setDefault(false); + } + } + var t = AppPrefs.get().theme.getValue(); + + Application.setUserAgentStylesheet(t.getTheme().getUserAgentStylesheet()); + TrackEvent.debug("Set theme " + t.getId() + " for scene"); + + detector.registerListener(dark -> { + PlatformThread.runLaterIfNeeded(() -> { + if (dark && !AppPrefs.get().theme.getValue().getTheme().isDarkMode()) { + AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme()); + } + + if (!dark && AppPrefs.get().theme.getValue().getTheme().isDarkMode()) { + AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme()); + } + }); + }); + + AppPrefs.get().theme.addListener((c, o, n) -> { + changeTheme(n); + }); + } + + private static void setDefault(boolean dark) { + if (dark) { + AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme()); + } else { + AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme()); + } + } + + private static void changeTheme(Theme newTheme) { + PlatformThread.runLaterIfNeeded(() -> { + Application.setUserAgentStylesheet(newTheme.getTheme().getUserAgentStylesheet()); + TrackEvent.debug("Set theme " + newTheme.getId() + " for scene"); + }); + } + + @AllArgsConstructor + @Getter + public enum Theme implements PrefsChoiceValue { + PRIMER_LIGHT("light", new PrimerLight()), + PRIMER_DARK("dark", new PrimerDark()), + NORD_LIGHT("nordLight", new NordLight()), + NORD_DARK("nordDark", new NordDark()), + CUPERTINO_LIGHT("cupertinoLight", new CupertinoLight()), + CUPERTINO_DARK("cupertinoDark", new CupertinoDark()), + DRACULA("dracula", new Dracula()); + + static Theme getDefaultLightTheme() { + return switch (OsType.getLocal()) { + case OsType.Windows windows -> PRIMER_LIGHT; + case OsType.Linux linux -> NORD_LIGHT; + case OsType.MacOs macOs -> CUPERTINO_LIGHT; + }; + } + + static Theme getDefaultDarkTheme() { + return switch (OsType.getLocal()) { + case OsType.Windows windows -> PRIMER_DARK; + case OsType.Linux linux -> NORD_DARK; + case OsType.MacOs macOs -> CUPERTINO_DARK; + }; + } + + private final String id; + private final atlantafx.base.theme.Theme theme; + + @Override + public String toTranslatedString() { + return theme.getName(); + } + } +} diff --git a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java index 1b1147075..63ceeddcd 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/PlatformMode.java @@ -60,6 +60,7 @@ public abstract class PlatformMode extends OperationMode { TrackEvent.info("mode", "Platform mode initial setup"); AppI18n.init(); AppFont.loadFonts(); + AppTheme.init(); AppStyle.init(); AppImages.init(); TrackEvent.info("mode", "Finished essential component initialization before platform"); diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 07973cc3b..f7345b5e4 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -11,7 +11,7 @@ import com.dlsc.preferencesfx.util.VisibilityProperty; import io.xpipe.app.comp.base.ButtonComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppProperties; -import io.xpipe.app.core.AppStyle; +import io.xpipe.app.core.AppTheme; import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.ext.PrefsHandler; import io.xpipe.app.ext.PrefsProvider; @@ -68,8 +68,8 @@ public class AppPrefs { private static AppPrefs INSTANCE; private final SimpleListProperty languageList = new SimpleListProperty<>(FXCollections.observableArrayList(Arrays.asList(SupportedLocale.values()))); - private final SimpleListProperty themeList = - new SimpleListProperty<>(FXCollections.observableArrayList(Arrays.asList(AppStyle.Theme.values()))); + private final SimpleListProperty themeList = + new SimpleListProperty<>(FXCollections.observableArrayList(Arrays.asList(AppTheme.Theme.values()))); private final SimpleListProperty closeBehaviourList = new SimpleListProperty<>( FXCollections.observableArrayList(PrefsChoiceValue.getSupported(CloseBehaviour.class))); private final SimpleListProperty externalEditorList = new SimpleListProperty<>( @@ -90,11 +90,10 @@ public class AppPrefs { languageList, languageInternal) .render(() -> new TranslatableComboBoxControl<>()); - private final ObjectProperty themeInternal = - typed(new SimpleObjectProperty<>(AppStyle.Theme.LIGHT), AppStyle.Theme.class); - public final ReadOnlyProperty theme = themeInternal; - private final SingleSelectionField themeControl = - Field.ofSingleSelectionType(themeList, themeInternal).render(() -> new TranslatableComboBoxControl<>()); + public final ObjectProperty theme = + typed(new SimpleObjectProperty<>(), AppTheme.Theme.class); + private final SingleSelectionField themeControl = + Field.ofSingleSelectionType(themeList, theme).render(() -> new TranslatableComboBoxControl<>()); private final BooleanProperty useSystemFontInternal = typed(new SimpleBooleanProperty(true), Boolean.class); public final ReadOnlyBooleanProperty useSystemFont = useSystemFontInternal; private final IntegerProperty tooltipDelayInternal = typed(new SimpleIntegerProperty(1000), Integer.class); @@ -512,7 +511,7 @@ public class AppPrefs { Group.of( "uiOptions", Setting.of("language", languageControl, languageInternal), - Setting.of("theme", themeControl, themeInternal), + Setting.of("theme", themeControl, theme), Setting.of("useSystemFont", useSystemFontInternal), Setting.of("tooltipDelay", tooltipDelayInternal, tooltipDelayMin, tooltipDelayMax)), Group.of("windowOptions", Setting.of("saveWindowLocation", saveWindowLocationInternal))), diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index e8a5481c0..69dd3b9ca 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -83,6 +83,8 @@ open module io.xpipe.app { requires java.management; requires jdk.management; requires jdk.management.agent; + requires com.jthemedetector; + requires versioncompare; // Required by extensions requires commons.math3; diff --git a/gradle/gradle_scripts/atlantafx-base-1.2.1.jar b/gradle/gradle_scripts/atlantafx-base-1.2.1.jar new file mode 100644 index 000000000..91e1b140c Binary files /dev/null and b/gradle/gradle_scripts/atlantafx-base-1.2.1.jar differ diff --git a/gradle/gradle_scripts/atlantafx-styles-1.2.1.jar b/gradle/gradle_scripts/atlantafx-styles-1.2.1.jar new file mode 100644 index 000000000..3b47eea28 Binary files /dev/null and b/gradle/gradle_scripts/atlantafx-styles-1.2.1.jar differ diff --git a/gradle/gradle_scripts/jSystemThemeDetector-3.8.jar b/gradle/gradle_scripts/jSystemThemeDetector-3.8.jar new file mode 100644 index 000000000..a8ac11f54 Binary files /dev/null and b/gradle/gradle_scripts/jSystemThemeDetector-3.8.jar differ diff --git a/gradle/gradle_scripts/versioncompare.gradle b/gradle/gradle_scripts/versioncompare.gradle new file mode 100644 index 000000000..b8a249e73 --- /dev/null +++ b/gradle/gradle_scripts/versioncompare.gradle @@ -0,0 +1,19 @@ +dependencies { + implementation files("$buildDir/generated-modules/versioncompare-1.5.0.jar") +} + +addDependenciesModuleInfo { + overwriteExistingFiles = true + jdepsExtraArgs = ['-q'] + outputDirectory = file("$buildDir/generated-modules") + modules { + module { + artifact "io.github.g00fy2:versioncompare:1.5.0" + moduleInfoSource = ''' + module versioncompare { + exports io.github.g00fy2.versioncompare; + } + ''' + } + } +}