This repository has been archived on 2021-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
envoy/client/src/main/java/envoy/client/ui/SceneContext.java

192 lines
5.3 KiB
Java

package envoy.client.ui;
import java.io.IOException;
import java.util.Stack;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.input.*;
import javafx.stage.Stage;
import envoy.client.data.Settings;
import envoy.client.event.*;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.*;
/**
* 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.
* <p>
* When a scene is loaded, the style sheet for the current theme is applied to
* it.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SceneContext.java</strong><br>
* Created: <strong>06.06.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class SceneContext implements EventListener {
/**
* Contains information about different scenes and their FXML resource files.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public enum SceneInfo {
/**
* The main scene in which the chat screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
CHAT_SCENE("/fxml/ChatScene.fxml"),
/**
* The scene in which the settings screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/**
* The scene in which the login screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
LOGIN_SCENE("/fxml/LoginScene.fxml");
/**
* The path to the FXML resource.
*/
public final String path;
SceneInfo(String path) { this.path = path; }
}
private final Stage stage;
private final FXMLLoader loader = new FXMLLoader();
private final Stack<Scene> sceneStack = new Stack<>();
private final Stack<Object> controllerStack = new Stack<>();
private static final Settings settings = Settings.getInstance();
/**
* 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 sceneInfo specifies the scene to load
* @throws RuntimeException if the loading process fails
* @since Envoy Client v0.1-beta
*/
public void load(SceneInfo sceneInfo) {
loader.setRoot(null);
loader.setController(null);
try {
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
final var scene = new Scene(rootNode);
controllerStack.push(loader.getController());
sceneStack.push(scene);
stage.setScene(scene);
// Adding the option to exit Linux-like with "Control" + "Q"
scene.getAccelerators()
.put(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN),
() -> {
// Presumably no Settings are loaded in the login scene, hence Envoy is closed
// directly
if (sceneInfo != SceneInfo.LOGIN_SCENE && settings.isHideOnClose()) stage.setIconified(true);
else {
EventBus.getInstance().dispatch(new EnvoyCloseEvent());
System.exit(0);
}
});
// The LoginScene is the only scene not intended to be resized
// As strange as it seems, this is needed as otherwise the LoginScene won't be
// displayed on some OS (...Debian...)
stage.sizeToScene();
Platform.runLater(() -> stage.setResizable(sceneInfo != SceneInfo.LOGIN_SCENE));
applyCSS();
stage.show();
} catch (final IOException e) {
EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, String.format("Could not load scene for %s: ", sceneInfo), e);
throw new RuntimeException(e);
}
}
/**
* Removes the current scene and displays the previous one.
*
* @since Envoy Client v0.1-beta
*/
public void pop() {
// Pop scene and controller
sceneStack.pop();
controllerStack.pop();
// Apply new scene if present
if (!sceneStack.isEmpty()) {
final var newScene = sceneStack.peek();
stage.setScene(newScene);
applyCSS();
stage.sizeToScene();
// If the controller implements the Restorable interface,
// the actions to perform on restoration will be executed here
final var controller = controllerStack.peek();
if (controller instanceof Restorable) ((Restorable) controller).onRestore();
}
stage.show();
}
private void applyCSS() {
if (!sceneStack.isEmpty()) {
final var styleSheets = stage.getScene().getStylesheets();
final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
styleSheets.clear();
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), getClass().getResource(themeCSS).toExternalForm());
}
}
@Event(priority = 150, eventType = ThemeChangeEvent.class)
private void onThemeChange() { applyCSS(); }
/**
* @param <T> the type of the controller
* @return the controller used by the current scene
* @since Envoy Client v0.1-beta
*/
public <T> T getController() { return (T) controllerStack.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 sceneStack.isEmpty(); }
}