Initial commit
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### 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
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
7
.idea/cssdialects.xml
generated
Normal file
7
.idea/cssdialects.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CssDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources/chat.css" dialect="JavaFX" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources/global.css" dialect="JavaFX" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@@ -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>
|
||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -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_19" default="true" project-jdk-name="19" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
124
.idea/uiDesigner.xml
generated
Normal file
124
.idea/uiDesigner.xml
generated
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# ECS Chat
|
||||
|
||||
Chat application with built-in game built as part of COMP1206 at the University of Southampton.
|
||||
|
||||
Thanks goes to Oli Bills for building the backend system and template for this software.
|
||||
46
pom.xml
Normal file
46
pom.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<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/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>uk.ac.soton.comp1206</groupId>
|
||||
<artifactId>app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>19.0.2.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<release>11</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running -->
|
||||
<!-- Usage: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>uk.ac.soton.comp1206.App</mainClass>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
4
src/main/java/module-info.java
Normal file
4
src/main/java/module-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
module uk.ac.soton.comp1206 {
|
||||
requires javafx.controls;
|
||||
exports uk.ac.soton.comp1206;
|
||||
}
|
||||
30
src/main/java/uk/mgrove/ac/soton/comp1206/App.java
Normal file
30
src/main/java/uk/mgrove/ac/soton/comp1206/App.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package uk.ac.soton.comp1206;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
||||
/**
|
||||
* JavaFX App
|
||||
*/
|
||||
public class App extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
var javaVersion = SystemInfo.javaVersion();
|
||||
var javafxVersion = SystemInfo.javafxVersion();
|
||||
|
||||
var label = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
|
||||
var scene = new Scene(new StackPane(label), 640, 480);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
}
|
||||
|
||||
}
|
||||
13
src/main/java/uk/mgrove/ac/soton/comp1206/SystemInfo.java
Normal file
13
src/main/java/uk/mgrove/ac/soton/comp1206/SystemInfo.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package uk.ac.soton.comp1206;
|
||||
|
||||
public class SystemInfo {
|
||||
|
||||
public static String javaVersion() {
|
||||
return System.getProperty("java.version");
|
||||
}
|
||||
|
||||
public static String javafxVersion() {
|
||||
return System.getProperty("javafx.version");
|
||||
}
|
||||
|
||||
}
|
||||
252
src/main/java/uk/mgrove/ac/soton/comp1206/ui/BaseWindow.java
Normal file
252
src/main/java/uk/mgrove/ac/soton/comp1206/ui/BaseWindow.java
Normal file
@@ -0,0 +1,252 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.*;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base window class, providing template from which all windows should extend
|
||||
*/
|
||||
public abstract class BaseWindow extends Window {
|
||||
protected static final Logger logger = LogManager.getLogger(ChatWindow.class);
|
||||
|
||||
protected final App app;
|
||||
protected final Communicator communicator;
|
||||
|
||||
/**
|
||||
* Duration over which menus should slide open/closed
|
||||
*/
|
||||
protected final static Duration menuTransitionDuration = Duration.millis(300);
|
||||
/**
|
||||
* Duration over which buttons should rotate - used for menu open/close buttons
|
||||
*/
|
||||
protected final static Duration buttonRotationDuration = Duration.millis(150);
|
||||
/**
|
||||
* Duration over which windows should fade in
|
||||
*/
|
||||
protected final static Duration windowFadeInDuration = Duration.millis(500);
|
||||
|
||||
@FXML
|
||||
protected VBox rightPanel;
|
||||
@FXML
|
||||
protected VBox leftPanel;
|
||||
@FXML
|
||||
protected SplitPane mainSplitPane;
|
||||
@FXML
|
||||
protected ImageView showLeftPanel;
|
||||
@FXML
|
||||
protected ImageView showRightPanel;
|
||||
@FXML
|
||||
protected ImageView hideLeftPanel;
|
||||
@FXML
|
||||
protected ImageView hideRightPanel;
|
||||
@FXML
|
||||
protected StackPane mainStackPane;
|
||||
|
||||
protected Parent root;
|
||||
protected Node rightPanelPane;
|
||||
protected Node leftPanelPane;
|
||||
protected double[] mainSplitPaneDividerPositions;
|
||||
protected boolean[] sidePanelsVisible;
|
||||
|
||||
/**
|
||||
* Initialise the window with an FXML template
|
||||
* @param app app to use
|
||||
* @param communicator communicator to use
|
||||
* @param fxmlPath path to FXML file
|
||||
*/
|
||||
public BaseWindow(App app, Communicator communicator, String fxmlPath) {
|
||||
|
||||
this.app = app;
|
||||
this.communicator = communicator;
|
||||
|
||||
//Load the Chat Window GUI
|
||||
try {
|
||||
//Instead of building this GUI programmatically, we are going to use FXML
|
||||
var loader = new FXMLLoader(getClass().getResource(fxmlPath));
|
||||
|
||||
//Link the GUI in the FXML to this class
|
||||
loader.setController(this);
|
||||
root = loader.load();
|
||||
} catch (Exception e) {
|
||||
//Handle any exceptions with loading the FXML
|
||||
logger.error("Unable to read file: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
root.setOpacity(0);
|
||||
fadeInWindow();
|
||||
|
||||
//We are the login window
|
||||
setScene(new Scene(root));
|
||||
|
||||
rightPanelPane = mainSplitPane.getItems().get(2);
|
||||
leftPanelPane = mainSplitPane.getItems().get(0);
|
||||
mainSplitPaneDividerPositions = mainSplitPane.getDividerPositions();
|
||||
sidePanelsVisible = new boolean[2];
|
||||
Arrays.fill(sidePanelsVisible, Boolean.TRUE);
|
||||
mainStackPane.getChildren().removeAll(showLeftPanel, showRightPanel);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade the root node from invisible to visible
|
||||
*/
|
||||
protected void fadeInWindow() {
|
||||
FadeTransition ft = new FadeTransition(windowFadeInDuration, root);
|
||||
ft.setFromValue(0);
|
||||
ft.setToValue(1);
|
||||
ft.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a given pane in a ScrollPane - used to hide menus
|
||||
*
|
||||
* @param panelPane the pane to hide
|
||||
* @param dividerPositionProperty the property for the appropriate divider's position
|
||||
* @param hideButton button to hide the pane
|
||||
* @param showButton button to show the pane
|
||||
* @param panelIndex index of the panel in local info stores (e.g. list of open/closed menus)
|
||||
*/
|
||||
protected void hidePanel(Pane panelPane, DoubleProperty dividerPositionProperty, Node hideButton, Node showButton, int panelIndex) {
|
||||
panelPane.setMinWidth(0);
|
||||
|
||||
int finalDividerPosition = panelIndex == 0 ? 0 : 1;
|
||||
|
||||
KeyValue keyValue = new KeyValue(dividerPositionProperty, finalDividerPosition, Interpolator.EASE_BOTH);
|
||||
Timeline hidePanel = new Timeline(new KeyFrame(menuTransitionDuration, keyValue));
|
||||
|
||||
RotateTransition rotateButton = new RotateTransition(buttonRotationDuration, hideButton);
|
||||
rotateButton.setFromAngle(0);
|
||||
rotateButton.setToAngle(panelIndex == 0 ? 180 : -180);
|
||||
rotateButton.setOnFinished((event2) -> {
|
||||
mainStackPane.getChildren().add(showButton);
|
||||
mainStackPane.getChildren().remove(hideButton);
|
||||
hideButton.setRotate(0);
|
||||
});
|
||||
|
||||
hidePanel.play();
|
||||
|
||||
hidePanel.setOnFinished((event) -> {
|
||||
rotateButton.play();
|
||||
sidePanelsVisible[panelIndex] = false;
|
||||
mainSplitPane.getItems().remove(panelPane);
|
||||
if (panelIndex == 0 && sidePanelsVisible[1])
|
||||
mainSplitPane.setDividerPosition(0, mainSplitPaneDividerPositions[1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a given pane in a ScrollPane - used to show menus
|
||||
*
|
||||
* @param panelPane the pane to show
|
||||
* @param hideButton button to hide the pane
|
||||
* @param showButton button to show the pane
|
||||
* @param panelIndex index of the panel in local info stores (e.g. list of open/closed menus)
|
||||
*/
|
||||
protected void showPanel(Pane panelPane, Node hideButton, Node showButton, int panelIndex) {
|
||||
if (panelIndex == 0) mainSplitPane.getItems().add(0, panelPane);
|
||||
else mainSplitPane.getItems().add(panelPane);
|
||||
|
||||
DoubleProperty dividerPositionProperty = mainSplitPane.getDividers().get(panelIndex == 1 && !sidePanelsVisible[0] ? 0 : panelIndex).positionProperty();
|
||||
|
||||
int initialDividerPosition = panelIndex == 0 ? 0 : 1;
|
||||
|
||||
mainSplitPane.setDividerPosition(panelIndex == 1 && !sidePanelsVisible[0] ? 0 : panelIndex, initialDividerPosition);
|
||||
KeyValue keyValue = new KeyValue(dividerPositionProperty, mainSplitPaneDividerPositions[panelIndex], Interpolator.EASE_BOTH);
|
||||
Timeline showPanel = new Timeline(new KeyFrame(menuTransitionDuration, keyValue));
|
||||
|
||||
RotateTransition rotateButton = new RotateTransition(buttonRotationDuration, showButton);
|
||||
rotateButton.setFromAngle(0);
|
||||
rotateButton.setToAngle(panelIndex == 0 ? -180 : 180);
|
||||
rotateButton.setOnFinished((event2) -> {
|
||||
mainStackPane.getChildren().add(hideButton);
|
||||
mainStackPane.getChildren().remove(showButton);
|
||||
showButton.setRotate(0);
|
||||
});
|
||||
|
||||
showPanel.play();
|
||||
|
||||
showPanel.setOnFinished((event1) -> {
|
||||
rotateButton.play();
|
||||
sidePanelsVisible[panelIndex] = true;
|
||||
panelPane.setMinWidth(Region.USE_COMPUTED_SIZE);
|
||||
if (panelIndex == 0 && sidePanelsVisible[1])
|
||||
mainSplitPane.setDividerPosition(1, mainSplitPaneDividerPositions[1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the right-hand sidebar
|
||||
*/
|
||||
protected void toggleRightPanel() {
|
||||
if (sidePanelsVisible[1]) {
|
||||
DoubleProperty positionProperty;
|
||||
if (sidePanelsVisible[0]) {
|
||||
mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[1];
|
||||
positionProperty = mainSplitPane.getDividers().get(1).positionProperty();
|
||||
} else {
|
||||
mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[0];
|
||||
positionProperty = mainSplitPane.getDividers().get(0).positionProperty();
|
||||
}
|
||||
|
||||
hidePanel(rightPanel, positionProperty, hideRightPanel, showRightPanel, 1);
|
||||
} else {
|
||||
showPanel(rightPanel, hideRightPanel, showRightPanel, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the right-hand sidebar
|
||||
*/
|
||||
protected void toggleLeftPanel() {
|
||||
if (sidePanelsVisible[0]) {
|
||||
mainSplitPaneDividerPositions[0] = mainSplitPane.getDividerPositions()[0];
|
||||
if (sidePanelsVisible[1]) mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[1];
|
||||
|
||||
hidePanel(leftPanel, mainSplitPane.getDividers().get(0).positionProperty(), hideLeftPanel, showLeftPanel, 0);
|
||||
} else {
|
||||
if (sidePanelsVisible[1]) mainSplitPaneDividerPositions[1] = mainSplitPane.getDividerPositions()[0];
|
||||
showPanel(leftPanel, hideLeftPanel, showLeftPanel, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse click to toggle visibility of right-hand sidebar
|
||||
*
|
||||
* @param event mouse clicked
|
||||
*/
|
||||
public void toggleRightPanel(MouseEvent event) {
|
||||
toggleRightPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse click to toggle visibility of right-hand sidebar
|
||||
*
|
||||
* @param event mouse clicked
|
||||
*/
|
||||
public void toggleLeftPanel(MouseEvent event) {
|
||||
toggleLeftPanel();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
import uk.mgrove.ac.soton.comp1206.utility.Utility;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Chat window for displaying chat interface
|
||||
*/
|
||||
public class ChatWindow extends BaseWindow {
|
||||
|
||||
private final static Duration messageFadeInDuration = Duration.millis(150);
|
||||
|
||||
@FXML
|
||||
private TextField usersSearchField;
|
||||
@FXML
|
||||
private Text myUsernameText;
|
||||
@FXML
|
||||
private TextField myUsernameInput;
|
||||
@FXML
|
||||
private ScrollPane messagesContainer;
|
||||
@FXML
|
||||
private Label noMessagesYetText;
|
||||
@FXML
|
||||
private VBox messages;
|
||||
@FXML
|
||||
private TextField messageToSend;
|
||||
@FXML
|
||||
private CheckBox audioEnabled;
|
||||
@FXML
|
||||
private HBox myUsernameInputContainer;
|
||||
@FXML
|
||||
private HBox myUsernameTextContainer;
|
||||
@FXML
|
||||
private VBox myUsernameContainer;
|
||||
@FXML
|
||||
private final UserList userList;
|
||||
@FXML
|
||||
private VBox rightPanel;
|
||||
@FXML
|
||||
private VBox leftPanel;
|
||||
@FXML
|
||||
private SplitPane mainSplitPane;
|
||||
@FXML
|
||||
private ImageView showLeftPanel;
|
||||
@FXML
|
||||
private ImageView showRightPanel;
|
||||
@FXML
|
||||
private ImageView hideLeftPanel;
|
||||
@FXML
|
||||
private ImageView hideRightPanel;
|
||||
@FXML
|
||||
private StackPane mainStackPane;
|
||||
@FXML
|
||||
private Button openGame;
|
||||
private boolean messagesReceived;
|
||||
private boolean scrollToBottom;
|
||||
private boolean lastMessageFromMe;
|
||||
|
||||
/**
|
||||
* Initialise the chat window
|
||||
* @param app the app
|
||||
* @param communicator the communicator
|
||||
*/
|
||||
public ChatWindow(App app, Communicator communicator) {
|
||||
super(app, communicator, "/chat.fxml");
|
||||
|
||||
//Link the communicator to this window
|
||||
communicator.setWindow(this);
|
||||
|
||||
getScene().addPostLayoutPulseListener(this::scrollToBottom);
|
||||
|
||||
StringProperty userSearchProperty = new SimpleStringProperty("");
|
||||
userList = new UserList(userSearchProperty);
|
||||
Platform.runLater(() -> rightPanel.getChildren().add(userList));
|
||||
usersSearchField.textProperty().bindBidirectional(userSearchProperty);
|
||||
usersSearchField.setOnKeyReleased((event) -> {
|
||||
// refresh list of people
|
||||
userList.updateDisplayedUsers();
|
||||
});
|
||||
hideMyUsernameInput(false);
|
||||
|
||||
rightPanelPane = mainSplitPane.getItems().get(2);
|
||||
leftPanelPane = mainSplitPane.getItems().get(0);
|
||||
mainSplitPaneDividerPositions = mainSplitPane.getDividerPositions();
|
||||
sidePanelsVisible = new boolean[2];
|
||||
Arrays.fill(sidePanelsVisible, Boolean.TRUE);
|
||||
mainStackPane.getChildren().removeAll(showLeftPanel, showRightPanel);
|
||||
|
||||
messagesReceived = false;
|
||||
myUsernameInput.textProperty().bindBidirectional(app.usernameProperty());
|
||||
myUsernameText.textProperty().bind(app.usernameProperty());
|
||||
audioEnabled.selectedProperty().bindBidirectional(Utility.audioEnabledProperty());
|
||||
|
||||
openGame.setOnAction((e) -> {
|
||||
app.openGame();
|
||||
});
|
||||
|
||||
communicator.addListener(this::receiveMessage);
|
||||
|
||||
Platform.runLater(() -> messageToSend.requestFocus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when the user presses enter on the message to send field
|
||||
*
|
||||
* @param event key pressed
|
||||
*/
|
||||
@FXML
|
||||
protected void handleUsernameKeypress(KeyEvent event) {
|
||||
if (event.getCode() != KeyCode.ENTER) return;
|
||||
// hide input
|
||||
hideMyUsernameInput(true);
|
||||
messageToSend.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when the user presses enter on the message to send field
|
||||
*
|
||||
* @param event key pressed
|
||||
*/
|
||||
@FXML
|
||||
protected void handleMessageToSendKeypress(KeyEvent event) {
|
||||
if (event.getCode() != KeyCode.ENTER) return;
|
||||
sendCurrentMessage(messageToSend.getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle what happens when the user presses the send message button
|
||||
*
|
||||
* @param event button clicked
|
||||
*/
|
||||
@FXML
|
||||
protected void handleSendMessage(ActionEvent event) {
|
||||
String message = messageToSend.getText();
|
||||
if (message.isBlank()) return;
|
||||
sendCurrentMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle event to show username input when button pressed
|
||||
*
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void showMyUsernameInput(MouseEvent event) {
|
||||
showMyUsernameInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle show username input
|
||||
*/
|
||||
public void showMyUsernameInput() {
|
||||
myUsernameContainer.getChildren().remove(myUsernameTextContainer);
|
||||
myUsernameContainer.getChildren().add(myUsernameInputContainer);
|
||||
myUsernameInput.requestFocus();
|
||||
myUsernameInput.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle event to hide username input when button pressed
|
||||
*
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void hideMyUsernameInput(MouseEvent event) {
|
||||
hideMyUsernameInput(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle hide username input, with option to only remove input container and not add text container
|
||||
*
|
||||
* @param showTextContainer add text container to window
|
||||
*/
|
||||
private void hideMyUsernameInput(boolean showTextContainer) {
|
||||
myUsernameContainer.getChildren().remove(myUsernameInputContainer);
|
||||
if (showTextContainer) {
|
||||
myUsernameContainer.getChildren().add(myUsernameTextContainer);
|
||||
messageToSend.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming message from the Communicator
|
||||
*
|
||||
* @param text The message that has been received, in the form User:Message
|
||||
*/
|
||||
public void receiveMessage(String text) {
|
||||
if (!text.contains(":")) return;
|
||||
if (!messagesReceived) {
|
||||
messages.getChildren().remove(noMessagesYetText);
|
||||
messagesReceived = true;
|
||||
}
|
||||
var components = text.split(":");
|
||||
if (components.length < 2) return;
|
||||
var username = components[0];
|
||||
var message = String.join(":", Arrays.copyOfRange(components, 1, components.length));
|
||||
userList.addUser(username);
|
||||
|
||||
Message receivedMessage = new Message(app, username, message);
|
||||
receivedMessage.setOpacity(0);
|
||||
FadeTransition fadeMessage = new FadeTransition(messageFadeInDuration, receivedMessage);
|
||||
fadeMessage.setToValue(1);
|
||||
messages.getChildren().add(receivedMessage);
|
||||
fadeMessage.play();
|
||||
|
||||
if (messagesContainer.getVvalue() == 1.0f) scrollToBottom = true;
|
||||
|
||||
if (!lastMessageFromMe) Utility.playAudio("incoming.mp3");
|
||||
else lastMessageFromMe = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to bottom of messages list.
|
||||
*/
|
||||
private void scrollToBottom() {
|
||||
if (!scrollToBottom) return;
|
||||
messagesContainer.setVvalue(1.0);
|
||||
scrollToBottom = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an outgoing message from the Chat window.
|
||||
*
|
||||
* @param text The text of the message to send to the Communicator
|
||||
*/
|
||||
private void sendCurrentMessage(String text) {
|
||||
if (!text.isBlank()) {
|
||||
if (text.strip().equals("/game")) app.openGame();
|
||||
else if (text.matches("/nick (.+)$")) app.setUsername(text.substring(6));
|
||||
else {
|
||||
communicator.send(app.getUsername() + ":" + text);
|
||||
lastMessageFromMe = true;
|
||||
}
|
||||
messageToSend.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unhide this window
|
||||
*/
|
||||
public void show() {
|
||||
super.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.utility.Utility;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* Class for a message, which displays the message in a formatted way
|
||||
*/
|
||||
public class Message extends TextFlow {
|
||||
|
||||
private final App app;
|
||||
private final String username;
|
||||
private final String message;
|
||||
private final LocalDateTime received;
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
/**
|
||||
* Initialise the message with basic info
|
||||
* @param app the app
|
||||
* @param username username of user who sent the message
|
||||
* @param message the message contents
|
||||
*/
|
||||
public Message(App app, String username, String message) {
|
||||
this.app = app;
|
||||
this.username = username;
|
||||
this.message = message;
|
||||
received = LocalDateTime.now();
|
||||
build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message
|
||||
*/
|
||||
public void build() {
|
||||
Text timeField = new Text("[" + formatter.format(received) + "] ");
|
||||
timeField.getStyleClass().add("timestamp");
|
||||
Text usernameField = new Text(username + ": ");
|
||||
usernameField.getStyleClass().add("username");
|
||||
getChildren().addAll(timeField,usernameField);
|
||||
|
||||
Matcher urlMatcher = Utility.getUrlPattern().matcher(message);
|
||||
var previousMatchEnd = 0;
|
||||
while (urlMatcher.find()) {
|
||||
if (urlMatcher.start() > previousMatchEnd) {
|
||||
getChildren().add(new Text(message.substring(previousMatchEnd, urlMatcher.start())));
|
||||
}
|
||||
var url = message.substring(urlMatcher.start(), urlMatcher.end());
|
||||
var urlField = new Hyperlink(url);
|
||||
urlField.setOnAction((event) -> app.getHostServices().showDocument(url));
|
||||
getChildren().add(urlField);
|
||||
previousMatchEnd = urlMatcher.end();
|
||||
}
|
||||
if (message.length() > previousMatchEnd) {
|
||||
getChildren().add(new Text(message.substring(previousMatchEnd)));
|
||||
}
|
||||
|
||||
getStyleClass().add("message-container");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
/**
|
||||
* Interface for listeners that handle new messages being received by the Communicator class
|
||||
* @see uk.mgrove.ac.soton.comp1206.network.Communicator
|
||||
*/
|
||||
public interface MessageListener {
|
||||
|
||||
/**
|
||||
* Receive a message
|
||||
* @param message the message to receive
|
||||
*/
|
||||
void receiveMessage(String message);
|
||||
|
||||
}
|
||||
58
src/main/java/uk/mgrove/ac/soton/comp1206/ui/chat/User.java
Normal file
58
src/main/java/uk/mgrove/ac/soton/comp1206/ui/chat/User.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
|
||||
/**
|
||||
* Class for a user, which is an element to be displayed in the chat window
|
||||
*/
|
||||
public class User extends VBox {
|
||||
|
||||
private final String username;
|
||||
private final IntegerProperty messageCount;
|
||||
|
||||
/**
|
||||
* Initialise the user with a username
|
||||
* @param username username of user
|
||||
*/
|
||||
public User(String username) {
|
||||
setSpacing(2);
|
||||
setPadding(new Insets(0, 12,0,12));
|
||||
setFillWidth(true);
|
||||
this.username = username;
|
||||
this.messageCount = new SimpleIntegerProperty(1);
|
||||
var messageCountLabel = new Label("");
|
||||
messageCountLabel.getStyleClass().add("italic");
|
||||
messageCountLabel.setWrapText(true);
|
||||
messageCountLabel.textProperty().bind(Bindings.concat(messageCount.asString()," message(s)"));
|
||||
var usernameLabel = new Label(username);
|
||||
usernameLabel.setWrapText(true);
|
||||
getChildren().addAll(
|
||||
usernameLabel,
|
||||
messageCountLabel
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property for number of messages sent by user
|
||||
* @return property
|
||||
*/
|
||||
public IntegerProperty messageCountProperty() {
|
||||
return messageCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's username
|
||||
* @return username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* List of users to be displayed in chat window
|
||||
*/
|
||||
public class UserList extends ScrollPane {
|
||||
|
||||
private final Map<String,IntegerProperty> userMessageCounts = new HashMap<>();
|
||||
private final VBox container;
|
||||
private final ArrayList<User> userElements = new ArrayList<>();
|
||||
private final StringProperty userSearchProperty;
|
||||
|
||||
/**
|
||||
* Initialise the list of users
|
||||
* @param userSearchProperty property for content of users search field
|
||||
*/
|
||||
public UserList(StringProperty userSearchProperty) {
|
||||
setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
|
||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
getStyleClass().add("user-list");
|
||||
setFitToWidth(true);
|
||||
container = new VBox();
|
||||
container.setSpacing(12);
|
||||
container.setFillWidth(true);
|
||||
setContent(container);
|
||||
this.userSearchProperty = userSearchProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user to list of users
|
||||
* @param username username of user to add
|
||||
*/
|
||||
public void addUser(String username) {
|
||||
if (userMessageCounts.containsKey(username)) {
|
||||
var property = userMessageCounts.get(username);
|
||||
property.set(property.get() + 1);
|
||||
} else {
|
||||
var newUser = new User(username);
|
||||
userMessageCounts.put(username, newUser.messageCountProperty());
|
||||
|
||||
newUser.setOpacity(0);
|
||||
FadeTransition fadeMessage = new FadeTransition(Duration.millis(150), newUser);
|
||||
fadeMessage.setToValue(1);
|
||||
userElements.add(newUser);
|
||||
fadeMessage.play();
|
||||
|
||||
sortUserElements();
|
||||
updateDisplayedUsers();
|
||||
}
|
||||
}
|
||||
|
||||
private void sortUserElements() {
|
||||
userElements.sort(Comparator.comparing(User::getUsername));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of users displayed
|
||||
*/
|
||||
public void updateDisplayedUsers() {
|
||||
if (userSearchProperty.get().isBlank()) {
|
||||
container.getChildren().setAll(userElements);
|
||||
} else {
|
||||
container.getChildren().setAll(userElements.stream().filter((user) -> user.getUsername().toLowerCase().contains(userSearchProperty.get().toLowerCase())).toList());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
/**
|
||||
* Interface for listeners that handle a game block being clicked
|
||||
*/
|
||||
public interface BlockClickedListener {
|
||||
|
||||
/**
|
||||
* Handle game block being clicked
|
||||
* @param block block that was clicked
|
||||
*/
|
||||
void blockClicked(GameBlock block);
|
||||
|
||||
}
|
||||
114
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/GameBlock.java
Normal file
114
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/GameBlock.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Class for a game block, which is a single block with a colour that can be changed
|
||||
*/
|
||||
public class GameBlock extends Canvas {
|
||||
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final Color[] colours;
|
||||
private final double width;
|
||||
private final double height;
|
||||
private final IntegerProperty value;
|
||||
private final boolean showStrokes;
|
||||
private final boolean roundedCorners;
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the game block with a set of available colours, a set size,
|
||||
* a set location in the grid, and outlines around the block
|
||||
* @param colours available colours
|
||||
* @param x column index of location in grid
|
||||
* @param y row index of location in grid
|
||||
* @param width block width
|
||||
* @param height block height
|
||||
*/
|
||||
public GameBlock(Color[] colours, int x, int y, double width, double height) {
|
||||
this(colours,x,y,width,height,true,false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the game block with a set of available colours, a set size,
|
||||
* a set location in the grid, and optional outlines around the block
|
||||
* @param colours available colours
|
||||
* @param x column index of location in grid
|
||||
* @param y row index of location in grid
|
||||
* @param width block width
|
||||
* @param height block height
|
||||
* @param showStrokes whether block should have an outline
|
||||
* @param roundedCorners whether block should have rounded corners
|
||||
*/
|
||||
public GameBlock(Color[] colours, int x, int y, double width, double height, boolean showStrokes, boolean roundedCorners) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colours = colours;
|
||||
this.showStrokes = showStrokes;
|
||||
this.roundedCorners = roundedCorners;
|
||||
value = new SimpleIntegerProperty();
|
||||
|
||||
setWidth(width);
|
||||
setHeight(height);
|
||||
|
||||
value.addListener(((observableValue, oldValue, newValue) -> paint()));
|
||||
|
||||
paint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint the block - i.e. draw it
|
||||
*/
|
||||
public void paint() {
|
||||
GraphicsContext gc = getGraphicsContext2D();
|
||||
gc.clearRect(0,0,width,height);
|
||||
|
||||
if (value.get() >= 0) {
|
||||
// fill in colour block
|
||||
gc.setFill(colours[value.get()]);
|
||||
gc.setStroke(Color.BLACK);
|
||||
|
||||
if (roundedCorners) {
|
||||
gc.fillRoundRect(0,0,width,height,16,16);
|
||||
if (showStrokes) gc.strokeRoundRect(0,0,width,height,16,16);
|
||||
} else {
|
||||
gc.fillRect(0,0,width,height);
|
||||
if (showStrokes) gc.strokeRect(0,0,width,height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x-coordinate of this block in the grid
|
||||
* @return x-coordinate
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y-coordinate of this block in the grid
|
||||
* @return y-coordinate
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value property for binding, which holds the current colour value
|
||||
* @return value property
|
||||
*/
|
||||
public IntegerProperty valueProperty() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Game grid class, which displays a grid of game blocks for the game
|
||||
*/
|
||||
public class GameGrid extends GridPane {
|
||||
|
||||
private final List<BlockClickedListener> listeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Initialise game grid with an existing grid, a set size, and a set of available colours
|
||||
* @param colours available colours
|
||||
* @param grid existing grid to use
|
||||
* @param width game grid width
|
||||
* @param height game grid height
|
||||
*/
|
||||
public GameGrid(Color[] colours, Grid grid, int width, int height) {
|
||||
var cols = grid.getCols();
|
||||
var rows = grid.getRows();
|
||||
|
||||
setMaxWidth(width);
|
||||
setMaxHeight(height);
|
||||
setGridLinesVisible(true);
|
||||
|
||||
// create each internal game block
|
||||
for (var y=0; y < rows; y++) {
|
||||
for (var x=0; x < cols; x++) {
|
||||
var block = new GameBlock(colours, x, y, (double) width/cols, (double) height/rows);
|
||||
add(block,x,y);
|
||||
|
||||
block.valueProperty().bind(grid.getGridProperty(x,y));
|
||||
|
||||
// handle when game block is clicked
|
||||
block.setOnMouseClicked((e) -> blockClicked(block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener for blocks being clicked
|
||||
* @param listener listener
|
||||
*/
|
||||
public void addListener(BlockClickedListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle block being clicked by calling all appropriate listeners
|
||||
* @param block block that was clicked
|
||||
*/
|
||||
public void blockClicked(GameBlock block) {
|
||||
for (BlockClickedListener listener : listeners) {
|
||||
listener.blockClicked(block);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import uk.mgrove.ac.soton.comp1206.App;
|
||||
import uk.mgrove.ac.soton.comp1206.network.Communicator;
|
||||
import uk.mgrove.ac.soton.comp1206.utility.Utility;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Game window which will display mini-game for users to play
|
||||
*/
|
||||
public class GameWindow extends BaseWindow {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(GameWindow.class);
|
||||
|
||||
private static final int maxColourChanges = 30;
|
||||
|
||||
private final Grid grid;
|
||||
private final Random random = new Random();
|
||||
private final Color[] colours;
|
||||
private final IntegerProperty currentColour = new SimpleIntegerProperty();
|
||||
private final IntegerProperty score = new SimpleIntegerProperty(0);
|
||||
private Timer gameTimer;
|
||||
private int colourChangesCount;
|
||||
private final List<String> scores = new ArrayList<>();
|
||||
@FXML
|
||||
private BorderPane gameBoardContainer;
|
||||
@FXML
|
||||
private Text currentScoreField;
|
||||
@FXML
|
||||
private VBox currentColourContainer;
|
||||
@FXML
|
||||
private VBox highScoresContainer;
|
||||
@FXML
|
||||
private VBox gameCoverContainer;
|
||||
@FXML
|
||||
private Text gameCoverTitle;
|
||||
@FXML
|
||||
private ProgressBar progressBar;
|
||||
|
||||
/**
|
||||
* Initialise the game window
|
||||
*
|
||||
* @param app the app
|
||||
* @param communicator the communicator
|
||||
*/
|
||||
public GameWindow(App app, Communicator communicator) {
|
||||
super(app, communicator, "/game.fxml");
|
||||
|
||||
colours = new Color[]{
|
||||
Color.web("0x005C84"),
|
||||
Color.web("0x74C9E5"),
|
||||
Color.web("0x4BB694"),
|
||||
Color.web("0xC1D100"),
|
||||
Color.web("0xEF7D00"),
|
||||
Color.web("0xE73037"),
|
||||
Color.web("0xD5007F"),
|
||||
Color.web("0x8D3970")
|
||||
};
|
||||
|
||||
grid = new Grid(6, 6);
|
||||
|
||||
currentScoreField.textProperty().bind(score.asString());
|
||||
|
||||
var currentColourBlock = new GameBlock(colours, 0, 0, 100, 100, false, true);
|
||||
currentColourContainer.getChildren().add(currentColourBlock);
|
||||
currentColourBlock.valueProperty().bind(currentColour);
|
||||
|
||||
communicator.addListener((message) -> {
|
||||
if (!message.startsWith("SCORES")) return;
|
||||
Platform.runLater(() -> this.receiveScore(message));
|
||||
});
|
||||
|
||||
communicator.send("SCORES");
|
||||
}
|
||||
|
||||
/**
|
||||
* Events that occur at regular intervals to maintain the game: update the current colour and handle game ending
|
||||
*/
|
||||
public void gameLoop() {
|
||||
if (colourChangesCount < maxColourChanges) {
|
||||
var randomColour = random.nextInt(colours.length);
|
||||
logger.info("Game loop - Colour chosen: {}", randomColour);
|
||||
currentColour.set(randomColour);
|
||||
colourChangesCount++;
|
||||
progressBar.setProgress((double) (maxColourChanges - colourChangesCount + 1) / maxColourChanges);
|
||||
} else {
|
||||
progressBar.setProgress(0);
|
||||
endGame();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle game block being clicked - adjust score appropriately
|
||||
*
|
||||
* @param block block that was clicked
|
||||
*/
|
||||
public void blockClicked(GameBlock block) {
|
||||
var gridBlock = grid.getGridProperty(block.getX(), block.getY());
|
||||
if (currentColour.get() == gridBlock.get()) {
|
||||
logger.info("{} is {}, scoring 5 points", currentColour.get(), gridBlock.getValue());
|
||||
score.set(score.get() + 5);
|
||||
gridBlock.set(random.nextInt(colours.length));
|
||||
} else {
|
||||
logger.info("{} is not {}, losing 1 point", currentColour.get(), gridBlock.getValue());
|
||||
if (score.get() > 0) {
|
||||
score.set(score.get() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and store scores from server
|
||||
* @param message message containing scores
|
||||
*/
|
||||
private void receiveScore(String message) {
|
||||
scores.clear();
|
||||
|
||||
for (var line : message.split("\n")) {
|
||||
if (!line.equals("SCORES")) {
|
||||
int splitIndex = line.lastIndexOf("=");
|
||||
try {
|
||||
String[] info = {line.substring(0,splitIndex), line.substring(splitIndex+1)};
|
||||
scores.add(info[0] + ": " + info[1]);
|
||||
} catch (Exception ignored) {
|
||||
logger.error("Couldn't load score from message {}",message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highScoresContainer.getChildren().clear();
|
||||
for (var i=0; i < Math.min(scores.size(),5); i++) {
|
||||
highScoresContainer.getChildren().add(new Text(scores.get(i)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show game over screen
|
||||
*/
|
||||
private void showGameOver() {
|
||||
gameCoverTitle.setText("Game Over!");
|
||||
Platform.runLater(() -> gameBoardContainer.setCenter(gameCoverContainer));
|
||||
// TODO: include a timer bar
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new game
|
||||
*/
|
||||
private void startGame() {
|
||||
colourChangesCount = 0;
|
||||
score.set(0);
|
||||
|
||||
// set up grid with random values
|
||||
for (int y = 0; y < grid.getRows(); y++) {
|
||||
for (int x = 0; x < grid.getCols(); x++) {
|
||||
grid.set(x, y, random.nextInt(colours.length));
|
||||
}
|
||||
}
|
||||
|
||||
currentColour.set(random.nextInt(colours.length));
|
||||
|
||||
var gameGrid = new GameGrid(colours, grid, 400, 350);
|
||||
gameGrid.addListener(this::blockClicked);
|
||||
|
||||
gameBoardContainer.setCenter(gameGrid);
|
||||
|
||||
TimerTask timerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
gameLoop();
|
||||
}
|
||||
};
|
||||
gameTimer = new Timer("Timer");
|
||||
gameTimer.schedule(timerTask, 0, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start game when button clicked
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void startGame(ActionEvent event) {
|
||||
startGame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return to the chat window when button clicked
|
||||
* @param event button clicked
|
||||
*/
|
||||
public void returnToChat(ActionEvent event) {
|
||||
stopGameTimer();
|
||||
app.setGameOpen(false);
|
||||
app.returnToChat(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the game
|
||||
*/
|
||||
private void endGame() {
|
||||
// game finished
|
||||
stopGameTimer();
|
||||
currentColour.set(-1);
|
||||
communicator.send(String.format("SCORE %s %d",app.getUsername(),score.get()));
|
||||
communicator.send("SCORES");
|
||||
showGameOver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the game timer
|
||||
*/
|
||||
public void stopGameTimer() {
|
||||
if (gameTimer != null) gameTimer.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
79
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/Grid.java
Normal file
79
src/main/java/uk/mgrove/ac/soton/comp1206/ui/game/Grid.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package uk.mgrove.ac.soton.comp1206.ui;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
/**
|
||||
* Grid class, used to store a grid of game blocks for the game
|
||||
*/
|
||||
public class Grid {
|
||||
|
||||
private final int cols;
|
||||
private final int rows;
|
||||
final SimpleIntegerProperty[][] grid;
|
||||
|
||||
/**
|
||||
* Initialise the grid
|
||||
* @param cols number of columns in the grid
|
||||
* @param rows number of rows in the grid
|
||||
*/
|
||||
public Grid(int cols, int rows) {
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
|
||||
grid = new SimpleIntegerProperty[cols][rows];
|
||||
|
||||
for (var y=0; y < rows; y++) {
|
||||
for (var x=0; x < cols; x++) {
|
||||
grid[x][y] = new SimpleIntegerProperty(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property relating to a specific element in the grid
|
||||
* @param x column index
|
||||
* @param y row index
|
||||
* @return the property relating to the element at the given location
|
||||
*/
|
||||
public IntegerProperty getGridProperty(int x, int y) {
|
||||
return grid[x][y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a specific element in the grid
|
||||
* @param x column index
|
||||
* @param y row index
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void set(int x, int y, int value) {
|
||||
grid[x][y].set(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a specific element in the grid
|
||||
* @param x column index
|
||||
* @param y row index
|
||||
* @return the value of the element
|
||||
*/
|
||||
public int get(int x, int y) {
|
||||
return grid[x][y].get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of columns in the grid
|
||||
* @return number of columns
|
||||
*/
|
||||
public int getCols() {
|
||||
return cols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of rows in the grid
|
||||
* @return number of rows
|
||||
*/
|
||||
public int getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
}
|
||||
204
src/main/resources/base.fxml
Normal file
204
src/main/resources/base.fxml
Normal file
@@ -0,0 +1,204 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.Cursor?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.SplitPane?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
|
||||
<SplitPane fx:id="mainSplitPane" dividerPositions="0.14, 0.75" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="500.0" minWidth="800.0" prefHeight="628.0" prefWidth="1105.0" styleClass=".root" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<items>
|
||||
<VBox fx:id="leftPanel" alignment="TOP_RIGHT" spacing="4.0">
|
||||
<children>
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="active" text="Chat" />
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Game" />
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Whiteboard" />
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<CheckBox fx:id="audioEnabled" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" selected="true" text="Sound" />
|
||||
</graphic>
|
||||
<VBox.margin>
|
||||
<Insets top="12.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
</children>
|
||||
<styleClass>
|
||||
<String fx:value="menu" />
|
||||
<String fx:value="left-menu" />
|
||||
</styleClass>
|
||||
</VBox>
|
||||
<StackPane fx:id="chatContainerStackPane" prefHeight="150.0" prefWidth="200.0">
|
||||
<children>
|
||||
<VBox maxHeight="1.7976931348623157E308" spacing="4.0" styleClass="chat-container">
|
||||
<children>
|
||||
<HBox prefHeight="28.0" styleClass="header">
|
||||
<children>
|
||||
<ImageView fitHeight="42.0" fitWidth="42.0" pickOnBounds="true" preserveRatio="true" styleClass="logo">
|
||||
<image>
|
||||
<Image url="@uos_logo.png" />
|
||||
</image>
|
||||
<HBox.margin>
|
||||
<Insets bottom="8.0" left="12.0" right="12.0" top="8.0" />
|
||||
</HBox.margin>
|
||||
</ImageView>
|
||||
<VBox alignment="CENTER_LEFT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="h2" text="ECS Chat" />
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="The ultimate chatroom." />
|
||||
</children>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
<HBox.margin>
|
||||
<Insets bottom="4.0" />
|
||||
</HBox.margin>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
<Separator prefWidth="200.0" />
|
||||
<ScrollPane fx:id="messagesContainer" fitToWidth="true" focusTraversable="false" hbarPolicy="NEVER" maxHeight="1.7976931348623157E308" styleClass="messages-container" vvalue="1.0" VBox.vgrow="ALWAYS">
|
||||
<content>
|
||||
<VBox fx:id="messages" maxWidth="1.7976931348623157E308" styleClass="messages">
|
||||
<children>
|
||||
<Label fx:id="noMessagesYetText" text="No messages yet." wrapText="true" />
|
||||
</children>
|
||||
</VBox>
|
||||
</content>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
</ScrollPane>
|
||||
<HBox alignment="CENTER_LEFT" maxWidth="1.7976931348623157E308" spacing="4.0">
|
||||
<children>
|
||||
<TextField fx:id="messageToSend" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" onKeyPressed="#handleMessageToSendKeypress" promptText="Type a message..." HBox.hgrow="ALWAYS">
|
||||
<styleClass>
|
||||
<String fx:value="message-input" />
|
||||
<String fx:value="text-field-no-outline" />
|
||||
</styleClass>
|
||||
</TextField>
|
||||
<Button mnemonicParsing="false" styleClass="button-primary" text="Send" />
|
||||
</children>
|
||||
<styleClass>
|
||||
<String fx:value="send-message-container" />
|
||||
<String fx:value="outline-section" />
|
||||
</styleClass>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
<ImageView fx:id="showRightPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleRightPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_RIGHT">
|
||||
<image>
|
||||
<Image url="@angle_left_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
<ImageView fx:id="showLeftPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleLeftPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_LEFT">
|
||||
<image>
|
||||
<Image url="@angle_right_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
<ImageView fx:id="hideLeftPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleLeftPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_LEFT">
|
||||
<image>
|
||||
<Image url="@angle_left_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
<ImageView fx:id="hideRightPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleRightPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_RIGHT">
|
||||
<image>
|
||||
<Image url="@angle_right_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
</children>
|
||||
</StackPane>
|
||||
<VBox fx:id="rightPanel" prefHeight="200.0" prefWidth="100.0" spacing="4.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="h3" text="Connected Users" />
|
||||
<HBox alignment="CENTER_LEFT" spacing="4.0" styleClass="outline-section">
|
||||
<children>
|
||||
<TextField fx:id="usersSearchField" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" promptText="Search" styleClass="text-field-no-outline" HBox.hgrow="ALWAYS" />
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@search_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
</children>
|
||||
</HBox>
|
||||
<VBox>
|
||||
<children>
|
||||
<VBox fx:id="myUsernameContainer" styleClass="outline-section">
|
||||
<children>
|
||||
<HBox fx:id="myUsernameTextContainer" alignment="CENTER_LEFT" maxWidth="1.7976931348623157E308" onMouseClicked="#showMyUsernameInput" spacing="4.0">
|
||||
<children>
|
||||
<VBox alignment="CENTER_LEFT" maxWidth="1.7976931348623157E308" styleClass="my-nickname-container" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Text fx:id="myUsernameText" strokeType="OUTSIDE" strokeWidth="0.0" text="Guest" />
|
||||
</children>
|
||||
</VBox>
|
||||
<ImageView id="editMyNickname" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@pen_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox id="editMyNickname" fx:id="myUsernameInputContainer" alignment="CENTER_LEFT" maxWidth="1.7976931348623157E308" spacing="4.0">
|
||||
<children>
|
||||
<TextField fx:id="myUsernameInput" maxHeight="1.7976931348623157E308" onKeyPressed="#handleUsernameKeypress" text="Guest" HBox.hgrow="ALWAYS">
|
||||
<styleClass>
|
||||
<String fx:value="edit-nickname-input" />
|
||||
<String fx:value="text-field-no-outline" />
|
||||
</styleClass>
|
||||
</TextField>
|
||||
<ImageView id="saveMyNickname" fitHeight="16.0" fitWidth="16.0" onMouseClicked="#hideMyUsernameInput" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@check_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<styleClass>
|
||||
<String fx:value="menu" />
|
||||
<String fx:value="right-menu" />
|
||||
</styleClass>
|
||||
</VBox>
|
||||
</items>
|
||||
<stylesheets>
|
||||
<URL value="@chat.css" />
|
||||
<URL value="@global.css" />
|
||||
</stylesheets>
|
||||
</SplitPane>
|
||||
115
src/main/resources/game.fxml
Normal file
115
src/main/resources/game.fxml
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.Cursor?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.SplitPane?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
|
||||
<SplitPane fx:id="mainSplitPane" dividerPositions="0.14, 0.75" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="500.0" minWidth="800.0" prefHeight="628.0" prefWidth="1105.0" styleClass=".root" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<items>
|
||||
<VBox fx:id="leftPanel" alignment="TOP_RIGHT" spacing="4.0">
|
||||
<children>
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="active" text="Chat" />
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Game" />
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Whiteboard" />
|
||||
<Button alignment="TOP_LEFT" maxWidth="1.7976931348623157E308" mnemonicParsing="false">
|
||||
<graphic>
|
||||
<CheckBox fx:id="audioEnabled" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" selected="true" text="Sound" />
|
||||
</graphic>
|
||||
<VBox.margin>
|
||||
<Insets top="12.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
</children>
|
||||
<styleClass>
|
||||
<String fx:value="menu" />
|
||||
<String fx:value="left-menu" />
|
||||
</styleClass>
|
||||
</VBox>
|
||||
<StackPane fx:id="mainStackPane" prefHeight="150.0" prefWidth="200.0">
|
||||
<children>
|
||||
<VBox maxHeight="1.7976931348623157E308" spacing="4.0" styleClass="chat-container">
|
||||
<children>
|
||||
<HBox prefHeight="28.0" styleClass="header">
|
||||
<children>
|
||||
<ImageView fitHeight="42.0" fitWidth="42.0" pickOnBounds="true" preserveRatio="true" styleClass="logo">
|
||||
<image>
|
||||
<Image url="@uos_logo.png" />
|
||||
</image>
|
||||
<HBox.margin>
|
||||
<Insets bottom="8.0" left="12.0" right="12.0" top="8.0" />
|
||||
</HBox.margin>
|
||||
</ImageView>
|
||||
<VBox alignment="CENTER_LEFT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="h2" text="ECS Chat" />
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="The ultimate chatroom." />
|
||||
</children>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
<HBox.margin>
|
||||
<Insets bottom="4.0" />
|
||||
</HBox.margin>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
<Separator prefWidth="200.0" />
|
||||
</children>
|
||||
</VBox>
|
||||
<ImageView fx:id="showRightPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleRightPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_RIGHT">
|
||||
<image>
|
||||
<Image url="@angle_left_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
<ImageView fx:id="showLeftPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleLeftPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_LEFT">
|
||||
<image>
|
||||
<Image url="@angle_right_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
<ImageView fx:id="hideLeftPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleLeftPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_LEFT">
|
||||
<image>
|
||||
<Image url="@angle_left_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
<ImageView fx:id="hideRightPanel" fitHeight="24.0" fitWidth="24.0" onMouseClicked="#toggleRightPanel" pickOnBounds="true" preserveRatio="true" StackPane.alignment="CENTER_RIGHT">
|
||||
<image>
|
||||
<Image url="@angle_right_icon.png" />
|
||||
</image>
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</ImageView>
|
||||
</children>
|
||||
</StackPane>
|
||||
<VBox fx:id="rightPanel" prefHeight="200.0" prefWidth="100.0" spacing="4.0">
|
||||
<styleClass>
|
||||
<String fx:value="menu" />
|
||||
<String fx:value="right-menu" />
|
||||
</styleClass>
|
||||
</VBox>
|
||||
</items>
|
||||
<stylesheets>
|
||||
<URL value="@chat.css" />
|
||||
<URL value="@global.css" />
|
||||
</stylesheets>
|
||||
</SplitPane>
|
||||
93
src/main/resources/global.css
Normal file
93
src/main/resources/global.css
Normal file
@@ -0,0 +1,93 @@
|
||||
/* global */
|
||||
root {
|
||||
display: block;
|
||||
}
|
||||
.root > * {
|
||||
-fx-padding: 4px;
|
||||
-fx-background-color:#ffffff;
|
||||
}
|
||||
* {
|
||||
-fx-font-family: "Roboto";
|
||||
}
|
||||
|
||||
/* menus */
|
||||
.menu {
|
||||
-fx-padding: 32px 0 0 0;
|
||||
}
|
||||
.menu > .button {
|
||||
-fx-text-alignment: left;
|
||||
-fx-alignment: top-left !important;
|
||||
}
|
||||
|
||||
/* checkboxes */
|
||||
.check-box > .box {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: #9FB1BD;
|
||||
-fx-border-radius: 4px;
|
||||
}
|
||||
.check-box:selected > .box {
|
||||
-fx-background-color: #E1E8EC;
|
||||
}
|
||||
.check-box:selected > .box > .mark,
|
||||
.check-box:indeterminate > .box > .mark {
|
||||
-fx-background-color: #005C84;
|
||||
}
|
||||
|
||||
/* buttons and text fields */
|
||||
.button {
|
||||
-fx-text-fill: #2a2a2a;
|
||||
-fx-font-family: "Arial";
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-background-color: #2a2a2a;
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: #E1E8EC;
|
||||
-fx-border-radius: 8px;
|
||||
-fx-background-radius: 8px;
|
||||
}
|
||||
.button.active {
|
||||
-fx-background-color: #E1E8EC;
|
||||
-fx-text-background-color: #005C84;
|
||||
-fx-text-fill: #005C84;
|
||||
-fx-border-color: #758D9A;
|
||||
}
|
||||
.button-primary {
|
||||
-fx-background-color: #005C84;
|
||||
-fx-text-background-color: #fff;
|
||||
-fx-text-fill: #fff;
|
||||
-fx-border-color: #005C84;
|
||||
}
|
||||
.text-field, .button {
|
||||
-fx-padding: 12px;
|
||||
}
|
||||
.text-field {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: #cccccc;
|
||||
-fx-border-radius: 8px;
|
||||
-fx-background-radius: 8px;
|
||||
-fx-padding: 12px;
|
||||
}
|
||||
|
||||
/* headings */
|
||||
.h2 {
|
||||
-fx-font-size: 2em;
|
||||
}
|
||||
.h3 {
|
||||
-fx-font-size: 1.4em;
|
||||
}
|
||||
|
||||
/* headers */
|
||||
.header .logo {
|
||||
-fx-start-margin: 8px;
|
||||
-fx-end-margin: 8px;
|
||||
}
|
||||
|
||||
/* sections */
|
||||
.outline-section {
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: #E1E8EC;
|
||||
-fx-border-radius: 8px;
|
||||
-fx-padding: 12px;
|
||||
}
|
||||
Reference in New Issue
Block a user