Skip to contentMethod: createScene(Parent)
1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * SteelBlue: DCI User Interfaces
5: * http://tidalwave.it/projects/steelblue
6: *
7: * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/steelblue-src
22: * git clone https://github.com/tidalwave-it/steelblue-src
23: *
24: * *************************************************************************************************************************************************************
25: */
26: package it.tidalwave.ui.javafx.impl;
27:
28: import jakarta.annotation.Nonnull;
29: import javafx.animation.KeyFrame;
30: import javafx.animation.Timeline;
31: import javafx.scene.Parent;
32: import javafx.scene.Scene;
33: import javafx.scene.input.KeyCombination;
34: import javafx.stage.Screen;
35: import javafx.stage.Stage;
36: import javafx.stage.StageStyle;
37: import javafx.util.Duration;
38: import javafx.application.Application;
39: import javafx.application.Platform;
40: import it.tidalwave.ui.javafx.NodeAndDelegate;
41: import org.slf4j.Logger;
42: import org.slf4j.LoggerFactory;
43: import it.tidalwave.util.Key;
44: import it.tidalwave.util.PreferencesHandler;
45: import lombok.Getter;
46: import lombok.Setter;
47:
48: /***************************************************************************************************************************************************************
49: *
50: * @author Fabrizio Giudici
51: *
52: **************************************************************************************************************************************************************/
53: public abstract class JavaFXApplicationWithSplash extends Application
54: {
55: private static final String K_BASE_NAME = "it.tidalwave.ui.javafx";
56:
57: /** A property representing the initial main window size as a percentual of the screen size. */
58: public static final Key<Double> K_INITIAL_SIZE = Key.of(K_BASE_NAME + ".initialSize", Double.class);
59:
60: /** Whether the application should start maximized. */
61: public static final Key<Boolean> K_MAXIMIZED = Key.of(K_BASE_NAME + ".maximized", Boolean.class);
62:
63: /** Whether the application should start at full screen. */
64: public static final Key<Boolean> K_FULL_SCREEN = Key.of(K_BASE_NAME + ".fullScreen", Boolean.class);
65:
66: /** Whether the application should always stay at full screen. */
67: public static final Key<Boolean> K_FULL_SCREEN_LOCKED = Key.of(K_BASE_NAME + ".fullScreenLocked", Boolean.class);
68:
69: /** The minimum duration of the splash screen. */
70: public static final Key<Duration> K_MIN_SPLASH_DURATION = Key.of(K_BASE_NAME + ".minSplashDuration", Duration.class);
71:
72: /** Whether invocations to presentation delegate methods should be logged at debug level. */
73: public static final Key<Boolean> K_LOG_DELEGATE_INVOCATIONS = Key.of(K_BASE_NAME + ".logDelegateInvocations", Boolean.class);
74:
75: private static final String DEFAULT_APPLICATION_FXML = "Application.fxml";
76:
77: private static final String DEFAULT_SPLASH_FXML = "Splash.fxml";
78:
79: private static final Duration DEFAULT_MIN_SPLASH_DURATION = Duration.seconds(2);
80:
81: // Don't use Slf4j and its static logger - give Main a chance to initialize things
82: private final Logger log = LoggerFactory.getLogger(JavaFXApplicationWithSplash.class);
83:
84: private Splash splash;
85:
86: private boolean maximized;
87:
88: private boolean fullScreen;
89:
90: private boolean fullScreenLocked;
91:
92: @Getter @Setter
93: protected String applicationFxml = DEFAULT_APPLICATION_FXML;
94:
95: @Getter @Setter
96: protected String splashFxml = DEFAULT_SPLASH_FXML;
97:
98: /***********************************************************************************************************************************************************
99: * {@inheritDoc}
100: **********************************************************************************************************************************************************/
101: @Override
102: public void start (@Nonnull final Stage stage)
103: {
104: log.info("start({})", stage);
105: splash = new Splash(this, splashFxml, this::createScene);
106: splash.init();
107: final var preferencesHandler = PreferencesHandler.getInstance();
108: fullScreen = preferencesHandler.getProperty(K_FULL_SCREEN).orElse(false);
109: fullScreenLocked = preferencesHandler.getProperty(K_FULL_SCREEN_LOCKED).orElse(false);
110: maximized = preferencesHandler.getProperty(K_MAXIMIZED).orElse(false);
111:
112: final var splashStage = new Stage(StageStyle.TRANSPARENT);
113: stage.setMaximized(maximized);
114: // splashStage.setMaximized(maximized); FIXME: doesn't work
115: configureFullScreen(stage);
116: // configureFullScreen(splashStage); FIXME: deadlocks JDK 1.8.0_40
117:
118: if (!maximized && !fullScreen)
119: {
120: splashStage.centerOnScreen();
121: }
122:
123: final var splashCreationTime = System.currentTimeMillis();
124: splash.show(splashStage);
125: // No executors available here
126: final var thread = new Thread(() -> initializeInBackground(stage, splashStage, splashCreationTime), "initializer");
127: thread.start();
128: }
129:
130: /***********************************************************************************************************************************************************
131: *
132: **********************************************************************************************************************************************************/
133: protected void onStageCreated (@Nonnull final Stage stage, @Nonnull final NodeAndDelegate<?> applicationNad)
134: {
135: }
136:
137: /***********************************************************************************************************************************************************
138: *
139: **********************************************************************************************************************************************************/
140: @Nonnull
141: protected abstract NodeAndDelegate<?> createParent();
142:
143: /***********************************************************************************************************************************************************
144: *
145: **********************************************************************************************************************************************************/
146: protected abstract void initializeInBackground();
147:
148: /***********************************************************************************************************************************************************
149: * Invoked when the main {@link Stage} is being closed. This method is called in the JavaFX thread.
150: **********************************************************************************************************************************************************/
151: protected void onCloseRequest()
152: {
153: }
154:
155: /***********************************************************************************************************************************************************
156: *
157: **********************************************************************************************************************************************************/
158: @Nonnull
159: protected Scene createScene (@Nonnull final Parent parent)
160: {
161: return new Scene(parent);
162: }
163:
164: /***********************************************************************************************************************************************************
165: * Calls {@link #initializeInBackground()} and then {@link #afterInitializationCompleted(Stage, Stage, long)} in the JavaFX thread.
166: * @param stage the main {@code Stage}
167: * @param splashStage the splash {@code Stage}
168: * @param splashCreationTime the time at which the splash {@code Stage} was created
169: **********************************************************************************************************************************************************/
170: private void initializeInBackground (@Nonnull final Stage stage, @Nonnull final Stage splashStage, final long splashCreationTime)
171: {
172: try
173: {
174: initializeInBackground();
175: Platform.runLater(() -> afterInitializationCompleted(stage, splashStage, splashCreationTime));
176: }
177: catch (Throwable t)
178: {
179: log.error("", t);
180: }
181: }
182:
183: /***********************************************************************************************************************************************************
184: * Called after the initialisation in background has been completed, this method dismisses the splash {@link Stage} and brings the main {@code Stage} to
185: * view.
186: * @param stage the main {@code Stage}
187: * @param splashStage the splash {@code Stage}
188: * @param splashCreationTime the time at which the splash {@code Stage} was created
189: **********************************************************************************************************************************************************/
190: private void afterInitializationCompleted (@Nonnull final Stage stage, @Nonnull final Stage splashStage, final long splashCreationTime)
191: {
192: log.info("afterInitializationCompleted({}, {}, {})", stage, splashStage, splashCreationTime);
193: final var preferencesHandler = PreferencesHandler.getInstance();
194: final var applicationNad = createParent();
195: final var scene = createScene((Parent)applicationNad.getNode());
196: stage.setOnCloseRequest(event -> onCloseRequest());
197: stage.setScene(scene);
198: onStageCreated(stage, applicationNad);
199: final double scale = preferencesHandler.getProperty(K_INITIAL_SIZE).orElse(0.65);
200: final var screenSize = Screen.getPrimary().getBounds();
201: stage.setWidth(scale * screenSize.getWidth());
202: stage.setHeight(scale * screenSize.getHeight());
203: stage.show();
204: splashStage.toFront();
205:
206: final var duration = preferencesHandler.getProperty(K_MIN_SPLASH_DURATION).orElse(DEFAULT_MIN_SPLASH_DURATION);
207: final var delay = Math.max(0, splashCreationTime + duration.toMillis() - System.currentTimeMillis());
208: final var dismissSplash = new Timeline(new KeyFrame(Duration.millis(delay), e -> splash.dismiss()));
209: dismissSplash.play();
210: }
211:
212: /***********************************************************************************************************************************************************
213: *
214: **********************************************************************************************************************************************************/
215: private void configureFullScreen (@Nonnull final Stage stage)
216: {
217: stage.setFullScreen(fullScreen);
218:
219: if (fullScreen && fullScreenLocked)
220: {
221: stage.setFullScreenExitHint("");
222: stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
223: }
224: }
225: }