Skip to content

Commit

Permalink
Implement Duke Level 10
Browse files Browse the repository at this point in the history
Add a GUI using JavaFX.
Remove text-based UI.
  • Loading branch information
szelongq committed Sep 2, 2021
1 parent d715bd1 commit f058d9d
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 114 deletions.
17 changes: 16 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ repositories {
dependencies {
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'

String javaFxVersion = '11'

implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
}

test {
Expand All @@ -29,7 +44,7 @@ test {
}

application {
mainClassName = "duke.Duke"
mainClassName = "duke.Launcher"
}

shadowJar {
Expand Down
Binary file removed config/checkstyle/.checkstyle.xml.swp
Binary file not shown.
Binary file added src/main/Resources/Images/Cecilia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/main/Resources/Images/ItemShopNPC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions src/main/Resources/view/DialogBox.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>

<fx:root alignment="TOP_RIGHT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefWidth="400.0" style="-fx-background-color: aliceblue; -fx-border-color: aquamarine; -fx-padding: 10;" type="javafx.scene.layout.HBox" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="dialog" text="Label" wrapText="true">
<font>
<Font name="Chalkboard" size="13.0" />
</font>
<padding>
<Insets left="5.0" right="5.0" />
</padding></Label>
<StackPane style="-fx-background-color: cadetblue;">
<children>
<ImageView fx:id="displayPicture" fitHeight="99.0" fitWidth="99.0" pickOnBounds="true" preserveRatio="true" />
</children>
</StackPane>
</children>
<padding>
<Insets bottom="15.0" left="5.0" right="5.0" top="15.0" />
</padding>
</fx:root>
19 changes: 19 additions & 0 deletions src/main/Resources/view/MainWindow.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="duke.MainWindow">
<children>
<TextField fx:id="userInput" layoutY="558.0" onAction="#handleUserInput" prefHeight="41.0" prefWidth="324.0" AnchorPane.bottomAnchor="1.0" />
<Button fx:id="sendButton" layoutX="324.0" layoutY="558.0" mnemonicParsing="false" onAction="#handleUserInput" prefHeight="41.0" prefWidth="76.0" text="Send" />
<ScrollPane fx:id="scrollPane" hbarPolicy="NEVER" hvalue="1.0" prefHeight="557.0" prefWidth="400.0" vvalue="1.0">
<content>
<VBox fx:id="dialogContainer" prefHeight="552.0" prefWidth="388.0" style="-fx-background-color: gray;" />
</content>
</ScrollPane>
</children>
</AnchorPane>
61 changes: 61 additions & 0 deletions src/main/java/duke/DialogBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package duke;

import java.io.IOException;
import java.util.Collections;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;

/**
* An example of a custom control using FXML.
* This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
* containing text from the speaker.
*/
public class DialogBox extends HBox {
@FXML
private Label dialog;
@FXML
private ImageView displayPicture;

private DialogBox(String text, Image img) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
fxmlLoader.setController(this);
fxmlLoader.setRoot(this);
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}

dialog.setText(text);
displayPicture.setImage(img);
}

/**
* Flips the dialog box such that the ImageView is on the left and text on the right.
*/
private void flip() {
ObservableList<Node> tmp = FXCollections.observableArrayList(this.getChildren());
Collections.reverse(tmp);
getChildren().setAll(tmp);
setAlignment(Pos.TOP_LEFT);
}

public static DialogBox getUserDialog(String text, Image img) {
return new DialogBox(text, img);
}

public static DialogBox getDukeDialog(String text, Image img) {
var db = new DialogBox(text, img);
db.flip();
return db;
}
}
94 changes: 52 additions & 42 deletions src/main/java/duke/Duke.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package duke;

import java.util.Scanner;

public class Duke {

private Ui ui;
Expand All @@ -10,6 +8,14 @@ public class Duke {

/**
* Class constructor.
* Sets save file path to "./data/duke.txt" by default.
*/
public Duke() {
this("data/duke.txt");
}

/**
* Class constructor specifying the save file's path.
*
* @param filepath The file path to the save file for Duke.
*/
Expand All @@ -29,33 +35,42 @@ public Duke(String filepath) {
}

/**
* Starts the operation of Duke task manager.
* Returns the message informing that Duke has started up.
*
* @return A String object containing the start-up message.
*/
public void run() {
ui.printStartInteractionsMessage();

Scanner scanner = new Scanner(System.in);
String userInput;

do {
ui.printWaitingUserInput();
userInput = scanner.nextLine();
public String getStartUpMessage() {
return ui.printStartInteractionsMessage();
}

try {
executeCommand(userInput);
} catch (DukeException e) {
ui.printErrorMessage(e.getMessage());
}
// Duke will keep accepting user input until user inputs "bye",
// which will lead to executeCommand() exiting the program.
} while (true);
/**
* Returns a printout of loaded tasks from the save file, if any.
*
* @return A String object containing a printout of the loaded tasks
* or an empty String object if no tasks were loaded.
*/
public String getTasksLoadedMessage() {
if (tasks.getSize() > 0) {
return ui.printLoadTasks(tasks);
} else {
return "";
}
}

public static void main(String[] args) {
new Duke("data/duke.txt").run();
/**
* Gets the response from Duke to the command inputted by the user.
*
* @return A String object containing Duke's response.
*/
public String getResponse(String userInput) {
try {
return executeCommand(userInput);
} catch (DukeException e) {
return ui.printErrorMessage(e.getMessage());
}
}

private void executeCommand(String userInput) throws DukeException {
private String executeCommand(String userInput) throws DukeException {
// First, extract the command type inputted by the user.
// parseCommandType() will throw an UnsupportedOperationException if
// no valid command types was inputted by the user.
Expand All @@ -66,48 +81,43 @@ private void executeCommand(String userInput) throws DukeException {
// if no valid further input is entered by the user.
switch (commandType) {
case EXIT:
ui.printExitMessage();
System.exit(0);
break;
case LIST:
ui.printTaskList(tasks);
break;
return ui.printTaskList(tasks);
case ADD_TASK:
addNewTask(Parser.parseNewTask(userInput));
break;
return addNewTask(Parser.parseNewTask(userInput));
case COMPLETE_TASK:
completeTask(Parser.parseTaskNum(userInput));
break;
return completeTask(Parser.parseTaskNum(userInput));
case DELETE_TASK:
deleteTask(Parser.parseTaskNum(userInput));
break;
return deleteTask(Parser.parseTaskNum(userInput));
case FIND_TASK:
findTask(Parser.parseSearchSubject(userInput));
break;
return findTask(Parser.parseSearchSubject(userInput));
default:
throw new UnsupportedOperationException(); // Error
}
return "";
}

private void addNewTask(Task newTask) {
private String addNewTask(Task newTask) {
tasks.addTask(newTask);
storage.saveTasks(tasks);
ui.printAddTask(tasks, newTask);
return ui.printAddTask(tasks, newTask);
}

private void completeTask(int taskNum) {
private String completeTask(int taskNum) {
tasks.completeTask(taskNum);
storage.saveTasks(tasks);
ui.printCompleteTask(tasks.getTask(taskNum));
return ui.printCompleteTask(tasks.getTask(taskNum));
}

private void deleteTask(int taskNum) {
private String deleteTask(int taskNum) {
Task deletedTask = tasks.deleteTask(taskNum);
storage.saveTasks(tasks);
ui.printDeleteTask(tasks, deletedTask);
return ui.printDeleteTask(tasks, deletedTask);
}

private void findTask(String subject) {
ui.printTasksWithSubject(tasks, subject);
private String findTask(String subject) {
return ui.printTasksWithSubject(tasks, subject);
}
}
12 changes: 12 additions & 0 deletions src/main/java/duke/Launcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package duke;

import javafx.application.Application;

/**
* A launcher class to workaround classpath issues.
*/
public class Launcher {
public static void main(String[] args) {
Application.launch(Main.class, args);
}
}
31 changes: 31 additions & 0 deletions src/main/java/duke/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package duke;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

/**
* A GUI for Duke using FXML.
*/
public class Main extends Application {

private Duke duke = new Duke();

@Override
public void start(Stage stage) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
AnchorPane ap = fxmlLoader.load();
Scene scene = new Scene(ap);
stage.setScene(scene);
fxmlLoader.<MainWindow>getController().setDuke(duke);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
61 changes: 61 additions & 0 deletions src/main/java/duke/MainWindow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package duke;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;

/**
* Controller for MainWindow. Provides the layout for the other controls.
*/
public class MainWindow extends AnchorPane {

@FXML
private ScrollPane scrollPane;
@FXML
private VBox dialogContainer;
@FXML
private TextField userInput;
@FXML
private Button sendButton;

private Duke duke;

// Image credits to Rhaennys from https://rhaennys.itch.io/pixel-art
private Image userImage = new Image(this.getClass().getResourceAsStream("/images/Cecilia.png"));
private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/ItemShopNPC.png"));

@FXML
public void initialize() {
scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
}

public void setDuke(Duke d) {
duke = d;
dialogContainer.getChildren().add(
DialogBox.getDukeDialog(d.getStartUpMessage(), dukeImage));

if (!d.getTasksLoadedMessage().equals("")) {
dialogContainer.getChildren().add(
DialogBox.getDukeDialog(d.getTasksLoadedMessage(), dukeImage));
}
}

/**
* Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
* the dialog container. Clears the user input after processing.
*/
@FXML
private void handleUserInput() {
String input = userInput.getText();
String response = duke.getResponse(input);
dialogContainer.getChildren().addAll(
DialogBox.getUserDialog(input, userImage),
DialogBox.getDukeDialog(response, dukeImage)
);
userInput.clear();
}
}
Loading

0 comments on commit f058d9d

Please sign in to comment.