Page MenuHomedesp's stash

UIHelper.java
No OneTemporary

UIHelper.java

package me.despawningbone.arithtrain;
import java.io.IOException;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import javafx.animation.PauseTransition;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import me.despawningbone.arithtrain.ExpressionGenerator;
import me.despawningbone.arithtrain.Main;
import me.despawningbone.arithtrain.User;
/**
* The UIHelper class for providing GUI pages and elements.<br>
* It has been designed so that multiple stages can utilize the UIHelper with multiple UIHelper instances.
*
* @author despawningbone
*/
@SuppressWarnings({"unchecked", "rawtypes"}) //had to do this to drastically reduce code lines; besides i wont actually use the events in the functions anyways
public class UIHelper {
private Stage m;
private int h;
private int w;
/**
* Instantiates the UIHelper with the respective window stage, and its size.
* @param main the window stage the UIHelper should operate on
* @param width the width of the window stage, in pixels
* @param height the height of the window stage, in pixels
*/
public UIHelper(Stage main, int width, int height) {
m = main;
h = height;
w = width;
}
/**
* Displays a page according to the node given, adding suitable uniformed styling in the process.
* @param node the page node to be displayed
* @param darkenBg whether the background should be darkened or not
*/
public void display(Parent node, boolean darkenBg) {
Scene scene = new Scene(node, (double) w, (double) h);
scene.getStylesheets().add("resources/main.css");
node.setStyle("-fx-background-image: url(\"resources/bg" + (darkenBg ? "-darken.png" : ".jpg")
+ "\"); -fx-background-size: cover;");
this.m.setScene(scene);
}
/**
* A helper method for retrieving the 2 main menu buttons.
* @param op the name of the button to be retrieved
* @return the button object
*/
public Button getMenuButton(String op) { //wrapper
return this.getPicButton(op, (event) -> { //no need to unrelease it
if (op.equals("train")) {
this.showLogin(false);
} else if (op.equals("solve")) {
this.showSolver();
}
}, 0.2D, false);
}
/**
* Retrieves a generic PNG-based button from the program's resources directory, and assigns generic reaction listeners to the button.
* @param op the name of the button to be retrieved
* @param func the function to be executed on mouse press (or enter if the defaultButton is true)
* @param scaleFactor how large the button should appear
* @param defaultButton whether the button should be triggered on pressing enter anywhere on the GUI.
* @return the retrieved button
*/
private Button getPicButton(String op, EventHandler func, double scaleFactor, boolean defaultButton) {
Button b = new Button();
b.setGraphic(this.getImage(op, "normal", scaleFactor));
b.setStyle("-fx-background-color: transparent;");
b.setOnMouseEntered((event) -> {
b.setGraphic(this.getImage(op, "hover", scaleFactor));
});
b.setOnDragEntered((event) -> {
b.setGraphic(this.getImage(op, "hover", scaleFactor));
});
b.setOnMouseExited((event) -> {
b.setGraphic(this.getImage(op, "normal", scaleFactor));
});
b.setOnDragExited((event) -> {
b.setGraphic(this.getImage(op, "normal", scaleFactor));
});
b.setOnMousePressed((event) -> {
b.setGraphic(this.getImage(op, "pressed", scaleFactor));
});
b.setOnMouseReleased(func);
b.setOnMouseClicked((event) -> {
b.setGraphic(this.getImage(op, "hover", scaleFactor));
});
if (defaultButton) {
b.setDefaultButton(true);
b.setOnAction(func);
}
return b;
}
/**
* Constructs a generic CSS-based button, and assigns generic reaction listeners to the button.
* @param name the name of the CSS button
* @param style the CSS styling for the main button
* @param pressedStyle the CSS styling for when the button is pressed
* @param hoverStyle the CSS styling for when the button is hovered
* @param func the function to be executed on mouse press (or enter if the defaultButton is true)
* @param defaultButton whether the button should be triggered on pressing enter anywhere on the GUI.
* @return the retrieved button
*/
private Button getCSSButton(String name, String style, String pressedStyle, String hoverStyle, EventHandler func,
boolean defaultButton) {
Button b = new Button(name);
b.setStyle(style);
b.setOnMouseEntered((event) -> {
b.setStyle(hoverStyle);
});
b.setOnDragEntered((event) -> {
b.setStyle(hoverStyle);
});
b.setOnMouseExited((event) -> {
b.setStyle(style);
});
b.setOnDragExited((event) -> {
b.setStyle(style);
});
b.setOnMousePressed((event) -> {
b.setStyle(pressedStyle);
});
b.setOnMouseReleased(func);
b.setOnMouseClicked((event) -> {
b.setStyle(hoverStyle);
});
if (defaultButton) {
b.setDefaultButton(true);
b.setOnAction(func);
}
return b;
}
/**
* Gets an image from the program's resource directory with pre-defined names.
* @param op the name of the image
* @param type the type of the image (hover, normal, pressed etc)
* @param scaleFactor how large the image should appear
* @return the retrieved image
*/
private ImageView getImage(String op, String type, double scaleFactor) {
ImageView img = new ImageView(new Image("resources/" + op + "_" + type + ".png"));
img.setFitHeight((double) h * scaleFactor);
img.setPreserveRatio(true);
return img;
}
/**
* Constructs a uniform footer for GUI pages that needs a back button.
* @param func a custom function to be executed on pressing the back button, or null for returning to the main menu.
* @return the footer as a StackPane
*/
public StackPane getFooter(EventHandler func) {
StackPane footer = new StackPane();
Rectangle box = new Rectangle(0.0D, 0.0D, (double) w, (double) (h / 8)); //h isnt / 10 to get all of available space
box.setStyle("-fx-fill: rgba(0, 0, 0, 0.5);");
Button back = this.getPicButton("back", func == null ? (event) -> {
this.showMainMenu();
} : func, 0.1D, false);
back.setCancelButton(true);
back.setOnAction(func == null ? (event) -> {
this.showMainMenu();
} : func);
footer.getChildren().add(box);
footer.getChildren().add(back);
footer.setPrefSize((double) w, (double) (h / 10));
footer.setAlignment(Pos.CENTER_LEFT);
StackPane.setAlignment(footer, Pos.BOTTOM_CENTER);
return footer;
}
/**
* Constructs a text field with the icon specified, according to the size instructed.
* @param name the name of the text field, this will be shown when the text field is not focused
* @param img the icon name in the resources directory of the program
* @param width the specified field width
* @param height the specified field height
* @return the constructed TextField
*/
public TextField getTextField(String name, String img, double width, double height) {
Object f = img != null && img.equals("pw") ? new PasswordField() : new TextField(); //exception for passwords
((TextField) f).setPromptText(name);
((TextField) f).setMaxSize(width, height);
if (img != null) {
((TextField) f).setPadding(new Insets(0.0D, 0.0D, 0.0D, (double) (w / 30)));
}
((TextField) f).setStyle("-fx-background-insets: -0.5em;"
+ (img == null ? ""
: " -fx-background-image:url(\"resources/" + img
+ ".png\"); -fx-background-size: 1.8em; -fx-background-repeat: no-repeat; -fx-background-position: left center;")
+ "-fx-background-radius: 5em; -fx-background-color: #fff; -fx-font-size: 1.8em;");
return (TextField) f;
}
//END HELPER METHODS
//START PAGES
/**
* Shows the main menu page.
*/
public void showMainMenu() {
VBox menu = new VBox();
menu.getChildren().add(this.getMenuButton("train"));
menu.getChildren().add(this.getMenuButton("solve"));
menu.setAlignment(Pos.CENTER);
this.display(menu, false);
}
/**
* Shows the login or the register page.
* @param reg true to show the register page, and false for the login page.
*/
public void showLogin(boolean reg) {
BorderPane pane = new BorderPane();
VBox box = new VBox();
box.setStyle(
"-fx-background-color: linear-gradient(to bottom right, rgba(50, 120, 134, 0.7), rgba(53, 204, 147, 0.9)); -fx-background-radius: 1.5em; -fx-padding: 2em;");
box.setAlignment(Pos.CENTER);
box.setSpacing((double) (h / 20));
HBox header = new HBox();
header.setMaxSize((double) (w / 2), (double) (h / 10));
header.setAlignment(Pos.CENTER_LEFT);
header.setSpacing((double) (w / 60));
Text name = new Text(reg ? "Register" : "Sign In");
name.setStyle("-fx-font-weight: bold; -fx-font-size: 2.5em; -fx-fill: #fff");
name.setFont(Font.font((String) null, FontWeight.EXTRA_BOLD, 25.0D));
TextField user = this.getTextField("Username", "user", (double) (w / 3), (double) (h / 10));
TextField password = this.getTextField("Password", "pw", (double) (w / 3), (double) (h / 10));
TextField reEnter = this.getTextField("Confirm Password", "pw", (double) (w / 3), (double) (h / 10));
HBox buttons = new HBox();
Button signIn = this.getCSSButton(reg ? "Register" : "Sign In",
"-fx-background-color: rgba(20, 20, 20, 0.7); -fx-text-fill: #fff; -fx-background-radius: 0.5em;",
"-fx-background-color: rgba(20, 20, 20, 0.85); -fx-text-fill: #fff; -fx-background-radius: 0.5em;",
"-fx-background-color: rgba(20, 20, 20, 0.55); -fx-text-fill: #fff; -fx-background-radius: 0.5em;",
(event) -> {
try {
if (reg) {
this.showTrainer(User.register(user.getText(), password.getText(), reEnter.getText()));
} else {
this.showTrainer(new User(user.getText(), password.getText()));
}
} catch (IllegalArgumentException | NoSuchElementException | IOException e) {
Text error = new Text(e instanceof IOException ? "Something went wrong: " + e.getMessage()
: e.getMessage());
error.setStyle("-fx-font-size: 1em; -fx-fill: #d00");
error.setWrappingWidth((double) w / 3.5D);
if (header.getChildren().size() == 1) {
header.getChildren().add(error);
} else {
header.getChildren().set(1, error);
}
}
}, true);
signIn.setPrefSize((double) (w / 8), (double) (h / 15));
signIn.setMinSize((double) (w / 8), (double) (h / 15));
header.getChildren().add(name);
box.getChildren().add(header);
box.getChildren().add(user);
box.getChildren().add(password);
buttons.getChildren().add(signIn);
buttons.setAlignment(Pos.CENTER);
buttons.setSpacing((double) (w / 12));
if (!reg) {
Button regButton = this.getCSSButton("Create Account",
"-fx-background-color: transparent; -fx-text-fill: #fff;",
"-fx-background-color: transparent; -fx-text-fill: #aaa; -fx-underline: true",
"-fx-background-color: transparent; -fx-text-fill: #fff; -fx-underline: true", (event) -> {
this.showLogin(true);
}, false);
regButton.setPrefSize((double) (w / 8), (double) (h / 10));
buttons.getChildren().add(regButton);
} else {
box.getChildren().add(reEnter);
}
box.getChildren().add(buttons);
box.setMaxSize((double) (w / 2), (double) (h / 2));
pane.setCenter(box);
pane.setBottom(reg ? this.getFooter((event) -> {
this.showLogin(false);
}) : this.getFooter((EventHandler) null));
this.display(pane, true);
}
private String currentResult;
/**
* Shows the trainer page.
* @param user the logged in user that the trainer page should operate in context of
*/
public void showTrainer(User user) {
BorderPane pane = new BorderPane();
pane.setPadding(new Insets((double) (h / 30), 0.0D, 0.0D, 0.0D));
HBox top = new HBox();
Text level = new Text("Level");
level.setStyle("-fx-font-weight: bold; -fx-font-size: 2em; -fx-fill: #fff");
StackPane progress = new StackPane();
progress.setMaxSize((double) w * 0.8D, (double) (h / 10));
ProgressBar bar = new ProgressBar();
bar.setProgress(user.getNextLevelPercent());
bar.setPrefSize((double) w * 0.8D, (double) (h / 10));
bar.layout();
int initLv = user.getLevel();
Label fromLevel = new Label(String.valueOf(initLv));
fromLevel.setPrefSize((double) (h / 10), (double) (h / 10));
Label toLevel = new Label(String.valueOf(initLv + 1));
toLevel.setMaxSize((double) (h / 10), (double) (h / 10));
progress.getChildren().add(bar);
progress.getChildren().add(fromLevel);
toLevel.setAlignment(Pos.CENTER);
toLevel.setStyle(
"-fx-background-radius: 5em; -fx-background-color: #fff; -fx-font-size: 2em; -fx-background-insets: 1em;");
fromLevel.setAlignment(Pos.CENTER);
fromLevel.setStyle(
"-fx-background-radius: 5em; -fx-background-color: #fff; -fx-font-size: 2em; -fx-background-insets: 1em;");
progress.getChildren().add(toLevel);
StackPane.setAlignment(fromLevel, Pos.CENTER_LEFT);
StackPane.setAlignment(toLevel, Pos.CENTER_RIGHT);
DecimalFormat df = new DecimalFormat("#" + (Main.precision > 0 ? "." + String.join("", Collections.nCopies(Main.precision, "#")) : ""));
df.setRoundingMode(RoundingMode.HALF_UP);
PauseTransition wait = new PauseTransition(Duration.seconds(1.0D));
VBox center = new VBox();
StackPane exp = new StackPane();
List<String> gen = user.getCurrentGenerator().generate();
Label expContent = new Label(gen.get(0));
currentResult = df.format(Double.parseDouble(gen.get(gen.size() - 1)));
exp.getStyleClass().add("exp");
exp.setMaxSize((double) (w / 2), (double) (h / 4));
exp.setFocusTraversable(false);
expContent.setPrefSize((double) (w / 2), (double) (h / 4));
String expStyle = "-fx-padding: 0.5em; -fx-font-weight: bold; -fx-text-fill: #fff; -fx-alignment: center;"
+ "-fx-background-radius: 1.2em; -fx-background-color:linear-gradient(to bottom right, rgba(36, 224, 216, 0.6), rgba(62, 106, 169, 0.8));";
expContent.setStyle(expStyle + " -fx-font-size: 3em;");
exp.getChildren().add(expContent);
exp.setAlignment(Pos.CENTER);
HBox hB = new HBox();
hB.setPrefSize((double) w, (double) (h / 10));
hB.setPadding(new Insets(0.0D, 0.0D, 0.0D, (double) (w / 80))); // to offset the inflated textfield
TextField input = this.getTextField("Answer", (String) null, (double) (w / 2), (double) (h / 12));
input.setPrefSize((double) w, (double) (h / 10));
StackPane footer = this.getFooter((event) -> {
try {
user.save();
} catch (IOException e) {
e.printStackTrace();
}
this.showMainMenu();
});
Label score = new Label("Current Score: " + user.getScore());
score.setPadding(new Insets(0.0D, (double) (w / 50), 0.0D, 0.0D));
score.setStyle("-fx-font-weight: bold; -fx-font-size: 2.5em; -fx-text-fill: #fff;");
footer.getChildren().add(score);
StackPane.setAlignment(score, Pos.CENTER_RIGHT);
Button send = this.getPicButton("send", (event) -> {
int lv = Integer.parseInt(fromLevel.getText());
System.out.println("Expression generated: " + expContent.getText() + "; Expected: " + currentResult + ", Answer:" + input.getText());
int newLv = 0;
try {
if (!currentResult.equals(df.format(Double.parseDouble(input.getText())))) { //match with configured precision or preciser
throw new NumberFormatException(); //pass to exception handler
}
expContent.setText("Correct!");
newLv = user.updateScore(true);
score.setText("Current Score: " + user.getScore());
} catch (NumberFormatException e) {
newLv = user.updateScore(false);
expContent.setText("Incorrect! The answer was: " + currentResult);
expContent.setStyle(expStyle + " -fx-font-size: 2em;");
}
input.setText("");
((Button) event.getSource()).setDisable(true); //so people cant spam it when its in result mode; have to cast coz even tho send is definitely initialized it thinks it isnt
bar.setProgress(user.getNextLevelPercent());
if (lv != newLv) {
fromLevel.setText(String.valueOf(newLv));
toLevel.setText(String.valueOf(newLv + 1));
if (lv < newLv) {
expContent.setText("Leveled up!");
} else if (lv > newLv) {
expContent.setText("Oops! You dropped a level.");
}
}
wait.setOnFinished((e) -> {
((Button) event.getSource()).setDisable(false);
List<String> newGen = user.getCurrentGenerator().generate();
expContent.setText(newGen.get(0));
currentResult = df.format(Double.parseDouble(newGen.get(newGen.size() - 1)));
expContent.setStyle(expStyle + " -fx-font-size: 3em;");
});
wait.play();
}, 0.1D, true);
hB.getChildren().add(input);
hB.getChildren().add(send);
hB.setSpacing((double) (w / 60));
hB.setAlignment(Pos.CENTER);
center.getChildren().add(exp);
center.getChildren().add(hB);
center.setSpacing((double) (h / 12));
center.setMaxSize((double) (w / 2), (double) (h / 2));
center.setAlignment(Pos.CENTER);
top.getChildren().add(level);
top.getChildren().add(progress);
top.setSpacing((double) (w / 40));
top.setAlignment(Pos.CENTER);
pane.setTop(top);
pane.setCenter(center);
pane.setBottom(footer);
this.display(pane, true);
}
/**
* Shows the solver page.
*/
public void showSolver() {
BorderPane pane = new BorderPane();
VBox vB = new VBox();
vB.setSpacing((double) (h / 20));
vB.setStyle("-fx-padding: 4em;");
HBox hB = new HBox();
hB.setPrefSize((double) w, (double) (h / 10));
TextField input = this.getTextField("Enter your expression here", (String) null, (double) w * 0.79D,
(double) (h / 10));
input.setPrefSize((double) w, (double) (h / 10));
TextArea result = new TextArea();
result.setEditable(false); //so no input is done in the steps field
result.setPrefSize((double) w, (double) h);
result.setFocusTraversable(false);
String resultStyle = "-fx-focus-color: transparent; -fx-faint-focus-color: transparent; -fx-border-color: transparent;-fx-padding: 0.5em; -fx-font-size: 1.5em; -fx-font-weight: 400;-fx-opacity: 0.8; -fx-background-radius: 1.2em; -fx-background-color:";
result.setStyle(resultStyle + "rgba(255, 255, 255, 0.7)");
Button send = this.getPicButton("send", (event) -> {
String steps;
try {
steps = String.join("\n= ", ExpressionGenerator.evaluateWithSteps(input.getText()));
result.setStyle(resultStyle + "rgba(255, 255, 255, 0.7)");
} catch (IllegalArgumentException e) {
//shake the result box?
result.setStyle(resultStyle + "rgba(255, 146, 146, 0.7);");
steps = e.getMessage();
}
result.setText(steps);
}, 0.1D, true);
hB.setSpacing((double) (w / 40));
hB.getChildren().add(input);
hB.getChildren().add(send);
hB.setAlignment(Pos.CENTER);
vB.getChildren().add(hB);
vB.getChildren().add(result);
pane.setCenter(vB);
pane.setBottom(this.getFooter((EventHandler) null));
this.display(pane, true);
}
}

File Metadata

Mime Type
text/x-java
Expires
Wed, Oct 8, 11:28 PM (57 m, 4 s)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
88/51/4d84e0fd8538171cb968f16bb223

Event Timeline