1 /*
2 * *************************************************************************************************************************************************************
3 *
4 * SteelBlue: DCI User Interfaces
5 * http://tidalwave.it/projects/steelblue
6 *
7 * Copyright (C) 2015 - 2024 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;
27
28 import javax.annotation.Nonnull;
29 import java.util.concurrent.Executor;
30 import java.util.concurrent.Executors;
31 import java.io.IOException;
32 import javafx.animation.KeyFrame;
33 import javafx.animation.Timeline;
34 import javafx.scene.Parent;
35 import javafx.scene.Scene;
36 import javafx.scene.input.KeyCombination;
37 import javafx.stage.Screen;
38 import javafx.stage.Stage;
39 import javafx.stage.StageStyle;
40 import javafx.util.Duration;
41 import javafx.application.Application;
42 import javafx.application.Platform;
43 import jfxtras.styles.jmetro.JMetro;
44 import jfxtras.styles.jmetro.Style;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import it.tidalwave.util.Key;
48 import it.tidalwave.util.PreferencesHandler;
49 import lombok.Getter;
50 import lombok.Setter;
51
52 /***************************************************************************************************************************************************************
53 *
54 * @author Fabrizio Giudici
55 *
56 **************************************************************************************************************************************************************/
57 public abstract class JavaFXApplicationWithSplash extends Application
58 {
59 private static final String K_BASE_NAME = "it.tidalwave.javafx";
60
61 /** A property representing the initial main window size as a percentual of the screen size. */
62 public static final Key<Double> K_INITIAL_SIZE = Key.of(K_BASE_NAME + ".initialSize", Double.class);
63
64 /** Whether the application should start maximized. */
65 public static final Key<Boolean> K_MAXIMIZED = Key.of(K_BASE_NAME + ".maximized", Boolean.class);
66
67 /** Whether the application should start at full screen. */
68 public static final Key<Boolean> K_FULL_SCREEN = Key.of(K_BASE_NAME + ".fullScreen", Boolean.class);
69
70 /** Whether the application should always stay at full screen. */
71 public static final Key<Boolean> K_FULL_SCREEN_LOCKED = Key.of(K_BASE_NAME + ".fullScreenLocked", Boolean.class);
72
73 /** The minimum duration of the splash screen. */
74 public static final Key<Duration> K_MIN_SPLASH_DURATION = Key.of(K_BASE_NAME + ".minSplashDuration", Duration.class);
75
76 private static final String DEFAULT_APPLICATION_FXML = "Application.fxml";
77
78 private static final String DEFAULT_SPLASH_FXML = "Splash.fxml";
79
80 private static final Duration DEFAULT_MIN_SPLASH_DURATION = Duration.seconds(2);
81
82 // Don't use Slf4j and its static logger - give Main a chance to initialize things
83 private final Logger log = LoggerFactory.getLogger(JavaFXApplicationWithSplash.class);
84
85 private Splash splash;
86
87 private boolean maximized;
88
89 private boolean fullScreen;
90
91 private boolean fullScreenLocked;
92
93 @Getter @Setter
94 protected String applicationFxml = DEFAULT_APPLICATION_FXML;
95
96 @Getter @Setter
97 protected String splashFxml = DEFAULT_SPLASH_FXML;
98
99 /***********************************************************************************************************************************************************
100 * {@inheritDoc}
101 **********************************************************************************************************************************************************/
102 @Override
103 public void init()
104 {
105 log.info("init()");
106 splash = new Splash(this, splashFxml, this::createScene);
107 splash.init();
108 }
109
110 /***********************************************************************************************************************************************************
111 * {@inheritDoc}
112 **********************************************************************************************************************************************************/
113 @Override
114 public void start (@Nonnull final Stage stage)
115 {
116 log.info("start({})", stage);
117 final var preferencesHandler = PreferencesHandler.getInstance();
118 fullScreen = preferencesHandler.getProperty(K_FULL_SCREEN).orElse(false);
119 fullScreenLocked = preferencesHandler.getProperty(K_FULL_SCREEN_LOCKED).orElse(false);
120 maximized = preferencesHandler.getProperty(K_MAXIMIZED).orElse(false);
121
122 final var splashStage = new Stage(StageStyle.TRANSPARENT);
123 stage.setMaximized(maximized);
124 // splashStage.setMaximized(maximized); FIXME: doesn't work
125 configureFullScreen(stage);
126 // configureFullScreen(splashStage); FIXME: deadlocks JDK 1.8.0_40
127
128 if (!maximized && !fullScreen)
129 {
130 splashStage.centerOnScreen();
131 }
132
133 final var splashCreationTime = System.currentTimeMillis();
134 splash.show(splashStage);
135
136 getExecutor().execute(() -> // FIXME: use JavaFX Worker?
137 {
138 initializeInBackground();
139 Platform.runLater(() ->
140 {
141 try
142 {
143 final var applicationNad = createParent();
144 final var scene = createScene((Parent)applicationNad.getNode());
145 stage.setOnCloseRequest(event -> onClosing());
146 stage.setScene(scene);
147 onStageCreated(stage, applicationNad);
148 stage.setFullScreen(fullScreen);
149 final double scale = preferencesHandler.getProperty(K_INITIAL_SIZE).orElse(0.65);
150 final var screenSize = Screen.getPrimary().getBounds();
151 stage.setWidth(scale * screenSize.getWidth());
152 stage.setHeight(scale * screenSize.getHeight());
153 stage.show();
154 splashStage.toFront();
155
156 final var duration = preferencesHandler.getProperty(K_MIN_SPLASH_DURATION).orElse(DEFAULT_MIN_SPLASH_DURATION);
157 final var delay = Math.max(0, splashCreationTime + duration.toMillis() - System.currentTimeMillis());
158 final var dismissSplash = new Timeline(new KeyFrame(Duration.millis(delay), e -> splash.dismiss()));
159 Platform.runLater(dismissSplash::play);
160 }
161 catch (IOException e)
162 {
163 log.error("", e);
164 }
165 });
166 });
167 }
168
169 /***********************************************************************************************************************************************************
170 *
171 **********************************************************************************************************************************************************/
172 protected void onStageCreated (@Nonnull final Stage stage, @Nonnull final NodeAndDelegate<?> applicationNad)
173 {
174 }
175
176 /***********************************************************************************************************************************************************
177 *
178 **********************************************************************************************************************************************************/
179 @Nonnull
180 protected abstract NodeAndDelegate<?> createParent()
181 throws IOException;
182
183 /***********************************************************************************************************************************************************
184 *
185 **********************************************************************************************************************************************************/
186 protected abstract void initializeInBackground();
187
188 /***********************************************************************************************************************************************************
189 * Invoked when the main {@link Stage} is being closed.
190 **********************************************************************************************************************************************************/
191 protected void onClosing()
192 {
193 }
194
195 /***********************************************************************************************************************************************************
196 *
197 **********************************************************************************************************************************************************/
198 @Nonnull
199 protected Executor getExecutor()
200 {
201 return Executors.newSingleThreadExecutor();
202 }
203
204 /***********************************************************************************************************************************************************
205 *
206 **********************************************************************************************************************************************************/
207 protected Scene createScene (@Nonnull final Parent parent)
208 {
209 final var scene = new Scene(parent);
210 final var jMetro = new JMetro(Style.DARK);
211 jMetro.setScene(scene);
212 return scene;
213 }
214
215 /***********************************************************************************************************************************************************
216 *
217 **********************************************************************************************************************************************************/
218 private void configureFullScreen (@Nonnull final Stage stage)
219 {
220 stage.setFullScreen(fullScreen);
221
222 if (fullScreen && fullScreenLocked)
223 {
224 stage.setFullScreenExitHint("");
225 stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
226 }
227 }
228 }