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