Improved splash screen ergonomics.
This commit is contained in:
parent
b81b27770e
commit
f6dbc50155
|
@ -2,4 +2,11 @@
|
|||
|
||||
> *Per*sonal *Fin*ance
|
||||
|
||||
Personal accounting desktop app to track your finances using common file formats.
|
||||
A personal accounting desktop app to track your finances using an approachable
|
||||
interface and interoperable file formats for maximum compatibility.
|
||||
|
||||
## About Perfin
|
||||
|
||||
Perfin is a desktop app built with Java 21 and JavaFX, using the SQLite3
|
||||
database for most data storage. It's intended to be used by individuals to
|
||||
track their finances across multiple accounts (savings, checking, credit, etc.).
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
package com.andrewlalis.perfin;
|
||||
|
||||
import com.andrewlalis.perfin.view.SplashScreenStage;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* The class from which the JavaFX-based application starts.
|
||||
*/
|
||||
public class PerfinApp extends Application {
|
||||
public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin");
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
SplashScreenStage splashStage = new SplashScreenStage("Loading", SceneUtil.load("/startup-splash-screen.fxml"));
|
||||
splashStage.show();
|
||||
initMainScreen(stage);
|
||||
Stage splashStage = showStartupSplashScreen();
|
||||
// Once the splash stage is hidden, show the main stage.
|
||||
splashStage.showingProperty().not().addListener((v, old, hidden) -> {
|
||||
if (hidden) {
|
||||
showMainScreen(stage);
|
||||
}
|
||||
splashStage.stateProperty().addListener((v, oldState, state) -> {
|
||||
if (state == SplashScreenStage.State.DONE) stage.show();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -26,24 +31,4 @@ public class PerfinApp extends Application {
|
|||
stage.setScene(SceneUtil.load("/main.fxml"));
|
||||
stage.setTitle("Perfin");
|
||||
}
|
||||
|
||||
private void showMainScreen(Stage stage) {
|
||||
System.out.println("Showing the main application.");
|
||||
// stage.setMaximized(true);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a startup "splash" screen for a short time.
|
||||
* @return The stage in which the splash screen is shown.
|
||||
*/
|
||||
private Stage showStartupSplashScreen() {
|
||||
Stage stage = new Stage();
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
stage.setScene(SceneUtil.load("/startup-splash-screen.fxml"));
|
||||
stage.setTitle("Loading");
|
||||
stage.setResizable(false);
|
||||
stage.show();
|
||||
return stage;
|
||||
}
|
||||
}
|
|
@ -1,36 +1,81 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.perfin.view.SplashScreenStage;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.APP_DIR;
|
||||
|
||||
/**
|
||||
* A controller for the application's splash screen that shows initially on
|
||||
* startup. While the splash screen is shown, we do any complicated loading
|
||||
* tasks so that the application starts properly, and give the user periodic
|
||||
* updates as we go.
|
||||
*/
|
||||
public class StartupSplashScreenController {
|
||||
@FXML
|
||||
public Label content;
|
||||
public BorderPane sceneRoot;
|
||||
@FXML
|
||||
public TextArea content;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
content.setText("Loading Perfin...");
|
||||
Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
printlnLater("Initializing application files...");
|
||||
if (!initAppDir()) {
|
||||
Platform.runLater(() -> getSplashStage().setError());
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> content.setText("Still loading..."));
|
||||
Thread.sleep(1000);
|
||||
printlnLater("Perfin initialized. Starting the app now.");
|
||||
Thread.sleep(2000);
|
||||
|
||||
Platform.runLater(() -> content.setText("Almost done..."));
|
||||
Thread.sleep(1000);
|
||||
|
||||
Platform.runLater(() -> content.setText("Done!"));
|
||||
Thread.sleep(500);
|
||||
|
||||
System.out.println("Closing splash screen...");
|
||||
Stage stage = (Stage) content.getScene().getWindow();
|
||||
Platform.runLater(stage::close);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
Platform.runLater(() -> getSplashStage().setDone());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
printlnLater("An error occurred while starting: " + e.getMessage() + "\nThe application will now exit.");
|
||||
Platform.runLater(() -> getSplashStage().setError());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void println(String text) {
|
||||
content.appendText(text + "\n");
|
||||
}
|
||||
|
||||
private void printlnLater(String text) {
|
||||
Platform.runLater(() -> println(text));
|
||||
}
|
||||
|
||||
private SplashScreenStage getSplashStage() {
|
||||
return (SplashScreenStage) sceneRoot.getScene().getWindow();
|
||||
}
|
||||
|
||||
private boolean initAppDir() {
|
||||
if (Files.notExists(APP_DIR)) {
|
||||
printlnLater(APP_DIR + " doesn't exist yet. Creating it now.");
|
||||
try {
|
||||
Files.createDirectory(APP_DIR);
|
||||
} catch (IOException e) {
|
||||
printlnLater("Could not create directory " + APP_DIR + "; " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else if (Files.exists(APP_DIR) && Files.isRegularFile(APP_DIR)) {
|
||||
printlnLater(APP_DIR + " is a file, when it should be a directory. Deleting it and creating new directory.");
|
||||
try {
|
||||
Files.delete(APP_DIR);
|
||||
Files.createDirectory(APP_DIR);
|
||||
} catch (IOException e) {
|
||||
printlnLater("Could not delete file and create directory " + APP_DIR + "; " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.andrewlalis.perfin.view;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
public class SplashScreenStage extends Stage {
|
||||
public enum State {
|
||||
LOADING,
|
||||
DONE,
|
||||
ERROR
|
||||
}
|
||||
|
||||
private final SimpleObjectProperty<State> stateProperty = new SimpleObjectProperty<>(State.LOADING);
|
||||
|
||||
public SplashScreenStage(String title, Scene scene) {
|
||||
setTitle(title);
|
||||
setResizable(false);
|
||||
initStyle(StageStyle.UNDECORATED);
|
||||
setScene(scene);
|
||||
}
|
||||
|
||||
public void setDone() {
|
||||
stateProperty.set(State.DONE);
|
||||
close();
|
||||
}
|
||||
|
||||
public void setError() {
|
||||
stateProperty.set(State.ERROR);
|
||||
close();
|
||||
}
|
||||
|
||||
public ObservableValue<State> stateProperty() {
|
||||
return this.stateProperty;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
|
||||
<BorderPane xmlns="http://javafx.com/javafx/17.0.2-ea"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="com.andrewlalis.perfin.control.MainController"
|
||||
minWidth="600.0" minHeight="400.0"
|
||||
>
|
||||
<Label>Main UI</Label>
|
||||
</AnchorPane>
|
||||
minWidth="600.0" minHeight="400.0">
|
||||
<top>
|
||||
<MenuBar BorderPane.alignment="CENTER">
|
||||
<Menu mnemonicParsing="false" text="File">
|
||||
<MenuItem mnemonicParsing="false" text="Close"/>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Edit">
|
||||
<MenuItem mnemonicParsing="false" text="Delete"/>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Help">
|
||||
<MenuItem mnemonicParsing="false" text="About"/>
|
||||
</Menu>
|
||||
</MenuBar>
|
||||
</top>
|
||||
</BorderPane>
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<BorderPane fx:id="sceneRoot" prefHeight="200" prefWidth="400" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.andrewlalis.perfin.control.StartupSplashScreenController">
|
||||
<padding><Insets top="10" bottom="10" left="10" right="10"/></padding>
|
||||
<top>
|
||||
<Label BorderPane.alignment="CENTER_LEFT" style="-fx-font-weight: bold; -fx-font-size: large;">Perfin is starting up...</Label>
|
||||
</top>
|
||||
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.StartupSplashScreenController"
|
||||
prefHeight="200" prefWidth="400">
|
||||
<Label fx:id="content" wrapText="true"/>
|
||||
</AnchorPane>
|
||||
<center>
|
||||
<TextArea fx:id="content" wrapText="true" editable="false" style="-fx-font-family: monospace" focusTraversable="false"/>
|
||||
</center>
|
||||
|
||||
</BorderPane>
|
||||
|
|
Loading…
Reference in New Issue