mirror of
https://github.com/Akomry/makeyourownapp-jam.git
synced 2025-12-06 11:43:53 +00:00
commit
4680869df4
21 changed files with 1608 additions and 68 deletions
57
.github/workflows/maven.yml
vendored
Normal file
57
.github/workflows/maven.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
|
||||||
|
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
name: Maven
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-ubuntu:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 23
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '23'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package --file pom.xml
|
||||||
|
|
||||||
|
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
||||||
|
- name: Update dependency graph
|
||||||
|
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
|
||||||
|
|
||||||
|
- name: Run J-Link
|
||||||
|
run: javafx:jlink -f pom.xml
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up JDK 23
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '23'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package --file pom.xml
|
||||||
|
|
||||||
|
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
||||||
|
- name: Update dependency graph
|
||||||
|
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
|
||||||
|
|
||||||
|
- name: Run J-Link
|
||||||
|
run: mvn javafx:jlink -f pom.xml
|
||||||
23
README.md
23
README.md
|
|
@ -1,12 +1,13 @@
|
||||||
# Make your own app! - [appName]
|
# Make your own app! - Yet Another Collaborative Whiteboard App
|
||||||
|
|
||||||
## What is it ?
|
## What is it ?
|
||||||
@torineos and @akomry's submission to `Make your own app!` game jam.
|
@akomry's submission to `Make your own app!` game jam.
|
||||||
Probably a networking or multimedia app or something.
|
|
||||||
|
Basically, this is an online drawing app. It only has the most basic tools for now, pen and eraser with custom color, but I hope to be able to add more.
|
||||||
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
You need at least **[Java 23](https://adoptium.net/temurin/releases/?version=23)** up and running.
|
Probably **[Java 23](https://adoptium.net/temurin/releases/?version=23)** up and running.
|
||||||
|
|
||||||
|
|
||||||
## Installation guide
|
## Installation guide
|
||||||
|
|
@ -16,14 +17,20 @@ install a packaged zipped file. Then execute `[extract dir]/bin/app`.
|
||||||
|
|
||||||
## -Roadmap-
|
## -Roadmap-
|
||||||
- [x] Brainstorming
|
- [x] Brainstorming
|
||||||
- [ ] Find a name
|
- [x] Find a name
|
||||||
- [ ] List issues
|
- [x] List issues
|
||||||
|
- [x] Experiment with javafx Canvas
|
||||||
|
- [x] Implement Canvas creation
|
||||||
|
- [x] Implement TCP/IP server/client
|
||||||
|
- [x] Implement event callback
|
||||||
|
- [x] Implement brush, its size and color
|
||||||
|
- [x] Implement eraser
|
||||||
|
- [ ] Implement zoom control (WIP, sketchy zoom)
|
||||||
|
- [ ] Implement layering system
|
||||||
|
|
||||||
## License
|
## License
|
||||||
TBD
|
TBD
|
||||||
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
* @torineos
|
|
||||||
* @akomry
|
* @akomry
|
||||||
|
|
@ -9,20 +9,22 @@
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
<name>graphical-app</name>
|
<name>graphical-app</name>
|
||||||
|
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<junit.version>5.10.0</junit.version> </properties>
|
<junit.version>5.10.0</junit.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-controls</artifactId>
|
<artifactId>javafx-controls</artifactId>
|
||||||
<version>17.0.6</version>
|
<version>24</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>17.0.6</version>
|
<version>24</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
@ -36,14 +38,30 @@
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
<version>${junit.version}</version>
|
<version>${junit.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency> </dependencies>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.controlsfx</groupId>
|
||||||
|
<artifactId>controlsfx</artifactId>
|
||||||
|
<version>11.2.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.synedra</groupId>
|
||||||
|
<artifactId>validatorfx</artifactId>
|
||||||
|
<version>0.5.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20240303</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.11.0</version>
|
<version>3.14.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>23</source>
|
<source>23</source>
|
||||||
<target>23</target>
|
<target>23</target>
|
||||||
|
|
@ -55,10 +73,9 @@
|
||||||
<version>0.0.8</version>
|
<version>0.0.8</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
|
||||||
<id>default-cli</id>
|
<id>default-cli</id>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>fr.emiko.graphicalapp/fr.emiko.graphicalapp.HelloApplication</mainClass>
|
<mainClass>fr.emiko.graphicalapp/fr.emiko.graphicalapp.DrawApplication</mainClass>
|
||||||
<launcher>app</launcher>
|
<launcher>app</launcher>
|
||||||
<jlinkZipName>app</jlinkZipName>
|
<jlinkZipName>app</jlinkZipName>
|
||||||
<jlinkImageName>app</jlinkImageName>
|
<jlinkImageName>app</jlinkImageName>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package fr.emiko.graphicalapp;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class DrawApplication extends Application {
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws IOException {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(DrawApplication.class.getResource("draw-view.fxml"));
|
||||||
|
Scene scene = new Scene(fxmlLoader.load(), 1280, 720);
|
||||||
|
stage.setTitle("Yet Another Collaborative Drawing App");
|
||||||
|
stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("icon.png"))));
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,491 @@
|
||||||
|
package fr.emiko.graphicalapp;
|
||||||
|
|
||||||
|
import fr.emiko.graphicsElement.Line;
|
||||||
|
import fr.emiko.graphicsElement.layerListViewCell;
|
||||||
|
import fr.emiko.net.DrawClient;
|
||||||
|
import fr.emiko.net.DrawServer;
|
||||||
|
import fr.emiko.net.Event;
|
||||||
|
import fr.emiko.net.User;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.effect.BoxBlur;
|
||||||
|
import javafx.scene.input.*;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import fr.emiko.graphicsElement.Stroke;
|
||||||
|
import javafx.scene.transform.Scale;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class DrawController implements Initializable {
|
||||||
|
private final Pattern hostPortPattern = Pattern.compile("^([-.a-zA-Z0-9]+)(?::([0-9]{1,5}))?$");
|
||||||
|
public Canvas drawingCanvas;
|
||||||
|
public MenuItem saveButton;
|
||||||
|
public MenuItem loadButton;
|
||||||
|
public MenuItem newCanvasButton;
|
||||||
|
public Slider brushSizeSlider;
|
||||||
|
public ScrollPane scrollPane;
|
||||||
|
public Label brushSizeLabel;
|
||||||
|
public Pane pane;
|
||||||
|
public MenuItem hostButton;
|
||||||
|
public MenuItem joinButton;
|
||||||
|
public MenuItem disconnectButton;
|
||||||
|
public SplitPane mainPane;
|
||||||
|
public MenuItem stopHostButton;
|
||||||
|
public ColorPicker colorPicker;
|
||||||
|
public ListView<Canvas> layerListView;
|
||||||
|
public Button addLayerButton;
|
||||||
|
public Button removeLayerButton;
|
||||||
|
public MenuItem aboutMenuItem;
|
||||||
|
public Label statusLabel;
|
||||||
|
private double posX = 0;
|
||||||
|
private double posY = 0;
|
||||||
|
private double mouseX = 0;
|
||||||
|
private double mouseY = 0;
|
||||||
|
private Vector<Stroke> strokes = new Vector<>();
|
||||||
|
private Vector<Line> lastSaved = new Vector<>();
|
||||||
|
private Vector<Line> lines = new Vector<>();
|
||||||
|
private User user;
|
||||||
|
private boolean connected;
|
||||||
|
private DrawClient client;
|
||||||
|
private ToggleButton hostButtonToggle = new ToggleButton();
|
||||||
|
private DrawServer server;
|
||||||
|
private ObservableList<Canvas> layerObservableList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
saveButton.setOnAction(this::onActionSave);
|
||||||
|
loadButton.setOnAction(this::onActionLoad);
|
||||||
|
newCanvasButton.setOnAction(this::onActionCreateCanvas);
|
||||||
|
scrollPane.setOnScroll(this::onScrollZoom);
|
||||||
|
scrollPane.setOnKeyPressed(this::onActionKeyPressed);
|
||||||
|
brushSizeLabel.textProperty().bind(brushSizeSlider.valueProperty().asString());
|
||||||
|
setupCanvas(drawingCanvas);
|
||||||
|
scrollPane.prefViewportHeightProperty().bind(pane.layoutYProperty());
|
||||||
|
scrollPane.prefViewportWidthProperty().bind(pane.layoutXProperty());
|
||||||
|
|
||||||
|
stopHostButton.setOnAction(this::onActionStopHost);
|
||||||
|
hostButton.setOnAction(this::onActionHost);
|
||||||
|
joinButton.setOnAction(this::onActionJoin);
|
||||||
|
disconnectButton.setOnAction(this::onActionDisconnect);
|
||||||
|
|
||||||
|
newCanvasButton.disableProperty().bind(hostButtonToggle.selectedProperty().not());
|
||||||
|
stopHostButton.disableProperty().bind(hostButtonToggle.selectedProperty().not());
|
||||||
|
disconnectButton.disableProperty().bind(hostButtonToggle.selectedProperty().not());
|
||||||
|
hostButtonToggle.setSelected(false);
|
||||||
|
mainPane.disableProperty().bind(hostButtonToggle.selectedProperty().not());
|
||||||
|
|
||||||
|
layerListView.setCellFactory(layerListView -> new layerListViewCell());
|
||||||
|
layerListView.setItems(layerObservableList);
|
||||||
|
layerListView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
|
//addLayerButton.setOnAction(this::onActionAddLayer);
|
||||||
|
//removeLayerButton.setOnAction(this::onActionRemoveLayer);
|
||||||
|
//layerListView.setOnMouseClicked(this::onActionSelectCanvas);
|
||||||
|
|
||||||
|
aboutMenuItem.setOnAction(this::onActionAbout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionAbout(ActionEvent actionEvent) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
alert.setTitle("About Yet Another Whiteboard App");
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText("Hey ! This is my first ever game jam. :D \n" + "I made this in my free time, though a lot of things " +
|
||||||
|
"still don't work. This is basically yet another collaborative whiteboard app, you can host, someone can" +
|
||||||
|
" join, create a canvas and draw lines. For now, it works pretty horrendously; but i'll keep maintaining it " +
|
||||||
|
"and try to make something functional. \nFor now, the layers system doesn't work.");
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionSelectCanvas(MouseEvent mouseEvent) {
|
||||||
|
layerListView.getSelectionModel().getSelectedItem().requestFocus();
|
||||||
|
layerListView.getSelectionModel().getSelectedItem().toFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionRemoveLayer(ActionEvent actionEvent) {
|
||||||
|
pane.getChildren().remove(layerListView.getSelectionModel().getSelectedItem());
|
||||||
|
layerObservableList.remove(layerListView.getSelectionModel().getSelectedItem());
|
||||||
|
layerListView.refresh();
|
||||||
|
layerListView.getSelectionModel().select(layerObservableList.getFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionAddLayer(ActionEvent actionEvent) {
|
||||||
|
Canvas newLayer = new Canvas(
|
||||||
|
layerListView.getSelectionModel().getSelectedItem().getWidth(),
|
||||||
|
layerListView.getSelectionModel().getSelectedItem().getHeight()
|
||||||
|
);
|
||||||
|
pane.getChildren().add(newLayer);
|
||||||
|
layerObservableList.addFirst(newLayer);
|
||||||
|
layerListView.getSelectionModel().select(newLayer);
|
||||||
|
setupCanvas(newLayer);
|
||||||
|
layerListView.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionStopHost(ActionEvent actionEvent) {
|
||||||
|
client.close();
|
||||||
|
if (this.server != null) {
|
||||||
|
try {
|
||||||
|
server.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
showErrorDialog(e, "Could not close server instance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hostButtonToggle.setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionDisconnect(ActionEvent actionEvent) {
|
||||||
|
client.close();
|
||||||
|
statusLabel.setText("Disconnected");
|
||||||
|
hostButtonToggle.setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void onActionJoin(ActionEvent actionEvent) {
|
||||||
|
TextInputDialog dialog = new TextInputDialog();
|
||||||
|
dialog.setTitle("Join");
|
||||||
|
dialog.setHeaderText(null);
|
||||||
|
dialog.setContentText("Enter distant address");
|
||||||
|
Optional<String> result = dialog.showAndWait();
|
||||||
|
if (result.isPresent()) {
|
||||||
|
try {
|
||||||
|
Matcher matcher = hostPortPattern.matcher(result.get());
|
||||||
|
matcher.matches();
|
||||||
|
String host = matcher.group(1);
|
||||||
|
String port = matcher.group(2);
|
||||||
|
int finalPort = port == null ? 8090 : Integer.parseInt(port);
|
||||||
|
connectClient(host, finalPort);
|
||||||
|
client.sendEvent(new Event(Event.LINELST, new JSONObject()));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
showErrorDialog(e, "Invalid distant address");
|
||||||
|
} catch (IOException e) {
|
||||||
|
showErrorDialog(e, "Could not connect to host");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionHost(ActionEvent actionEvent) {
|
||||||
|
TextInputDialog dialog = new TextInputDialog();
|
||||||
|
dialog.setTitle("Host");
|
||||||
|
dialog.setContentText("Which port do you want to use? (default: 8090)");
|
||||||
|
Optional<String> result = dialog.showAndWait();
|
||||||
|
if (result.isPresent()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
server = new DrawServer(result.get().isEmpty() ? 8090 : Integer.parseInt(result.get()));
|
||||||
|
Thread thread = new Thread(server::acceptClients);
|
||||||
|
thread.setDaemon(true);
|
||||||
|
thread.start();
|
||||||
|
connectClient("localhost", result.get().isEmpty() ? 8090 : Integer.parseInt(result.get()));
|
||||||
|
} catch (NumberFormatException | IOException e) {
|
||||||
|
showErrorDialog(e, "Invalid port number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectClient(String host, int port) throws IOException {
|
||||||
|
this.client = new DrawClient(host, port, this);
|
||||||
|
hostButtonToggle.setSelected(true);
|
||||||
|
client.sendAuthEvent(String.valueOf(new Random().nextInt()));
|
||||||
|
statusLabel.setText("Connected to %s:%d".formatted(host, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showErrorDialog(Exception ex, String context) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
|
alert.setTitle("An error occured!");
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(context);
|
||||||
|
|
||||||
|
StringWriter sw = new StringWriter();
|
||||||
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
|
ex.printStackTrace(pw);
|
||||||
|
String exceptionText = sw.toString();
|
||||||
|
|
||||||
|
Label label = new Label("The exception stacktrace was:");
|
||||||
|
|
||||||
|
TextArea textArea = new TextArea(exceptionText);
|
||||||
|
textArea.setEditable(false);
|
||||||
|
textArea.setWrapText(true);
|
||||||
|
|
||||||
|
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||||
|
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||||
|
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||||
|
|
||||||
|
GridPane expContent = new GridPane();
|
||||||
|
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
expContent.add(label, 0, 0);
|
||||||
|
expContent.add(textArea, 0, 1);
|
||||||
|
|
||||||
|
alert.getDialogPane().setExpandableContent(expContent);
|
||||||
|
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCanvas(Canvas canvas) {
|
||||||
|
canvas.requestFocus();
|
||||||
|
canvas.getGraphicsContext2D().setFill(Color.WHITE);
|
||||||
|
canvas.getGraphicsContext2D().fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
|
||||||
|
brushSizeSlider.setValue(1);
|
||||||
|
// canvas.setTranslateX(scrollPane.getWidth()/2);
|
||||||
|
// canvas.setTranslateY(scrollPane.getHeight()/2);
|
||||||
|
colorPicker.setValue(Color.BLACK);
|
||||||
|
canvas.setOnMouseDragged(this::printLine);
|
||||||
|
canvas.setOnMouseClicked(this::resetPos);
|
||||||
|
|
||||||
|
layerListView.getSelectionModel().select(drawingCanvas);
|
||||||
|
layerObservableList.add(drawingCanvas);
|
||||||
|
layerListView.refresh();
|
||||||
|
scrollPane.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
|
||||||
|
@Override
|
||||||
|
public void handle(ScrollEvent event) {
|
||||||
|
onScrollZoom(event);
|
||||||
|
event.consume();
|
||||||
|
}});
|
||||||
|
BoxBlur blur = new BoxBlur();
|
||||||
|
blur.setHeight(1);
|
||||||
|
blur.setWidth(1);
|
||||||
|
blur.setIterations(1);
|
||||||
|
drawingCanvas.getGraphicsContext2D().setEffect(blur);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionKeyPressed(KeyEvent keyEvent) {
|
||||||
|
keyEvent.consume();
|
||||||
|
if (keyEvent.isControlDown() && keyEvent.getCode().equals(KeyCode.Z)) {
|
||||||
|
System.out.println("CTRL Z");
|
||||||
|
System.out.println(lines);
|
||||||
|
System.out.println(lines);
|
||||||
|
lines.remove(lines.lastElement());
|
||||||
|
Canvas currentLayer = layerListView.getSelectionModel().getSelectedItem();
|
||||||
|
GraphicsContext gc = currentLayer.getGraphicsContext2D();
|
||||||
|
gc.setFill(Color.WHITE);
|
||||||
|
gc.fillRect(0, 0, currentLayer.getWidth(), currentLayer.getHeight());
|
||||||
|
gc.clearRect(0, 0, currentLayer.getWidth(), currentLayer.getHeight());
|
||||||
|
gc.fill();
|
||||||
|
for (Vector<Stroke> strokeVector : lines) {
|
||||||
|
for (Stroke stroke: strokeVector) {
|
||||||
|
stroke.draw(gc, stroke.getColor());
|
||||||
|
//System.out.println(stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyEvent.isControlDown() && keyEvent.getCode().equals(KeyCode.Y)) {
|
||||||
|
System.out.println("CTRL Y");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScrollZoom(ScrollEvent event) {
|
||||||
|
|
||||||
|
event.consume();
|
||||||
|
double SCALE_DELTA = 1.1;
|
||||||
|
if (event.getDeltaY() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.isControlDown()) {
|
||||||
|
double scaleFactor =
|
||||||
|
(event.getDeltaY() > 0) ? SCALE_DELTA : 1 / SCALE_DELTA;
|
||||||
|
|
||||||
|
|
||||||
|
Scale newScale = new Scale();
|
||||||
|
newScale.setX(drawingCanvas.getScaleX() * scaleFactor);
|
||||||
|
newScale.setY(drawingCanvas.getScaleY() * scaleFactor);
|
||||||
|
newScale.setPivotX(drawingCanvas.getScaleX());
|
||||||
|
newScale.setPivotY(drawingCanvas.getScaleY());
|
||||||
|
drawingCanvas.getTransforms().add(newScale);
|
||||||
|
|
||||||
|
pane.setPrefHeight(pane.getHeight()*scaleFactor);
|
||||||
|
pane.setPrefWidth(pane.getWidth()*scaleFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionCreateCanvas(ActionEvent actionEvent) {
|
||||||
|
try {
|
||||||
|
NewCanvasController controller = showNewStage("New canvas...", "new-canvas-view.fxml");
|
||||||
|
|
||||||
|
if (controller.isOk()) {
|
||||||
|
//drawingCanvas = new Canvas(controller.getCanvasWidth(), controller.getCanvasHeight());
|
||||||
|
//setupCanvas();
|
||||||
|
layerObservableList.clear();
|
||||||
|
drawingCanvas.setWidth(controller.getCanvasWidth());
|
||||||
|
drawingCanvas.setHeight(controller.getCanvasHeight());
|
||||||
|
clearDrawingCanvas();
|
||||||
|
client.sendEvent(new Event(Event.ADDCANVAS, new JSONObject().put("width", drawingCanvas.getWidth()).put("height", drawingCanvas.getHeight())));
|
||||||
|
System.out.println("New canvas created");
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T showNewStage(String title, String fxmlFileName) throws IOException {
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFileName));
|
||||||
|
Scene scene = new Scene(fxmlLoader.load());
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
stage.setTitle(title);
|
||||||
|
stage.setResizable(false);
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.showAndWait();
|
||||||
|
return fxmlLoader.getController();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionLoad(ActionEvent actionEvent) {
|
||||||
|
// drawingCanvas.getGraphicsContext2D().drawImage(lastSaved, 0, 0);
|
||||||
|
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
|
||||||
|
gc.clearRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
|
||||||
|
gc.setFill(Color.WHITE);
|
||||||
|
gc.fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
|
||||||
|
System.out.println(lastSaved.size());
|
||||||
|
for (Vector<Stroke> strokeVector : lastSaved) {
|
||||||
|
for (Stroke stroke: strokeVector) {
|
||||||
|
stroke.draw(gc, colorPicker.getValue());
|
||||||
|
System.out.println(stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strokes = (Vector<Stroke>) lastSaved.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionSave(ActionEvent actionEvent) {
|
||||||
|
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
|
||||||
|
lastSaved = (Vector<Line>) lines.clone();
|
||||||
|
System.out.println(lastSaved.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetPos(MouseEvent mouseEvent) {
|
||||||
|
posX = 0;
|
||||||
|
posY = 0;
|
||||||
|
mouseX = 0;
|
||||||
|
mouseY = 0;
|
||||||
|
Line line = new Line();
|
||||||
|
for (Stroke stroke: strokes) {
|
||||||
|
line.add(stroke);
|
||||||
|
}
|
||||||
|
lines.add((Line) line.clone());
|
||||||
|
System.out.println(lines.size());
|
||||||
|
System.out.println(lines);
|
||||||
|
System.out.println(new Event("ADDLINE", line.toJSONObject()));
|
||||||
|
strokes.clear();
|
||||||
|
|
||||||
|
client.sendEvent(new Event(Event.ADDLINE, line.toJSONObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printLine(MouseEvent mouseEvent) {
|
||||||
|
Canvas currentLayer = layerListView.getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
GraphicsContext gc = currentLayer.getGraphicsContext2D();
|
||||||
|
|
||||||
|
if (posX == 0 || posY == 0) {
|
||||||
|
posX = mouseEvent.getX();
|
||||||
|
posY = mouseEvent.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stroke stroke = new Stroke(posX, posY, mouseEvent.getX(), mouseEvent.getY(), brushSizeSlider.getValue(), colorPicker.getValue());
|
||||||
|
strokes.add(stroke);
|
||||||
|
|
||||||
|
if (mouseEvent.isPrimaryButtonDown()) {
|
||||||
|
stroke.draw(gc, colorPicker.getValue());
|
||||||
|
|
||||||
|
posX = mouseEvent.getX();
|
||||||
|
posY = mouseEvent.getY();
|
||||||
|
|
||||||
|
|
||||||
|
} else if (mouseEvent.isSecondaryButtonDown()) {
|
||||||
|
stroke.draw(gc, Color.WHITE);
|
||||||
|
|
||||||
|
posX = mouseEvent.getX();
|
||||||
|
posY = mouseEvent.getY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleEvent(Event event) {
|
||||||
|
System.out.println("Received new event !:" + event.toJSON());
|
||||||
|
String type = event.getType();
|
||||||
|
switch (type) {
|
||||||
|
case Event.LINE -> {
|
||||||
|
doImportLine(event.getContent());
|
||||||
|
}
|
||||||
|
case Event.DELLINE -> {
|
||||||
|
doDeleteLine(event.getContent());
|
||||||
|
}
|
||||||
|
case Event.CNVS -> {
|
||||||
|
doAddCanvas(event.getContent());
|
||||||
|
}
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doAddCanvas(JSONObject content) {
|
||||||
|
layerObservableList.clear();
|
||||||
|
drawingCanvas.setWidth(content.getDouble("width"));
|
||||||
|
drawingCanvas.setHeight(content.getDouble("height"));
|
||||||
|
clearDrawingCanvas();
|
||||||
|
|
||||||
|
setupCanvas(drawingCanvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDrawingCanvas() {
|
||||||
|
drawingCanvas.getGraphicsContext2D().setFill(Color.WHITE);
|
||||||
|
drawingCanvas.getGraphicsContext2D().fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
|
||||||
|
drawingCanvas.getGraphicsContext2D().fill();
|
||||||
|
pane.setScaleX(1);
|
||||||
|
pane.setScaleY(1);
|
||||||
|
layerObservableList.add(drawingCanvas);
|
||||||
|
layerListView.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDeleteLine(JSONObject content) {
|
||||||
|
lines.remove(Line.fromJSONArray(content.getJSONArray("line")));
|
||||||
|
|
||||||
|
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
|
||||||
|
lines.sort(new Comparator<Line>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Line o1, Line o2) {
|
||||||
|
return Integer.compare(o2.getTimestamp(), o1.getTimestamp());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (Line line: lines) {
|
||||||
|
for (Stroke stroke: line) {
|
||||||
|
stroke.draw(gc, colorPicker.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doImportLine(JSONObject content) {
|
||||||
|
Line importedLine = Line.fromJSONArray(content.getJSONArray("line"));
|
||||||
|
this.lines.add(importedLine);
|
||||||
|
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
|
||||||
|
gc.clearRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
|
||||||
|
lines.sort(new Comparator<Line>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Line o1, Line o2) {
|
||||||
|
if (o1.getTimestamp() < o2.getTimestamp()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (Stroke stroke: importedLine) {
|
||||||
|
stroke.draw(gc, stroke.getColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package fr.emiko.graphicalapp;
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class HelloApplication extends Application {
|
|
||||||
@Override
|
|
||||||
public void start(Stage stage) throws IOException {
|
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
|
|
||||||
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
|
|
||||||
stage.setTitle("Hello!");
|
|
||||||
stage.setScene(scene);
|
|
||||||
stage.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
launch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package fr.emiko.graphicalapp;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
|
|
||||||
public class HelloController {
|
|
||||||
@FXML
|
|
||||||
private Label welcomeText;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
protected void onHelloButtonClick() {
|
|
||||||
welcomeText.setText("Welcome to JavaFX Application!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package fr.emiko.graphicalapp;
|
||||||
|
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import net.synedra.validatorfx.Check;
|
||||||
|
import net.synedra.validatorfx.Validator;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class NewCanvasController implements Initializable {
|
||||||
|
public TextField heightTextField;
|
||||||
|
public TextField widthTextField;
|
||||||
|
public Button createButton;
|
||||||
|
public Button cancelButton;
|
||||||
|
private double canvasWidth;
|
||||||
|
private double canvasHeight;
|
||||||
|
private boolean ok = false;
|
||||||
|
private Validator validator = new Validator();
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
createButton.setOnAction(this::create);
|
||||||
|
createButton.disableProperty().bind(validator.containsErrorsProperty());
|
||||||
|
cancelButton.setOnAction(this::close);
|
||||||
|
widthTextField.setOnAction(this::create);
|
||||||
|
validator.createCheck()
|
||||||
|
.decorates(createButton)
|
||||||
|
.dependsOn("width", widthTextField.textProperty())
|
||||||
|
.dependsOn("height", heightTextField.textProperty())
|
||||||
|
.withMethod(this::checkWidthHeight)
|
||||||
|
.immediate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkWidthHeight(Check.Context context) {
|
||||||
|
Pattern pattern = Pattern.compile("\\d+");
|
||||||
|
Matcher widthMatcher = pattern.matcher(widthTextField.getText());
|
||||||
|
Matcher heightMatcher = pattern.matcher(heightTextField.getText());
|
||||||
|
if (!widthMatcher.matches() || !heightMatcher.matches()) {
|
||||||
|
context.error("Width and height fields must contain only numbers.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCanvasWidth() {
|
||||||
|
return canvasWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getCanvasHeight() {
|
||||||
|
return canvasHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOk() {
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(ActionEvent actionEvent) {
|
||||||
|
((Stage) createButton.getScene().getWindow()).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void create(ActionEvent actionEvent) {
|
||||||
|
this.ok = true;
|
||||||
|
this.canvasWidth = Double.parseDouble(widthTextField.getText());
|
||||||
|
this.canvasHeight = Double.parseDouble(heightTextField.getText());
|
||||||
|
((Stage) createButton.getScene().getWindow()).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package fr.emiko.graphicsElement;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
public class Line extends Vector<Stroke> {
|
||||||
|
private int timestamp;
|
||||||
|
|
||||||
|
public JSONObject toJSONObject() {
|
||||||
|
JSONArray jsonArray = new JSONArray();
|
||||||
|
for (Stroke stroke: this) {
|
||||||
|
jsonArray.put(stroke.toJSON());
|
||||||
|
}
|
||||||
|
return new JSONObject().put("line", jsonArray).put("timestamp", timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Line fromJSONArray(JSONArray jsonArray) {
|
||||||
|
Line line = new Line();
|
||||||
|
for (int i = 0; i < jsonArray.length(); i++) {
|
||||||
|
line.add(Stroke.fromJSON(jsonArray.getString(i)));
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(int timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package fr.emiko.graphicsElement;
|
||||||
|
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Path;
|
||||||
|
import javafx.scene.shape.StrokeLineCap;
|
||||||
|
import javafx.scene.shape.StrokeLineJoin;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Stroke {
|
||||||
|
private final double fromX;
|
||||||
|
private final double fromY;
|
||||||
|
private final double toX;
|
||||||
|
private final double toY;
|
||||||
|
private final double brushSize;
|
||||||
|
private final Color color;
|
||||||
|
|
||||||
|
public Stroke (double fromX, double fromY, double toX, double toY, double brushSize, Color color) {
|
||||||
|
this.fromX = fromX;
|
||||||
|
this.fromY = fromY;
|
||||||
|
this.toX = toX;
|
||||||
|
this.toY = toY;
|
||||||
|
this.brushSize = brushSize;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stroke fromJSON(String jsonStroke) {
|
||||||
|
JSONObject jsonObject = new JSONObject(jsonStroke);
|
||||||
|
return new Stroke(
|
||||||
|
jsonObject.getDouble("fromX"),
|
||||||
|
jsonObject.getDouble("fromY"),
|
||||||
|
jsonObject.getDouble("toX"),
|
||||||
|
jsonObject.getDouble("toY"),
|
||||||
|
jsonObject.getDouble("brushSize"),
|
||||||
|
Color.valueOf(jsonObject.get("color").toString())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw (GraphicsContext g, javafx.scene.paint.Color color) {
|
||||||
|
g.setStroke(color);
|
||||||
|
g.setLineCap(StrokeLineCap.ROUND);
|
||||||
|
g.setMiterLimit(1);
|
||||||
|
g.setLineWidth(brushSize);
|
||||||
|
g.setLineJoin(StrokeLineJoin.ROUND);
|
||||||
|
g.beginPath();
|
||||||
|
g.moveTo(fromX, fromY);
|
||||||
|
g.lineTo(toX, toY);
|
||||||
|
g.closePath();
|
||||||
|
g.stroke();
|
||||||
|
g.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString () {
|
||||||
|
// return "Stroke{fromX=%f, fromY=%f, toX=%f, toY=%f, brushSize=%f}".formatted(fromX, fromY, toX, toY, brushSize);
|
||||||
|
return this.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject toJSONObject() {
|
||||||
|
return new JSONObject()
|
||||||
|
.put("fromX", fromX)
|
||||||
|
.put("fromY", fromY)
|
||||||
|
.put("toX", toX)
|
||||||
|
.put("toY", toY)
|
||||||
|
.put("brushSize", brushSize)
|
||||||
|
.put("color", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toJSON() {
|
||||||
|
return toJSONObject().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Stroke stroke = (Stroke) o;
|
||||||
|
return Double.compare(fromX, stroke.fromX) == 0 && Double.compare(fromY, stroke.fromY) == 0 && Double.compare(toX, stroke.toX) == 0 && Double.compare(toY, stroke.toY) == 0 && Double.compare(brushSize, stroke.brushSize) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package fr.emiko.graphicsElement;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
public class layerListViewCell extends ListCell<Canvas> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Canvas item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
if (empty) {
|
||||||
|
setGraphic(null);
|
||||||
|
} else {
|
||||||
|
updateItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateItem(Canvas item) {
|
||||||
|
ImageView imageView = new ImageView();
|
||||||
|
imageView.setImage(item.snapshot(null, null));
|
||||||
|
imageView.setFitHeight(25);
|
||||||
|
imageView.setFitWidth(25);
|
||||||
|
Text text = new Text(item.toString());
|
||||||
|
HBox hbox = new HBox(imageView, text);
|
||||||
|
hbox.setSpacing(10);
|
||||||
|
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
setGraphic(hbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
175
graphical-app/src/main/java/fr/emiko/net/ClientTCP.java
Normal file
175
graphical-app/src/main/java/fr/emiko/net/ClientTCP.java
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
package fr.emiko.net;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client TCP : envoie des chaines de caractères à un serveur et lit les chaines en retour.
|
||||||
|
*/
|
||||||
|
public class ClientTCP {
|
||||||
|
/** Couleur rouge */
|
||||||
|
public static final String RED = "\u001b[31m";
|
||||||
|
/** Couleur bleue */
|
||||||
|
public static final String BLUE = "\u001b[34m";
|
||||||
|
/** Couleur standard */
|
||||||
|
public static final String RST = "\u001b[0m";
|
||||||
|
/** Fin de message */
|
||||||
|
public static final String END_MESSAGE = "fin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket connecté au serveur
|
||||||
|
*/
|
||||||
|
protected Socket sock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flux de caractères UTF-8 en sortie
|
||||||
|
*/
|
||||||
|
protected PrintStream out;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flux de caractères UTF-8 en entrée
|
||||||
|
*/
|
||||||
|
protected BufferedReader in;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chaine de caractères "ip:port" du client
|
||||||
|
*/
|
||||||
|
protected String ipPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le client est-il connecté ?
|
||||||
|
*/
|
||||||
|
protected boolean connected;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Programme principal [Déprécié]
|
||||||
|
* @param args Arguments
|
||||||
|
* @throws Exception Si la connexion échoue
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
|
ClientTCP client = new ClientTCP("localhost", 2024);
|
||||||
|
Thread envoi = new Thread(client::sendLoop);
|
||||||
|
Thread reception = new Thread(client::receiveLoop);
|
||||||
|
envoi.start();
|
||||||
|
reception.start();
|
||||||
|
envoi.join();
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Le constructeur ouvre la connexion TCP au serveur <code>host:port</code>
|
||||||
|
* et récupère les flux de caractères en entrée {@link #in} et sortie {@link #out}
|
||||||
|
*import static rtgre.chat.ChatApplication.LOGGER;
|
||||||
|
* @param host IP ou nom de domaine du serveur
|
||||||
|
* @param port port d'écoute du serveur
|
||||||
|
* @throws IOException si la connexion échoue ou si les flux ne sont pas récupérables
|
||||||
|
*/
|
||||||
|
public ClientTCP(String host, int port) throws IOException {
|
||||||
|
sock = new Socket(host, port);
|
||||||
|
ipPort = "%s:%d".formatted(sock.getLocalAddress().getHostAddress(), sock.getLocalPort());
|
||||||
|
this.connected = true;
|
||||||
|
OutputStream os = sock.getOutputStream();
|
||||||
|
InputStream is = sock.getInputStream();
|
||||||
|
out = new PrintStream(os, true, StandardCharsets.UTF_8);
|
||||||
|
in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8), 2048);
|
||||||
|
Thread rcLoop = new Thread(this::receiveLoop);
|
||||||
|
rcLoop.setDaemon(true);
|
||||||
|
rcLoop.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoie une chaine de caractères
|
||||||
|
*
|
||||||
|
* @param message chaine de caractères à transmettre
|
||||||
|
* @throws IOException lorsqu'une erreur sur le flux de sortie est détectée
|
||||||
|
*/
|
||||||
|
public void send(String message) throws IOException {
|
||||||
|
out.println(message);
|
||||||
|
if (out.checkError()) {
|
||||||
|
throw new IOException("Output stream error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter de connected
|
||||||
|
* @return L'état de connected
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter de connected
|
||||||
|
* @param connected L'utilisateur est-il connecté ?
|
||||||
|
*/
|
||||||
|
public void setConnected(boolean connected) {
|
||||||
|
this.connected = connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attente d'une chaine de caractères en entrée.
|
||||||
|
*
|
||||||
|
* @return chaine de caractères reçue
|
||||||
|
* @throws IOException lorsque la fin du flux est atteinte
|
||||||
|
*/
|
||||||
|
public String receive() throws IOException {
|
||||||
|
String message = in.readLine();
|
||||||
|
if (message == null) {
|
||||||
|
throw new IOException("End of the stream has been reached");
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fermeture de la connexion TCP
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
sock.close();
|
||||||
|
this.connected = false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boucle d'envoi de messages
|
||||||
|
*/
|
||||||
|
public void sendLoop() {
|
||||||
|
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
connected = true;
|
||||||
|
try {
|
||||||
|
while (connected) {
|
||||||
|
String message = stdIn.readLine();
|
||||||
|
if (message == null) { // fin du flux stdIn
|
||||||
|
message = END_MESSAGE;
|
||||||
|
}
|
||||||
|
this.send(message);
|
||||||
|
if (END_MESSAGE.equals(message)) {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boucle de réception de messages
|
||||||
|
*/
|
||||||
|
public void receiveLoop() {
|
||||||
|
connected = true;
|
||||||
|
try {
|
||||||
|
while (connected) {
|
||||||
|
String message = this.receive();
|
||||||
|
System.out.println("Message received: " + message);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
graphical-app/src/main/java/fr/emiko/net/DrawClient.java
Normal file
68
graphical-app/src/main/java/fr/emiko/net/DrawClient.java
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package fr.emiko.net;
|
||||||
|
|
||||||
|
import fr.emiko.graphicalapp.DrawController;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DrawClient extends ClientTCP{
|
||||||
|
private final DrawController listener;
|
||||||
|
|
||||||
|
public DrawClient(String host, int port, DrawController listener) throws IOException {
|
||||||
|
super(host, port);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoi d'un évènement, sérialisé dans sa représentation JSON, au serveur.
|
||||||
|
* @param event L'évènement à envoyer
|
||||||
|
*/
|
||||||
|
public void sendEvent(Event event) {
|
||||||
|
connected = true;
|
||||||
|
try {
|
||||||
|
String message = event.toJSON();
|
||||||
|
if (message == null) { // fin du flux stdIn
|
||||||
|
message = END_MESSAGE;
|
||||||
|
}
|
||||||
|
this.send(message);
|
||||||
|
if (END_MESSAGE.equals(message)) {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boucle de réception des messages : chaque message est un évènement sérialisé en JSON, qui est transféré à ChatController.handleEvent(rtgre.modeles.Event) pour traitement.
|
||||||
|
* Si le message n'est pas conforme (format JSON), la connection est stoppée.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receiveLoop() {
|
||||||
|
try {
|
||||||
|
while (connected) {
|
||||||
|
String message = this.receive();
|
||||||
|
if (listener != null) {
|
||||||
|
Platform.runLater(() -> listener.handleEvent(Event.fromJSON(message)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
connected = false;
|
||||||
|
} finally {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void sendAuthEvent(String login) {
|
||||||
|
try {
|
||||||
|
this.send(new Event(Event.AUTH, new JSONObject().put("username", login)).toJSON());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
254
graphical-app/src/main/java/fr/emiko/net/DrawServer.java
Normal file
254
graphical-app/src/main/java/fr/emiko/net/DrawServer.java
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
package fr.emiko.net;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import fr.emiko.graphicsElement.Line;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class DrawServer {
|
||||||
|
|
||||||
|
private ServerSocket passiveSocket;
|
||||||
|
private Vector<DrawClientHandler> clientList = new Vector<DrawClientHandler>();
|
||||||
|
private Vector<Line> lines;
|
||||||
|
private double canvasWidth;
|
||||||
|
private double canvasHeight;
|
||||||
|
public DrawServer(int port) throws IOException {
|
||||||
|
passiveSocket = new ServerSocket(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
DrawServer server = new DrawServer(8090);
|
||||||
|
server.acceptClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acceptClients() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Socket sock = passiveSocket.accept();
|
||||||
|
handleNewClient(sock);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void removeClient(DrawClientHandler client) {
|
||||||
|
clientList.remove(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleNewClient(Socket sock) throws IOException {
|
||||||
|
DrawClientHandler client = new DrawClientHandler(sock);
|
||||||
|
clientList.add(client);
|
||||||
|
|
||||||
|
Thread clientLoop = new Thread(client::eventReceiveLoop);
|
||||||
|
clientLoop.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ferme la connexion du serveur, en fermant la connexion auprès de tous ses clients, puis en fermant son socket en écoute passive.
|
||||||
|
* @throws IOException si la connexion
|
||||||
|
*/
|
||||||
|
public void close() throws IOException {
|
||||||
|
for (DrawClientHandler client : clientList) {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
passiveSocket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private class DrawClientHandler {
|
||||||
|
|
||||||
|
/** Message de fin d'une connexion */
|
||||||
|
public static final String END_MESSAGE = "fin";
|
||||||
|
/**
|
||||||
|
* Socket connecté au client
|
||||||
|
*/
|
||||||
|
private Socket sock;
|
||||||
|
/**
|
||||||
|
* Flux de caractères en sortie
|
||||||
|
*/
|
||||||
|
private PrintStream out;
|
||||||
|
/**
|
||||||
|
* Flux de caractères en entrée
|
||||||
|
*/
|
||||||
|
private BufferedReader in;
|
||||||
|
/**
|
||||||
|
* Chaine de caractères "ip:port" du client
|
||||||
|
*/
|
||||||
|
private String ipPort;
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise les attributs {@link #sock} (socket connecté au client),
|
||||||
|
* {@link #out} (flux de caractères UTF-8 en sortie) et
|
||||||
|
* {@link #in} (flux de caractères UTF-8 en entrée).
|
||||||
|
*
|
||||||
|
* @param sock socket connecté au client
|
||||||
|
* @throws IOException si la connexion ne peut être établie ou si les flux ne peuvent être récupérés
|
||||||
|
*/
|
||||||
|
public DrawClientHandler(Socket sock) throws IOException {
|
||||||
|
this.sock = sock;
|
||||||
|
this.ipPort = "%s:%d".formatted(sock.getInetAddress().getHostAddress(), sock.getPort());
|
||||||
|
OutputStream os = sock.getOutputStream();
|
||||||
|
InputStream is = sock.getInputStream();
|
||||||
|
this.out = new PrintStream(os, true, StandardCharsets.UTF_8);
|
||||||
|
this.in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boucle de réception d'évènement : réceptionne les messages reçus et les délèguent à `handleEvent(java.lang.String)` pour les interpréter
|
||||||
|
*/
|
||||||
|
public void eventReceiveLoop() {
|
||||||
|
try {
|
||||||
|
String message = null;
|
||||||
|
while (!END_MESSAGE.equals(message)) {
|
||||||
|
message = in.readLine();
|
||||||
|
if (message == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
System.out.println("Réception de message : " + message);
|
||||||
|
try {
|
||||||
|
if (!handleEvent(message)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traitement d'un évènement. Ventile vers les méthodes traitant chaque type d'évènement.
|
||||||
|
* @param message objet évènement sous la forme d'une chaine JSON brute de réception
|
||||||
|
* @return `false` si l'évènement est de type Event.QUIT , `true` pour tous les autres types.
|
||||||
|
* @throws JSONException si l'objet JSON n'est pas conforme
|
||||||
|
* @throws IllegalStateException si l'authentification n'est pas effectuée
|
||||||
|
*/
|
||||||
|
private boolean handleEvent(String message) throws JSONException, IllegalStateException {
|
||||||
|
Event event = Event.fromJSON(message);
|
||||||
|
switch (event.getType()) {
|
||||||
|
case Event.AUTH -> {
|
||||||
|
doLogin(event.getContent());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Event.ADDLINE -> {
|
||||||
|
doAddLine(event.getContent());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Event.DELLINE -> {
|
||||||
|
doDelLine(event.getContent());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Event.LINELST -> {
|
||||||
|
doSendLines();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Event.ADDCANVAS -> {
|
||||||
|
doAddCanvas(event.getContent());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doAddCanvas(JSONObject content) throws JSONException {
|
||||||
|
canvasWidth = content.getDouble("width");
|
||||||
|
canvasHeight = content.getDouble("height");
|
||||||
|
sendAllOtherUsers(new Event(Event.CNVS, content));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDelLine(JSONObject content) {
|
||||||
|
Line line = Line.fromJSONArray(content.getJSONArray("line"));
|
||||||
|
this.user.getLines().remove(line);
|
||||||
|
|
||||||
|
sendAllOtherUsers(new Event("DELLINE", line.toJSONObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doAddLine(JSONObject content) {
|
||||||
|
try {
|
||||||
|
System.out.println(Line.fromJSONArray(content.getJSONArray("line")));
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Line line = Line.fromJSONArray(content.getJSONArray("line"));
|
||||||
|
this.user.getLines().add(line);
|
||||||
|
sendAllOtherUsers(new Event("LINE", line.toJSONObject()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendAllOtherUsers(Event event) {
|
||||||
|
System.out.println("current user: " + this.user.getUsername());
|
||||||
|
for (DrawClientHandler client : clientList) {
|
||||||
|
System.out.println("calculating user: " + client.user.getUsername());
|
||||||
|
if (client.user != this.user) {
|
||||||
|
System.out.println("found user: " + client.user.getUsername());
|
||||||
|
sendEvent(client, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendEvent(DrawClientHandler client, Event event) {
|
||||||
|
String jsonEvent = event.toJSON();
|
||||||
|
client.out.println(jsonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSendLines() {
|
||||||
|
out.println(
|
||||||
|
new Event("CNVS", new JSONObject()
|
||||||
|
.put("width", canvasWidth)
|
||||||
|
.put("height", canvasHeight))
|
||||||
|
);
|
||||||
|
|
||||||
|
Vector<Line> lines = new Vector<>();
|
||||||
|
for (DrawClientHandler client: clientList) {
|
||||||
|
for (Line line: client.user.getLines()) {
|
||||||
|
lines.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Line line: lines) {
|
||||||
|
out.println(new Event("LINE", line.toJSONObject()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doLogin(JSONObject content) {
|
||||||
|
this.user = new User(content.getString("username"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
sock.close();
|
||||||
|
removeClient(this);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
52
graphical-app/src/main/java/fr/emiko/net/Event.java
Normal file
52
graphical-app/src/main/java/fr/emiko/net/Event.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package fr.emiko.net;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class Event {
|
||||||
|
public static final String AUTH = "AUTH";
|
||||||
|
public static final String LSTLINE = "LSTLINE";
|
||||||
|
public static final String ADDLINE = "ADDLINE";
|
||||||
|
public static final String DELLINE = "DELLINE";
|
||||||
|
public static final String LINE = "LINE";
|
||||||
|
public static final String LINELST = "LINELST";
|
||||||
|
public static final String ADDCANVAS = "ADDCANVAS";
|
||||||
|
public static final String CNVS = "CNVS";
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
private JSONObject content;
|
||||||
|
|
||||||
|
public Event(String type, JSONObject content) {
|
||||||
|
this.type = type;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject toJSONObject() {
|
||||||
|
return new JSONObject()
|
||||||
|
.put("type", type)
|
||||||
|
.put("content", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toJSON() {
|
||||||
|
return toJSONObject().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Event fromJSON(String obj) {
|
||||||
|
JSONObject jobj = new JSONObject(obj);
|
||||||
|
String type = jobj.getString("type");
|
||||||
|
JSONObject content = jobj.getJSONObject("content");
|
||||||
|
return new Event(type, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.toJSON();
|
||||||
|
}
|
||||||
|
}
|
||||||
32
graphical-app/src/main/java/fr/emiko/net/User.java
Normal file
32
graphical-app/src/main/java/fr/emiko/net/User.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package fr.emiko.net;
|
||||||
|
|
||||||
|
import fr.emiko.graphicsElement.Line;
|
||||||
|
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
public class User {
|
||||||
|
private String username;
|
||||||
|
private String hashedPassword = "";
|
||||||
|
private Vector<Line> lines = new Vector<Line>();
|
||||||
|
private boolean connected;
|
||||||
|
|
||||||
|
public User(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector<Line> getLines() {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLines(Vector<Line> lines) {
|
||||||
|
this.lines = lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
module fr.emiko.graphicalapp {
|
module fr.emiko.graphicalapp {
|
||||||
requires javafx.controls;
|
requires javafx.controls;
|
||||||
requires javafx.fxml;
|
requires javafx.fxml;
|
||||||
|
requires java.desktop;
|
||||||
|
requires org.controlsfx.controls;
|
||||||
|
requires net.synedra.validatorfx;
|
||||||
|
requires org.json;
|
||||||
|
|
||||||
|
|
||||||
opens fr.emiko.graphicalapp to javafx.fxml;
|
opens fr.emiko.graphicalapp to javafx.fxml;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.canvas.Canvas?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ColorPicker?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.control.Menu?>
|
||||||
|
<?import javafx.scene.control.MenuBar?>
|
||||||
|
<?import javafx.scene.control.MenuItem?>
|
||||||
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
|
<?import javafx.scene.control.Separator?>
|
||||||
|
<?import javafx.scene.control.Slider?>
|
||||||
|
<?import javafx.scene.control.SplitPane?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
|
<VBox alignment="CENTER" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fr.emiko.graphicalapp.DrawController">
|
||||||
|
<children>
|
||||||
|
<MenuBar VBox.vgrow="NEVER">
|
||||||
|
<menus>
|
||||||
|
<Menu mnemonicParsing="false" text="File">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="newCanvasButton" mnemonicParsing="false" text="New canvas" />
|
||||||
|
</items>
|
||||||
|
</Menu>
|
||||||
|
<Menu mnemonicParsing="false" text="Connect">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="hostButton" mnemonicParsing="false" text="Host" />
|
||||||
|
<MenuItem fx:id="joinButton" mnemonicParsing="false" text="Join" />
|
||||||
|
<MenuItem fx:id="disconnectButton" mnemonicParsing="false" text="Disconnect" />
|
||||||
|
<MenuItem fx:id="stopHostButton" mnemonicParsing="false" text="Stop host" />
|
||||||
|
</items>
|
||||||
|
</Menu>
|
||||||
|
<Menu mnemonicParsing="false" text="Edit">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="saveButton" mnemonicParsing="false" text="Save" />
|
||||||
|
<MenuItem fx:id="loadButton" mnemonicParsing="false" text="Load" />
|
||||||
|
</items>
|
||||||
|
</Menu>
|
||||||
|
<Menu mnemonicParsing="false" text="Help">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="aboutMenuItem" mnemonicParsing="false" text="About" />
|
||||||
|
</items>
|
||||||
|
</Menu>
|
||||||
|
</menus>
|
||||||
|
</MenuBar>
|
||||||
|
<SplitPane fx:id="mainPane" dividerPositions="0.16948784722222218" VBox.vgrow="ALWAYS">
|
||||||
|
<items>
|
||||||
|
<VBox spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<GridPane>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Label fx:id="brushSizeLabel" text="1">
|
||||||
|
<graphic>
|
||||||
|
<Label text="Brush Size :" />
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
<Slider fx:id="brushSizeSlider" blockIncrement="1.0" majorTickUnit="5.0" max="10.0" minorTickCount="9" showTickLabels="true" showTickMarks="true" snapToTicks="true" GridPane.rowIndex="1" />
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
<ColorPicker fx:id="colorPicker" editable="true" />
|
||||||
|
<Separator />
|
||||||
|
<GridPane>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||||
|
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Button fx:id="addLayerButton" minHeight="25.0" minWidth="25.0" mnemonicParsing="false" text="+" />
|
||||||
|
<Button fx:id="removeLayerButton" minHeight="25.0" minWidth="25.0" mnemonicParsing="false" text="-" GridPane.columnIndex="1" />
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
<ListView fx:id="layerListView" prefHeight="200.0" prefWidth="200.0" />
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</VBox>
|
||||||
|
<ScrollPane fx:id="scrollPane" cache="true" cacheHint="QUALITY" depthTest="ENABLE" hbarPolicy="ALWAYS" style="-fx-background-color: #666666; -fx-background: #666666;" vbarPolicy="ALWAYS">
|
||||||
|
<content>
|
||||||
|
<GridPane>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="10.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" valignment="CENTER" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Pane fx:id="pane">
|
||||||
|
<children>
|
||||||
|
<Canvas fx:id="drawingCanvas" height="1.0" nodeOrientation="INHERIT" translateX="10.0" translateY="10.0" width="1.0" />
|
||||||
|
</children>
|
||||||
|
</Pane>
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
||||||
|
</content>
|
||||||
|
</ScrollPane>
|
||||||
|
</items>
|
||||||
|
</SplitPane>
|
||||||
|
<HBox>
|
||||||
|
<children>
|
||||||
|
<Label text="Status : " />
|
||||||
|
<Label fx:id="statusLabel" text="Disconnected" />
|
||||||
|
</children>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
|
||||||
|
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
|
|
||||||
fx:controller="fr.emiko.graphicalapp.HelloController">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
|
|
||||||
</padding>
|
|
||||||
|
|
||||||
<Label fx:id="welcomeText"/>
|
|
||||||
<Button text="Hello!" onAction="#onHelloButtonClick"/>
|
|
||||||
</VBox>
|
|
||||||
BIN
graphical-app/src/main/resources/fr/emiko/graphicalapp/icon.png
Normal file
BIN
graphical-app/src/main/resources/fr/emiko/graphicalapp/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
|
<VBox xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fr.emiko.graphicalapp.NewCanvasController">
|
||||||
|
<children>
|
||||||
|
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="200.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Label text="Height :" GridPane.hgrow="ALWAYS" />
|
||||||
|
<Label text="Width :" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" />
|
||||||
|
<TextField fx:id="heightTextField" promptText="300" GridPane.columnIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</TextField>
|
||||||
|
<TextField fx:id="widthTextField" promptText="300" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</TextField>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</GridPane>
|
||||||
|
<HBox alignment="TOP_CENTER" spacing="10.0" VBox.vgrow="ALWAYS">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="createButton" mnemonicParsing="false" text="Create">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="cancelButton" mnemonicParsing="false" text="Cancel" />
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue