1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package it.tidalwave.ui.javafx;
27
28 import jakarta.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
55
56
57 public abstract class JavaFXApplicationWithSplash extends Application
58 {
59 private static final String K_BASE_NAME = "it.tidalwave.javafx";
60
61
62 public static final Key<Double> K_INITIAL_SIZE = Key.of(K_BASE_NAME + ".initialSize", Double.class);
63
64
65 public static final Key<Boolean> K_MAXIMIZED = Key.of(K_BASE_NAME + ".maximized", Boolean.class);
66
67
68 public static final Key<Boolean> K_FULL_SCREEN = Key.of(K_BASE_NAME + ".fullScreen", Boolean.class);
69
70
71 public static final Key<Boolean> K_FULL_SCREEN_LOCKED = Key.of(K_BASE_NAME + ".fullScreenLocked", Boolean.class);
72
73
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
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
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
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
125 configureFullScreen(stage);
126
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(() ->
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
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 }