package envoy.client.ui; import java.io.IOException; import java.util.Stack; import java.util.logging.Level; import javafx.fxml.FXMLLoader; import javafx.scene.*; import javafx.stage.Stage; import dev.kske.eventbus.core.*; import envoy.util.EnvoyLog; import envoy.client.data.Settings; import envoy.client.data.shortcuts.*; import envoy.client.event.*; /** * Manages a stack of scenes. The most recently added scene is displayed inside a stage. When a * scene is removed from the stack, its predecessor is displayed. *

* When a scene is loaded, the style sheet for the current theme is applied to it. * * @author Kai S. K. Engelbart * @since Envoy Client v0.1-beta */ public final class SceneContext { private final Stage stage; private final Stack roots = new Stack<>(); private final Stack controllers = new Stack<>(); private Scene scene; /** * Initializes the scene context. * * @param stage the stage in which scenes will be displayed * @since Envoy Client v0.1-beta */ public SceneContext(Stage stage) { this.stage = stage; EventBus.getInstance().registerListener(this); } /** * Loads a new scene specified by a scene info. * * @param info specifies the scene to load * @throws RuntimeException if the loading process fails * @since Envoy Client v0.1-beta */ public void load(SceneInfo info) { EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + info); try { // Load root node and controller var loader = new FXMLLoader(); Parent root = loader.load(getClass().getResourceAsStream(info.path)); Object controller = loader.getController(); roots.push(root); controllers.push(controller); if (scene == null) { // One-time scene initialization scene = new Scene(root, stage.getWidth(), stage.getHeight()); applyCSS(); stage.setScene(scene); } else { scene.setRoot(root); } // Remove previous keyboard shortcuts scene.getAccelerators().clear(); // Supply the global custom keyboard shortcuts for that scene scene.getAccelerators() .putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(info)); // Supply the scene specific keyboard shortcuts if (controller instanceof KeyboardMapping) scene.getAccelerators() .putAll(((KeyboardMapping) controller).getKeyboardShortcuts()); } catch (IOException e) { throw new RuntimeException(e); } } /** * Removes the current scene and displays the previous one. * * @since Envoy Client v0.1-beta */ public void pop() { // Pop current root node and controller roots.pop(); controllers.pop(); // Apply new scene if present if (!roots.isEmpty()) { scene.setRoot(roots.peek()); // Invoke restore if controller is restorable var controller = controllers.peek(); if (controller instanceof Restorable) ((Restorable) controller).onRestore(); } else { // Remove the current scene entirely scene = null; stage.setScene(null); } } private void applyCSS() { if (scene != null) { var styleSheets = scene.getStylesheets(); var themeCSS = "/css/" + Settings.getInstance().getCurrentTheme() + ".css"; styleSheets.clear(); styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), getClass().getResource(themeCSS).toExternalForm()); } } @Event(Logout.class) @Priority(150) private void onLogout() { roots.clear(); controllers.clear(); } @Event(ThemeChangeEvent.class) @Priority(150) private void onThemeChange() { applyCSS(); } /** * @param the type of the controller * @return the controller used by the current scene * @since Envoy Client v0.1-beta */ public T getController() { return (T) controllers.peek(); } /** * @return the stage in which the scenes are displayed * @since Envoy Client v0.1-beta */ public Stage getStage() { return stage; } /** * @return whether the scene stack is empty * @since Envoy Client v0.2-beta */ public boolean isEmpty() { return roots.isEmpty(); } }