From 155e250483dab37a6af6da591931b3a42e8cb826 Mon Sep 17 00:00:00 2001 From: Sam Carlberg Date: Mon, 19 Mar 2018 22:33:43 -0400 Subject: [PATCH] Add a preloader to notify users of the status of startup routines (#445) --- .../app/MainWindowController.java | 12 +++ .../shuffleboard/app/PreloaderController.java | 38 ++++++++ .../first/shuffleboard/app/Shuffleboard.java | 52 +++++++---- .../app/ShuffleboardPreloader.java | 91 +++++++++++++++++++ .../wpi/first/shuffleboard/app/Preloader.fxml | 27 ++++++ 5 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/edu/wpi/first/shuffleboard/app/PreloaderController.java create mode 100644 app/src/main/java/edu/wpi/first/shuffleboard/app/ShuffleboardPreloader.java create mode 100644 app/src/main/resources/edu/wpi/first/shuffleboard/app/Preloader.fxml diff --git a/app/src/main/java/edu/wpi/first/shuffleboard/app/MainWindowController.java b/app/src/main/java/edu/wpi/first/shuffleboard/app/MainWindowController.java index d1924c788..34b1c9a24 100644 --- a/app/src/main/java/edu/wpi/first/shuffleboard/app/MainWindowController.java +++ b/app/src/main/java/edu/wpi/first/shuffleboard/app/MainWindowController.java @@ -156,6 +156,18 @@ private void initialize() throws IOException { running -> running ? "Stop recording" : "Start recording")); FxUtils.bind(root.getStylesheets(), stylesheets); + log.info("Setting up plugins in the UI"); + PluginLoader.getDefault().getLoadedPlugins().forEach(plugin -> { + plugin.loadedProperty().addListener((__, was, is) -> { + if (is) { + setup(plugin); + } else { + tearDown(plugin); + } + }); + setup(plugin); + }); + sourcesAccordion.getPanes().sort(Comparator.comparing(TitledPane::getText)); PluginLoader.getDefault().getKnownPlugins().addListener((ListChangeListener) c -> { while (c.next()) { if (c.wasAdded()) { diff --git a/app/src/main/java/edu/wpi/first/shuffleboard/app/PreloaderController.java b/app/src/main/java/edu/wpi/first/shuffleboard/app/PreloaderController.java new file mode 100644 index 000000000..18fd5b468 --- /dev/null +++ b/app/src/main/java/edu/wpi/first/shuffleboard/app/PreloaderController.java @@ -0,0 +1,38 @@ +package edu.wpi.first.shuffleboard.app; + +import edu.wpi.first.shuffleboard.api.util.FxUtils; + +import org.controlsfx.tools.Utils; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.Pane; + +public class PreloaderController { + + @FXML + private Pane root; + @FXML + private Label versionLabel; + @FXML + private Label stateLabel; + @FXML + private ProgressBar progressBar; + + @FXML + private void initialize() { + progressBar.setProgress(-1); + versionLabel.setText(Shuffleboard.getVersion()); + FxUtils.setController(root, this); + } + + public void setStateText(String text) { + stateLabel.setText(text + "..."); + } + + public void setProgress(double progress) { + progressBar.setProgress(Utils.clamp(0, progress, 1)); + } + +} diff --git a/app/src/main/java/edu/wpi/first/shuffleboard/app/Shuffleboard.java b/app/src/main/java/edu/wpi/first/shuffleboard/app/Shuffleboard.java index 3c8f0d88f..5a37b3967 100644 --- a/app/src/main/java/edu/wpi/first/shuffleboard/app/Shuffleboard.java +++ b/app/src/main/java/edu/wpi/first/shuffleboard/app/Shuffleboard.java @@ -15,6 +15,7 @@ import com.github.zafarkhaja.semver.Version; import com.google.common.base.Stopwatch; +import com.sun.javafx.application.LauncherImpl; import de.codecentric.centerdevice.javafxsvg.SvgImageLoaderFactory; @@ -57,8 +58,12 @@ public class Shuffleboard extends Application { private final Stopwatch startupTimer = Stopwatch.createStarted(); + public static void main(String[] args) { + LauncherImpl.launchApplication(Shuffleboard.class, ShuffleboardPreloader.class, args); + } + @Override - public void init() throws AlreadyLockedException, IOException { + public void init() throws AlreadyLockedException, IOException, InterruptedException { try { JUnique.acquireLock(getClass().getCanonicalName(), message -> { onOtherAppStart.run(); @@ -78,17 +83,10 @@ public void init() throws AlreadyLockedException, IOException { // Search for and load themes from the custom theme directory before loading application preferences // This avoids an issue with attempting to load a theme at startup that hasn't yet been registered logger.finer("Registering custom user themes from external dir"); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading custom themes", 0)); Themes.getDefault().loadThemesFromDir(); logger.info("Build time: " + getBuildTime()); - } - - @Override - public void start(Stage primaryStage) throws IOException { - // Set up the application thread to log exceptions instead of using printStackTrace() - // Must be called in start() because init() is run on the main thread, not the FX application thread - Thread.currentThread().setUncaughtExceptionHandler(Shuffleboard::uncaughtException); - onOtherAppStart = () -> Platform.runLater(primaryStage::toFront); // Before we load components that only work with Java 8, check to make sure // the application is running on Java 8. If we are running on an invalid @@ -110,25 +108,41 @@ public void start(Stage primaryStage) throws IOException { return; } - Stopwatch fxmlLoadTimer = Stopwatch.createStarted(); - - FXMLLoader loader = new FXMLLoader(MainWindowController.class.getResource("MainWindow.fxml")); - mainPane = loader.load(); - long fxmlLoadTime = fxmlLoadTimer.elapsed(TimeUnit.MILLISECONDS); - logger.log(fxmlLoadTime >= 500 ? Level.WARNING : Level.INFO, "Took " + fxmlLoadTime + "ms to load the main FXML"); - final MainWindowController mainWindowController = loader.getController(); - - primaryStage.setScene(new Scene(mainPane)); - + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading base plugin", 0.125)); PluginLoader.getDefault().load(new BasePlugin()); Recorder.getInstance().start(); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading NetworkTables plugin", 0.25)); PluginLoader.getDefault().load(new NetworkTablesPlugin()); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading CameraServer plugin", 0.375)); PluginLoader.getDefault().load(new CameraServerPlugin()); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading Powerup plugin", 0.5)); PluginLoader.getDefault().load(new PowerupPlugin()); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading custom plugins", 0.625)); PluginLoader.getDefault().loadAllJarsFromDir(Storage.getPluginPath()); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Loading custom plugins", 0.75)); PluginCache.getDefault().loadCache(PluginLoader.getDefault()); + notifyPreloader(new ShuffleboardPreloader.StateNotification("Starting up", 1)); + Thread.sleep(20); // small wait to let the status be visible - the preloader doesn't get notifications for a bit + } + + @Override + public void start(Stage primaryStage) throws IOException { + // Set up the application thread to log exceptions instead of using printStackTrace() + // Must be called in start() because init() is run on the main thread, not the FX application thread + Thread.currentThread().setUncaughtExceptionHandler(Shuffleboard::uncaughtException); + onOtherAppStart = () -> Platform.runLater(primaryStage::toFront); + + Stopwatch fxmlLoadTimer = Stopwatch.createStarted(); + + FXMLLoader loader = new FXMLLoader(MainWindowController.class.getResource("MainWindow.fxml")); + mainPane = loader.load(); + long fxmlLoadTime = fxmlLoadTimer.elapsed(TimeUnit.MILLISECONDS); + logger.log(fxmlLoadTime >= 500 ? Level.WARNING : Level.INFO, "Took " + fxmlLoadTime + "ms to load the main FXML"); + final MainWindowController mainWindowController = loader.getController(); + + primaryStage.setScene(new Scene(mainPane)); // Setup the dashboard tabs after all plugins are loaded Platform.runLater(() -> { diff --git a/app/src/main/java/edu/wpi/first/shuffleboard/app/ShuffleboardPreloader.java b/app/src/main/java/edu/wpi/first/shuffleboard/app/ShuffleboardPreloader.java new file mode 100644 index 000000000..a940d3783 --- /dev/null +++ b/app/src/main/java/edu/wpi/first/shuffleboard/app/ShuffleboardPreloader.java @@ -0,0 +1,91 @@ +package edu.wpi.first.shuffleboard.app; + +import edu.wpi.first.shuffleboard.api.util.FxUtils; +import edu.wpi.first.shuffleboard.app.prefs.AppPreferences; + +import javafx.application.Preloader; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +/** + * The preloader for shuffleboard. This will display the progress of various startup routines until the main window + * appears. + */ +public class ShuffleboardPreloader extends Preloader { + + private Stage preloaderStage; + private PreloaderController controller; + + @Override + public void start(Stage stage) throws Exception { + preloaderStage = stage; + + Pane preloaderPane = FXMLLoader.load(PreloaderController.class.getResource("Preloader.fxml")); + controller = FxUtils.getController(preloaderPane); + + Scene scene = new Scene(preloaderPane); + scene.getStylesheets().setAll(AppPreferences.getInstance().getTheme().getStyleSheets()); + stage.setScene(scene); + stage.initStyle(StageStyle.UNDECORATED); + stage.show(); + } + + @Override + public void handleApplicationNotification(PreloaderNotification info) { + if (info instanceof StateNotification) { + StateNotification notification = (StateNotification) info; + controller.setStateText(notification.getState()); + controller.setProgress(notification.getProgress()); + } + } + + @Override + public void handleStateChangeNotification(StateChangeNotification info) { + if (info.getType() == StateChangeNotification.Type.BEFORE_START) { + preloaderStage.close(); + } + } + + /** + * A notification for the progress of a state in the preloader. + */ + public static final class StateNotification implements PreloaderNotification { + + private final String state; + private final double progress; + + /** + * Creates a new state notification. + * + * @param state the state + * @param progress the progress of the state, in the range [0, 1] + */ + public StateNotification(String state, double progress) { + this.state = state; + this.progress = progress; + } + + /** + * Gets the state. + */ + public String getState() { + return state; + } + + /** + * Gets the progress. + */ + public double getProgress() { + return progress; + } + + @Override + public String toString() { + return String.format("StateNotification(state='%s', progress=%s)", state, progress); + } + } + +} diff --git a/app/src/main/resources/edu/wpi/first/shuffleboard/app/Preloader.fxml b/app/src/main/resources/edu/wpi/first/shuffleboard/app/Preloader.fxml new file mode 100644 index 000000000..33a77a1ce --- /dev/null +++ b/app/src/main/resources/edu/wpi/first/shuffleboard/app/Preloader.fxml @@ -0,0 +1,27 @@ + + + + + + + + +
+ + + + + +
+ + + + +