diff --git a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java index 246559e7..99193fc0 100644 --- a/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java +++ b/client/src/main/java/ctbrec/ui/settings/CtbrecPreferencesStorage.java @@ -48,12 +48,17 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { private Config config; private Settings settings; + private Preferences prefs; public CtbrecPreferencesStorage(Config config) { this.config = config; this.settings = config.getSettings(); } + public void setPreferences(Preferences prefs) { + this.prefs = prefs; + } + @Override public void save(Preferences preferences) throws IOException { throw new RuntimeException("not implemented"); @@ -103,6 +108,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { prop.addListener((obs, oldV, newV) -> saveValue(() -> { Field field = Settings.class.getField(setting.getKey()); field.set(settings, newV); + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + prefs.getRestartRequiredCallback().run(); + } config.save(); })); HBox row = new HBox(); @@ -143,7 +151,7 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { private int getRangeSliderValue(List values, List labels, int value) { for (int i = 0; i < labels.size(); i++) { int label = labels.get(i).intValue(); - if(label == value) { + if (label == value) { return values.get(i); } } @@ -158,6 +166,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { String oldValue = (String) field.get(settings); if (!Objects.equals(path, oldValue)) { field.set(settings, path); + if (setting.doesNeedRestart()) { + prefs.getRestartRequiredCallback().run(); + } config.save(); } })); @@ -175,6 +186,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { String oldValue = (String) field.get(settings); if (!Objects.equals(path, oldValue)) { field.set(settings, path); + if (setting.doesNeedRestart()) { + prefs.getRestartRequiredCallback().run(); + } config.save(); } })); @@ -188,6 +202,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { ctrl.textProperty().addListener((obs, oldV, newV) -> saveValue(() -> { Field field = Settings.class.getField(setting.getKey()); field.set(settings, newV); + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + prefs.getRestartRequiredCallback().run(); + } config.save(); })); StringProperty prop = (StringProperty) setting.getProperty(); @@ -205,6 +222,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { if (!ctrl.getText().isEmpty()) { Field field = Settings.class.getField(setting.getKey()); field.set(settings, Integer.parseInt(ctrl.getText())); + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + prefs.getRestartRequiredCallback().run(); + } config.save(); } })); @@ -227,6 +247,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { } Field field = Settings.class.getField(setting.getKey()); field.set(settings, value); + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + prefs.getRestartRequiredCallback().run(); + } config.save(); } })); @@ -240,6 +263,9 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { ctrl.selectedProperty().addListener((obs, oldV, newV) -> saveValue(() -> { Field field = Settings.class.getField(setting.getKey()); field.set(settings, newV); + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + prefs.getRestartRequiredCallback().run(); + } config.save(); })); BooleanProperty prop = (BooleanProperty) setting.getProperty(); @@ -266,9 +292,12 @@ public class CtbrecPreferencesStorage implements PreferencesStorage { } else { field.set(settings, newV); } + if (setting.doesNeedRestart() && !Objects.equals(oldV, newV)) { + prefs.getRestartRequiredCallback().run(); + } config.save(); })); - if(setting.getChangeListener() != null) { + if (setting.getChangeListener() != null) { comboBox.valueProperty().addListener((ChangeListener) setting.getChangeListener()); } return comboBox; diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index 39e30bce..40084cde 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -37,6 +37,9 @@ import ctbrec.ui.settings.api.SimpleRangeProperty; import ctbrec.ui.settings.api.ValueConverter; import ctbrec.ui.sites.ConfigUI; import ctbrec.ui.tabs.TabSelectionListener; +import javafx.animation.FadeTransition; +import javafx.animation.PauseTransition; +import javafx.animation.Transition; import javafx.beans.binding.BooleanExpression; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; @@ -46,13 +49,26 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.Tab; import javafx.scene.control.TextInputDialog; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.Border; +import javafx.scene.layout.BorderStroke; +import javafx.scene.layout.BorderStrokeStyle; +import javafx.scene.layout.BorderWidths; +import javafx.scene.layout.CornerRadii; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.util.Duration; public class SettingsTab extends Tab implements TabSelectionListener { @@ -109,6 +125,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { private ExclusiveSelectionProperty recordLocal; private SimpleIntegerProperty postProcessingThreads; private IgnoreList ignoreList; + private Label restartNotification; public SettingsTab(List sites, Recorder recorder) { this.sites = sites; @@ -171,19 +188,20 @@ public class SettingsTab extends Tab implements TabSelectionListener { .ifPresent(configPanel -> siteCategories.add(Category.of(site.getName(), configPanel))); } - Preferences prefs = Preferences.of(new CtbrecPreferencesStorage(config), + CtbrecPreferencesStorage storage = new CtbrecPreferencesStorage(config); + Preferences prefs = Preferences.of(storage, Category.of("General", Group.of("General", Setting.of("User-Agent", httpUserAgent), Setting.of("User-Agent mobile", httpUserAgentMobile), - Setting.of("Update overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds"), + Setting.of("Update overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds").needsRestart(), Setting.of("Update thumbnails", updateThumbnails, "The overviews will still be updated, but the thumbnails won't be changed. This is useful for less powerful systems."), Setting.of("Display stream resolution in overview", determineResolution), Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"), Setting.of("Enable live previews (experimental)", livePreviews), Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(), Setting.of("Start Tab", startTab), - Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())) + Setting.of("Colors (Base / Accent)", new ColorSettingsPane(Config.getInstance())).needsRestart() ), Group.of("Player", Setting.of("Player", mediaPlayer), @@ -208,7 +226,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { Setting.of("Skip online check for paused models", onlineCheckSkipsPausedModels, "Skip online check for paused models") ), Group.of("Location", - Setting.of("Record Location", recordLocal), + Setting.of("Record Location", recordLocal).needsRestart(), Setting.of("Server", server), Setting.of("Port", port), Setting.of("Path", path, "Leave empty, if you didn't change the servletContext in the server config"), @@ -230,15 +248,17 @@ public class SettingsTab extends Tab implements TabSelectionListener { Category.of("Sites", siteCategories.toArray(new Category[0])), Category.of("Proxy", Group.of("Proxy", - Setting.of("Type", proxyType), - Setting.of("Host", proxyHost), - Setting.of("Port", proxyPort), - Setting.of("Username", proxyUser), - Setting.of("Password", proxyPassword) + Setting.of("Type", proxyType).needsRestart(), + Setting.of("Host", proxyHost).needsRestart(), + Setting.of("Port", proxyPort).needsRestart(), + Setting.of("Username", proxyUser).needsRestart(), + Setting.of("Password", proxyPassword).needsRestart() ) ) ); Region preferencesView = prefs.getView(); + prefs.onRestartRequired(this::showRestartRequired); + storage.setPreferences(prefs); preferencesView.setMinSize(800, 400); preferencesView.setPrefSize(1280, 960); ScrollPane scrollPane = new ScrollPane(preferencesView); @@ -248,7 +268,20 @@ public class SettingsTab extends Tab implements TabSelectionListener { GridPane.setFillHeight(scrollPane, true); GridPane.setHgrow(scrollPane, Priority.ALWAYS); GridPane.setVgrow(scrollPane, Priority.ALWAYS); - setContent(container); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().add(container); + restartNotification = new Label("Restart Required"); + restartNotification.setVisible(false); + restartNotification.setOpacity(0); + restartNotification.setStyle("-fx-font-size: 28; -fx-padding: .3em"); + restartNotification.setBorder(new Border(new BorderStroke(Color.web(settings.colorAccent), BorderStrokeStyle.SOLID, new CornerRadii(5), new BorderWidths(2)))); + restartNotification.setBackground(new Background(new BackgroundFill(Color.web(settings.colorBase), new CornerRadii(5), Insets.EMPTY))); + stackPane.getChildren().add(restartNotification); + StackPane.setAlignment(restartNotification, Pos.TOP_RIGHT); + StackPane.setMargin(restartNotification, new Insets(10, 40, 0, 0)); + + setContent(stackPane); prefs.expandTree(); @@ -407,7 +440,26 @@ public class SettingsTab extends Tab implements TabSelectionListener { } void showRestartRequired() { - // TODO restartLabel.setVisible(true); + if (!restartNotification.isVisible()) { + restartNotification.setVisible(true); + Transition fadeIn = changeOpacity(restartNotification, 1); + fadeIn.play(); + fadeIn.setOnFinished(e -> { + Transition fadeOut = changeOpacity(restartNotification, 0); + fadeOut.setOnFinished(e2 -> restartNotification.setVisible(false)); + PauseTransition pauseTransition = new PauseTransition(Duration.seconds(5)); + pauseTransition.setOnFinished(evt -> fadeOut.play()); + pauseTransition.play(); + }); + } + } + + private static final Duration ANIMATION_DURATION = new Duration(500); + private Transition changeOpacity(Node node, double opacity) { + FadeTransition transition = new FadeTransition(ANIMATION_DURATION, node); + transition.setFromValue(node.getOpacity()); + transition.setToValue(opacity); + return transition; } public static class SplitAfterOption { diff --git a/client/src/main/java/ctbrec/ui/settings/api/Preferences.java b/client/src/main/java/ctbrec/ui/settings/api/Preferences.java index d8c9bb7d..f5a72a66 100644 --- a/client/src/main/java/ctbrec/ui/settings/api/Preferences.java +++ b/client/src/main/java/ctbrec/ui/settings/api/Preferences.java @@ -35,6 +35,8 @@ public class Preferences { private PreferencesStorage preferencesStorage; + private Runnable restartRequiredCallback = () -> {}; + private Preferences(PreferencesStorage preferencesStorage, Category...categories) { this.preferencesStorage = preferencesStorage; this.categories = categories; @@ -248,4 +250,12 @@ public class Preferences { return result; } } + + public void onRestartRequired(Runnable callback) { + this.restartRequiredCallback = callback; + } + + public Runnable getRestartRequiredCallback() { + return restartRequiredCallback; + } }