Skip to content

Commit

Permalink
Add a preloader to notify users of the status of startup routines (#445)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamCarlberg authored Mar 20, 2018
1 parent 4a1d68e commit 155e250
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Plugin>) c -> {
while (c.next()) {
if (c.wasAdded()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}

}
52 changes: 33 additions & 19 deletions app/src/main/java/edu/wpi/first/shuffleboard/app/Shuffleboard.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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(() -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="edu.wpi.first.shuffleboard.app.PreloaderController"
fx:id="root"
prefWidth="480.0" prefHeight="240.0">
<center>
<VBox alignment="CENTER_LEFT" styleClass="shuffleboard-dialog-header">
<padding>
<Insets topRightBottomLeft="32"/>
</padding>
<Label text="WPILib Shuffleboard" styleClass="shuffleboard-dialog-header-title"/>
<Label fx:id="versionLabel" styleClass="shuffleboard-dialog-header-subtitle"/>
</VBox>
</center>
<bottom>
<VBox>
<Label fx:id="stateLabel" alignment="CENTER" maxWidth="Infinity" textAlignment="CENTER"/>
<ProgressBar fx:id="progressBar" maxWidth="Infinity" maxHeight="6"/>
</VBox>
</bottom>
</BorderPane>

0 comments on commit 155e250

Please sign in to comment.