initial commit
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
.kotlin
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
Generated
+3
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>cz.jzitnik</groupId>
|
||||
<artifactId>jecnaclient</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<!-- Make sure this matches your exact package and class name -->
|
||||
<mainClass>cz.jzitnik.Main</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- JavaFX Controls -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>21.0.1</version> <!-- Use the version matching your JDK -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>21.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AtlantaFX Theme -->
|
||||
<dependency>
|
||||
<groupId>io.github.mkpaz</groupId>
|
||||
<artifactId>atlantafx-base</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Reflections -->
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,50 @@
|
||||
package cz.jzitnik;
|
||||
|
||||
import atlantafx.base.theme.PrimerDark;
|
||||
import cz.jzitnik.state.StateManager;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class Main extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws Exception {
|
||||
Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet());
|
||||
|
||||
// 1. Initialize State Manager
|
||||
// This will find all @State annotated classes inside the cz.jzitnik package and create singletons
|
||||
StateManager.getInstance().initialize("cz.jzitnik");
|
||||
|
||||
// 2. Set up routing container (this is our root)
|
||||
StackPane root = new StackPane();
|
||||
root.setStyle("-fx-background-color: -color-bg-default;");
|
||||
|
||||
// Ensure contents never bleed out and cause weird window scaling
|
||||
Rectangle clip = new Rectangle();
|
||||
clip.widthProperty().bind(root.widthProperty());
|
||||
clip.heightProperty().bind(root.heightProperty());
|
||||
root.setClip(clip);
|
||||
|
||||
// 3. Initialize Router
|
||||
cz.jzitnik.router.Router router = cz.jzitnik.router.Router.getInstance();
|
||||
router.setRootContainer(root);
|
||||
router.registerRoute("/login", cz.jzitnik.controllers.LoginController.class);
|
||||
router.registerRoute("/home", cz.jzitnik.controllers.HomeController.class);
|
||||
|
||||
// Initial screen
|
||||
router.navigate("/login");
|
||||
|
||||
// 4. Display
|
||||
Scene scene = new Scene(root, 800, 600);
|
||||
stage.setTitle("JecnaClient");
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cz.jzitnik.controllers;
|
||||
|
||||
import cz.jzitnik.router.Routable;
|
||||
import cz.jzitnik.router.Route;
|
||||
import cz.jzitnik.router.Router;
|
||||
import cz.jzitnik.state.AppState;
|
||||
import cz.jzitnik.state.InjectState;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Route(fxml = "/home.fxml")
|
||||
public class HomeController implements Routable {
|
||||
|
||||
@InjectState
|
||||
private AppState appState;
|
||||
|
||||
@FXML
|
||||
private Label welcomeLabel;
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> props) {
|
||||
// Because of dependency injection, appState is already fully populated
|
||||
String username = appState.getUsername();
|
||||
if (username == null) username = "User";
|
||||
|
||||
welcomeLabel.setText("Welcome, " + username + "!");
|
||||
System.out.println("Global Auth Token is: " + appState.getToken());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void onLogoutClick() {
|
||||
// Clear global state
|
||||
appState.clear();
|
||||
|
||||
// Redirect
|
||||
Router.getInstance().navigate("/login");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cz.jzitnik.controllers;
|
||||
|
||||
import cz.jzitnik.router.Routable;
|
||||
import cz.jzitnik.router.Route;
|
||||
import cz.jzitnik.router.Router;
|
||||
import cz.jzitnik.state.AppState;
|
||||
import cz.jzitnik.state.InjectState;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Route(fxml = "/login.fxml")
|
||||
public class LoginController implements Routable {
|
||||
|
||||
@InjectState
|
||||
private AppState appState;
|
||||
|
||||
@FXML
|
||||
private TextField usernameField;
|
||||
|
||||
@FXML
|
||||
private PasswordField passwordField;
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> props) {
|
||||
// Here you can use props if the screen was opened with them
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void onLoginClick() {
|
||||
String username = usernameField.getText();
|
||||
|
||||
// Save the username globally into the app state
|
||||
if (username != null && !username.isEmpty()) {
|
||||
appState.setUsername(username);
|
||||
appState.setToken("dummy_token_123");
|
||||
} else {
|
||||
appState.setUsername("Guest");
|
||||
}
|
||||
|
||||
// Redirect to the home screen (no longer need to pass username in props, it's global now!)
|
||||
Router.getInstance().navigate("/home");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cz.jzitnik.router;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface for screen controllers that can receive properties when navigated to.
|
||||
*/
|
||||
public interface Routable {
|
||||
default void onNavigate(Map<String, Object> props) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.router;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to map a controller class to an FXML file.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Route {
|
||||
String fxml();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cz.jzitnik.router;
|
||||
|
||||
import cz.jzitnik.state.StateManager;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Scalable API for managing screens.
|
||||
*/
|
||||
public class Router {
|
||||
private static Router instance;
|
||||
private Pane rootContainer;
|
||||
private final Map<String, Class<?>> routes = new HashMap<>();
|
||||
|
||||
private Router() {}
|
||||
|
||||
public static Router getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new Router();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void setRootContainer(Pane rootContainer) {
|
||||
this.rootContainer = rootContainer;
|
||||
}
|
||||
|
||||
public void registerRoute(String path, Class<?> controllerClass) {
|
||||
if (!controllerClass.isAnnotationPresent(Route.class)) {
|
||||
throw new IllegalArgumentException("Controller " + controllerClass.getName() + " must be annotated with @Route");
|
||||
}
|
||||
routes.put(path, controllerClass);
|
||||
}
|
||||
|
||||
public void navigate(String path) {
|
||||
navigate(path, new HashMap<>());
|
||||
}
|
||||
|
||||
public void navigate(String path, Map<String, Object> props) {
|
||||
if (rootContainer == null) {
|
||||
throw new IllegalStateException("Root container not set in Router.");
|
||||
}
|
||||
|
||||
Class<?> controllerClass = routes.get(path);
|
||||
if (controllerClass == null) {
|
||||
throw new IllegalArgumentException("Route not found: " + path);
|
||||
}
|
||||
|
||||
Route routeAnnotation = controllerClass.getAnnotation(Route.class);
|
||||
String fxmlPath = routeAnnotation.fxml();
|
||||
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(controllerClass.getResource(fxmlPath));
|
||||
Parent view = loader.load();
|
||||
Object controller = loader.getController();
|
||||
|
||||
// Inject global state into the controller if it uses @InjectState
|
||||
StateManager.getInstance().injectStates(controller);
|
||||
|
||||
if (controller instanceof Routable routable) {
|
||||
routable.onNavigate(props);
|
||||
}
|
||||
|
||||
rootContainer.getChildren().clear();
|
||||
rootContainer.getChildren().add(view);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load screen for route: " + path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cz.jzitnik.state;
|
||||
|
||||
@State
|
||||
public class AppState {
|
||||
|
||||
private String username;
|
||||
private String token; // Example for some auth token
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return username != null && !username.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.username = null;
|
||||
this.token = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cz.jzitnik.state;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a singleton state object into a controller field.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectState {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.state;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a class as a managed state object.
|
||||
* The StateManager will instantiate and hold a singleton of this class.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface State {
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cz.jzitnik.state;
|
||||
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Manages @State annotated classes, storing them as singletons and injecting them.
|
||||
*/
|
||||
public class StateManager {
|
||||
|
||||
private static StateManager instance;
|
||||
private final Map<Class<?>, Object> states = new HashMap<>();
|
||||
|
||||
private StateManager() {
|
||||
}
|
||||
|
||||
public static StateManager getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new StateManager();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the specified base package for @State annotated classes and instantiates them.
|
||||
* @param basePackage the base package to scan (e.g. "cz.jzitnik")
|
||||
*/
|
||||
public void initialize(String basePackage) {
|
||||
Reflections reflections = new Reflections(basePackage);
|
||||
Set<Class<?>> stateClasses = reflections.getTypesAnnotatedWith(State.class);
|
||||
|
||||
for (Class<?> stateClass : stateClasses) {
|
||||
try {
|
||||
Object stateInstance = stateClass.getDeclaredConstructor().newInstance();
|
||||
states.put(stateClass, stateInstance);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to instantiate state class: " + stateClass.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects initialized states into fields annotated with @InjectState in the given target object.
|
||||
* @param target the controller or object to inject state into
|
||||
*/
|
||||
public void injectStates(Object target) {
|
||||
if (target == null) return;
|
||||
|
||||
Class<?> targetClass = target.getClass();
|
||||
for (Field field : targetClass.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(InjectState.class)) {
|
||||
Class<?> fieldType = field.getType();
|
||||
Object stateInstance = states.get(fieldType);
|
||||
|
||||
if (stateInstance == null) {
|
||||
throw new RuntimeException("No @State found for type: " + fieldType.getName() + " in " + targetClass.getName());
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
field.set(target, stateInstance);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Failed to inject state into field: " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cz.jzitnik.ui;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.scene.effect.BlendMode;
|
||||
import javafx.scene.effect.GaussianBlur;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* Reusable animated background component.
|
||||
*/
|
||||
public class AuroraBackground extends StackPane {
|
||||
|
||||
public AuroraBackground() {
|
||||
// LOWER OPACITY: Dropped from 0.3 to 0.1 for a subtle background feel
|
||||
Color color1 = Color.web("#00FFFF", 0.10); // Cyan
|
||||
Color color2 = Color.web("#FF00FF", 0.10); // Magenta
|
||||
Color color3 = Color.web("#7D3CFF", 0.10); // Deep Purple
|
||||
|
||||
// MASSIVE BLOBS: Increased radius so they fill large screens beautifully
|
||||
Circle blob1 = new Circle(400, color1);
|
||||
Circle blob2 = new Circle(350, color2);
|
||||
Circle blob3 = new Circle(450, color3);
|
||||
|
||||
// HEAVY BLUR: Increased from 100 to 200 for maximum softness
|
||||
GaussianBlur blur = new GaussianBlur(200);
|
||||
blob1.setEffect(blur);
|
||||
blob2.setEffect(blur);
|
||||
blob3.setEffect(blur);
|
||||
|
||||
blob1.setBlendMode(BlendMode.ADD);
|
||||
blob2.setBlendMode(BlendMode.ADD);
|
||||
blob3.setBlendMode(BlendMode.ADD);
|
||||
|
||||
// Because this is a StackPane, all circles start perfectly centered
|
||||
getChildren().addAll(blob1, blob2, blob3);
|
||||
|
||||
// Animate them drifting away from the center using translation
|
||||
setupAnimation(blob1, -150, -100, 150, 100, 20);
|
||||
setupAnimation(blob2, 150, 150, -150, -150, 25);
|
||||
setupAnimation(blob3, 0, -200, 0, 200, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates a node shifting back and forth from a center point.
|
||||
*/
|
||||
private void setupAnimation(Circle node, double startX, double startY, double endX, double endY, int durationSeconds) {
|
||||
Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(node.translateXProperty(), startX),
|
||||
new KeyValue(node.translateYProperty(), startY)
|
||||
),
|
||||
new KeyFrame(Duration.seconds(durationSeconds),
|
||||
new KeyValue(node.translateXProperty(), endX),
|
||||
new KeyValue(node.translateYProperty(), endY)
|
||||
)
|
||||
);
|
||||
|
||||
timeline.setAutoReverse(true);
|
||||
timeline.setCycleCount(Animation.INDEFINITE);
|
||||
timeline.play();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
|
||||
<!-- Home screen with a solid transparent/default background (NO aurora background here) -->
|
||||
<StackPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cz.jzitnik.controllers.HomeController">
|
||||
|
||||
<VBox alignment="CENTER" spacing="15.0" maxWidth="350.0"
|
||||
style="-fx-background-color: transparent;">
|
||||
|
||||
<padding>
|
||||
<Insets bottom="40.0" left="40.0" right="40.0" top="40.0"/>
|
||||
</padding>
|
||||
|
||||
<Label text="Home Screen" styleClass="title-1"/>
|
||||
<Label fx:id="welcomeLabel" styleClass="text-muted"/>
|
||||
|
||||
<Button text="Logout" onAction="#onLogoutClick" maxWidth="Infinity" styleClass="danger"/>
|
||||
|
||||
</VBox>
|
||||
</StackPane>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.PasswordField?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import cz.jzitnik.ui.AuroraBackground?>
|
||||
|
||||
<StackPane xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cz.jzitnik.controllers.LoginController">
|
||||
|
||||
<!-- Screen Specific Background -->
|
||||
<AuroraBackground />
|
||||
|
||||
<!-- Actual Content -->
|
||||
<VBox alignment="CENTER" spacing="15.0" maxWidth="350.0"
|
||||
style="-fx-background-color: transparent;">
|
||||
|
||||
<padding>
|
||||
<Insets bottom="40.0" left="40.0" right="40.0" top="40.0"/>
|
||||
</padding>
|
||||
|
||||
<VBox alignment="CENTER" spacing="5.0">
|
||||
<Label text="Sign In" styleClass="title-2"/>
|
||||
<Label text="Welcome back to the portal" styleClass="text-muted"/>
|
||||
</VBox>
|
||||
|
||||
<Label prefHeight="15.0"/>
|
||||
<TextField fx:id="usernameField" promptText="Username or Email"/>
|
||||
<PasswordField fx:id="passwordField" promptText="Password"/>
|
||||
|
||||
<Button text="Login" onAction="#onLoginClick" maxWidth="Infinity" styleClass="accent"/>
|
||||
|
||||
</VBox>
|
||||
</StackPane>
|
||||
Reference in New Issue
Block a user