diff --git a/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java b/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java index 44504e1..1eb524d 100644 --- a/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java +++ b/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java @@ -6,7 +6,7 @@ import envoy.data.User.UserStatus; import envoy.client.data.Context; import envoy.client.helper.ShutdownHelper; -import envoy.client.ui.SceneContext.SceneInfo; +import envoy.client.ui.SceneInfo; import envoy.client.util.UserUtil; /** diff --git a/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java b/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java index 05ef3b7..3c4da5e 100644 --- a/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java +++ b/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java @@ -4,7 +4,7 @@ import java.util.*; import javafx.scene.input.KeyCombination; -import envoy.client.ui.SceneContext.SceneInfo; +import envoy.client.ui.SceneInfo; /** * Contains all keyboard shortcuts used throughout the application. diff --git a/client/src/main/java/envoy/client/ui/SceneContext.java b/client/src/main/java/envoy/client/ui/SceneContext.java index 8e42c33..a255f81 100644 --- a/client/src/main/java/envoy/client/ui/SceneContext.java +++ b/client/src/main/java/envoy/client/ui/SceneContext.java @@ -4,7 +4,6 @@ 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.stage.Stage; @@ -28,51 +27,11 @@ import envoy.client.event.*; */ 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 sceneStack = new Stack<>(); - private final Stack controllerStack = new Stack<>(); + private final Stack roots = new Stack<>(); + private final Stack controllers = new Stack<>(); - private static final Settings settings = Settings.getInstance(); + private Scene scene; /** * Initializes the scene context. @@ -88,44 +47,44 @@ public final class SceneContext implements EventListener { /** * Loads a new scene specified by a scene info. * - * @param sceneInfo specifies the scene to load + * @param info specifies the scene to load * @throws RuntimeException if the loading process fails * @since Envoy Client v0.1-beta */ - public void load(SceneInfo sceneInfo) { - EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + sceneInfo); - loader.setRoot(null); - loader.setController(null); + public void load(SceneInfo info) { + EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + info); try { - final var rootNode = - (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path)); - final var scene = new Scene(rootNode); - final var controller = loader.getController(); - controllerStack.push(controller); - sceneStack.push(scene); - stage.setScene(scene); + // 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(sceneInfo)); + .putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(info)); // Supply the scene specific keyboard shortcuts if (controller instanceof KeyboardMapping) scene.getAccelerators() .putAll(((KeyboardMapping) controller).getKeyboardShortcuts()); - - // 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); + } catch (IOException e) { throw new RuntimeException(e); } } @@ -137,29 +96,30 @@ public final class SceneContext implements EventListener { */ public void pop() { - // Pop scene and controller - sceneStack.pop(); - controllerStack.pop(); + // Pop current root node and controller + roots.pop(); + controllers.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 (!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); } - stage.show(); } private void applyCSS() { - if (!sceneStack.isEmpty()) { - final var styleSheets = stage.getScene().getStylesheets(); - final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css"; + 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()); @@ -168,8 +128,8 @@ public final class SceneContext implements EventListener { @Event(eventType = Logout.class, priority = 150) private void onLogout() { - sceneStack.clear(); - controllerStack.clear(); + roots.clear(); + controllers.clear(); } @Event(priority = 150, eventType = ThemeChangeEvent.class) @@ -182,7 +142,7 @@ public final class SceneContext implements EventListener { * @return the controller used by the current scene * @since Envoy Client v0.1-beta */ - public T getController() { return (T) controllerStack.peek(); } + public T getController() { return (T) controllers.peek(); } /** * @return the stage in which the scenes are displayed @@ -194,5 +154,5 @@ public final class SceneContext implements EventListener { * @return whether the scene stack is empty * @since Envoy Client v0.2-beta */ - public boolean isEmpty() { return sceneStack.isEmpty(); } + public boolean isEmpty() { return roots.isEmpty(); } } diff --git a/client/src/main/java/envoy/client/ui/SceneInfo.java b/client/src/main/java/envoy/client/ui/SceneInfo.java new file mode 100644 index 0000000..801e947 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/SceneInfo.java @@ -0,0 +1,40 @@ +package envoy.client.ui; + +/** + * 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; + } +} \ No newline at end of file diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java index 18468e3..ddbe0ef 100644 --- a/client/src/main/java/envoy/client/ui/Startup.java +++ b/client/src/main/java/envoy/client/ui/Startup.java @@ -20,7 +20,6 @@ import envoy.client.data.*; import envoy.client.data.shortcuts.EnvoyShortcutConfig; import envoy.client.helper.ShutdownHelper; import envoy.client.net.Client; -import envoy.client.ui.SceneContext.SceneInfo; import envoy.client.ui.controller.LoginScene; import envoy.client.util.IconUtil; @@ -94,7 +93,7 @@ public final class Startup extends Application { final var sceneContext = new SceneContext(stage); context.setSceneContext(sceneContext); - // Authenticate with token if present + // Authenticate with token if present or load login scene if (localDB.getAuthToken() != null) { logger.info("Attempting authentication with token..."); localDB.loadUserData(); @@ -103,8 +102,9 @@ public final class Startup extends Application { VERSION, localDB.getLastSync()))) sceneContext.load(SceneInfo.LOGIN_SCENE); } else - // Load login scene sceneContext.load(SceneInfo.LOGIN_SCENE); + + stage.show(); } /** @@ -226,7 +226,7 @@ public final class Startup extends Application { // Load ChatScene stage.setMinHeight(400); stage.setMinWidth(843); - context.getSceneContext().load(SceneContext.SceneInfo.CHAT_SCENE); + context.getSceneContext().load(SceneInfo.CHAT_SCENE); stage.centerOnScreen(); // Exit or minimize the stage when a close request occurs diff --git a/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java b/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java index 680074a..cba5b38 100644 --- a/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java +++ b/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java @@ -15,7 +15,7 @@ import envoy.util.EnvoyLog; import envoy.client.data.Context; import envoy.client.data.commands.*; import envoy.client.helper.ShutdownHelper; -import envoy.client.ui.SceneContext.SceneInfo; +import envoy.client.ui.SceneInfo; import envoy.client.ui.controller.ChatScene; import envoy.client.util.*; @@ -32,7 +32,7 @@ public final class ChatSceneCommands { private final SystemCommandBuilder builder = new SystemCommandBuilder(messageTextAreaCommands); - private static final String messageDependantCommandDescription = + private static final String messageDependentCommandDescription = " the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message."; /** @@ -141,7 +141,7 @@ public final class ChatSceneCommands { else useRelativeMessage(command, action, additionalCheck, positionalArgument, false); }).setDefaults("s").setNumberOfArguments(1) - .setDescription(description.concat(messageDependantCommandDescription)).build(command); + .setDescription(description.concat(messageDependentCommandDescription)).build(command); } private void selectionNeighbor(Consumer action, Predicate additionalCheck, diff --git a/client/src/main/java/envoy/client/ui/controller/ChatScene.java b/client/src/main/java/envoy/client/ui/controller/ChatScene.java index 8dc48ef..74499c2 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -1,5 +1,7 @@ package envoy.client.ui.controller; +import static envoy.client.ui.SceneInfo.SETTINGS_SCENE; + import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.io.*; @@ -32,7 +34,7 @@ import envoy.data.*; import envoy.data.Attachment.AttachmentType; import envoy.data.Message.MessageStatus; import envoy.event.*; -import envoy.event.contact.*; +import envoy.event.contact.UserOperation; import envoy.exception.EnvoyException; import envoy.util.EnvoyLog; @@ -445,7 +447,7 @@ public final class ChatScene implements EventListener, Restorable, KeyboardMappi */ @FXML private void settingsButtonClicked() { - sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); + sceneContext.load(SETTINGS_SCENE); } /** diff --git a/client/src/main/java/envoy/client/util/UserUtil.java b/client/src/main/java/envoy/client/util/UserUtil.java index bc7d7fe..247866e 100644 --- a/client/src/main/java/envoy/client/util/UserUtil.java +++ b/client/src/main/java/envoy/client/util/UserUtil.java @@ -16,7 +16,7 @@ import envoy.util.EnvoyLog; import envoy.client.data.Context; import envoy.client.event.*; import envoy.client.helper.*; -import envoy.client.ui.SceneContext.SceneInfo; +import envoy.client.ui.SceneInfo; import envoy.client.ui.controller.ChatScene; /** diff --git a/client/src/main/resources/fxml/ChatScene.fxml b/client/src/main/resources/fxml/ChatScene.fxml index f293d3e..88d5d0a 100644 --- a/client/src/main/resources/fxml/ChatScene.fxml +++ b/client/src/main/resources/fxml/ChatScene.fxml @@ -24,8 +24,8 @@ @@ -57,8 +57,7 @@ - + @@ -156,8 +155,7 @@ - + diff --git a/client/src/main/resources/fxml/SettingsScene.fxml b/client/src/main/resources/fxml/SettingsScene.fxml index 50a90ab..edca898 100644 --- a/client/src/main/resources/fxml/SettingsScene.fxml +++ b/client/src/main/resources/fxml/SettingsScene.fxml @@ -7,11 +7,16 @@ - + - + @@ -22,7 +27,8 @@ - + @@ -32,7 +38,8 @@ -