Browse Source

first commit

arnaud.lewandowski@univ-littoral.fr 2 months ago
commit
31571dc0ee

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/.idea/

+ 55 - 0
pom.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.ulco</groupId>
+    <artifactId>Minipaint</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>15</maven.compiler.source>
+        <maven.compiler.target>15</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>15.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testfx</groupId>
+            <artifactId>testfx-junit</artifactId>
+            <version>4.0.15-alpha</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testfx</groupId>
+            <artifactId>testfx-core</artifactId>
+            <version>4.0.16-alpha</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.openjfx</groupId>
+                <artifactId>javafx-maven-plugin</artifactId>
+                <version>0.0.3</version>
+                <configuration>
+                    <mainClass>drawing.PaintApplication</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 18 - 0
src/main/java/drawing/ClearButtonHandler.java

@@ -0,0 +1,18 @@
+package drawing;
+
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+
+public class ClearButtonHandler implements EventHandler<ActionEvent> {
+
+    private DrawingPane drawingPane;
+
+    public ClearButtonHandler(DrawingPane drawingPane) {
+        this.drawingPane = drawingPane;
+    }
+
+    @Override
+    public void handle(ActionEvent event) {
+        this.drawingPane.clear();
+    }
+}

+ 59 - 0
src/main/java/drawing/DrawingPane.java

@@ -0,0 +1,59 @@
+package drawing;
+
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.Shape;
+
+import java.util.ArrayList;
+
+/**
+ * Created by lewandowski on 20/12/2020.
+ */
+public class DrawingPane extends Pane {
+
+    private MouseMoveHandler mouseMoveHandler;
+
+    private ArrayList<Shape> shapes;
+
+    public DrawingPane() {
+        clipChildren();
+        shapes = new ArrayList<>();
+        mouseMoveHandler = new MouseMoveHandler(this);
+    }
+
+
+    /**
+     * Clips the children of this {@link Region} to its current size.
+     * This requires attaching a change listener to the region’s layout bounds,
+     * as JavaFX does not currently provide any built-in way to clip children.
+     */
+    void clipChildren() {
+        final Rectangle outputClip = new Rectangle();
+        this.setClip(outputClip);
+
+        this.layoutBoundsProperty().addListener((ov, oldValue, newValue) -> {
+            outputClip.setWidth(newValue.getWidth());
+            outputClip.setHeight(newValue.getHeight());
+        });
+    }
+
+    public void addShape(Shape shape) {
+        shapes.add(shape);
+        this.getChildren().add(shape);
+    }
+
+    public void removeShape(Shape shape) {
+        shapes.remove(shape);
+        this.getChildren().remove(shape);
+    }
+
+    public ArrayList<Shape> getShapes() {
+        return shapes;
+    }
+
+    public void clear() {
+        this.getChildren().removeAll(shapes);
+        shapes.clear();
+    }
+}

+ 25 - 0
src/main/java/drawing/EllipseButtonHandler.java

@@ -0,0 +1,25 @@
+package drawing;
+
+import javafx.scene.shape.Ellipse;
+import javafx.scene.shape.Shape;
+
+/**
+ * Created by lewandowski on 20/12/2020.
+ */
+public class EllipseButtonHandler extends ShapeButtonHandler {
+
+    public EllipseButtonHandler(DrawingPane drawingPane) {
+        super(drawingPane);
+    }
+
+    @Override
+    protected Shape createShape() {
+        double x = Math.min(originX, destinationX);
+        double y = Math.min(originY, destinationY);
+        double width = Math.abs(destinationX - originX);
+        double height = Math.abs(destinationY - originY);
+        Ellipse ellipse = new Ellipse(x + width / 2, y + height / 2, width / 2, height / 2);
+        ellipse.getStyleClass().add("ellipse");
+        return ellipse;
+    }
+}

+ 64 - 0
src/main/java/drawing/MouseMoveHandler.java

@@ -0,0 +1,64 @@
+package drawing;
+
+import javafx.event.EventHandler;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.shape.Shape;
+
+/**
+ * Created by lewandowski on 20/12/2020.
+ */
+public class MouseMoveHandler implements EventHandler<MouseEvent> {
+
+    private DrawingPane drawingPane;
+
+    private double orgSceneX;
+    private double orgSceneY;
+    private double orgTranslateX;
+    private double orgTranslateY;
+
+    private Shape selectedShape;
+
+    public MouseMoveHandler(DrawingPane drawingPane) {
+        this.drawingPane = drawingPane;
+        drawingPane.addEventHandler(MouseEvent.MOUSE_PRESSED, this);
+        drawingPane.addEventHandler(MouseEvent.MOUSE_DRAGGED, this);
+        drawingPane.addEventHandler(MouseEvent.MOUSE_RELEASED, this);
+    }
+
+    @Override
+    public void handle(MouseEvent event) {
+
+        if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
+            orgSceneX = event.getSceneX();
+            orgSceneY = event.getSceneY();
+
+            for (Shape shape : drawingPane.getShapes()) {
+                if (shape.getBoundsInParent().contains(event.getX(), event.getY())) {
+                    selectedShape = shape;
+                    break;
+                }
+            }
+
+            orgTranslateX = selectedShape == null ? 0 : selectedShape.getTranslateX();
+            orgTranslateY = selectedShape == null ? 0 : selectedShape.getTranslateY();
+
+        }
+
+        if (event.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
+            if (selectedShape == null)
+                return;
+
+            double offsetX = event.getSceneX() - orgSceneX;
+            double offsetY = event.getSceneY() - orgSceneY;
+            double newTranslateX = orgTranslateX + offsetX;
+            double newTranslateY = orgTranslateY + offsetY;
+
+            selectedShape.setTranslateX(newTranslateX);
+            selectedShape.setTranslateY(newTranslateY);
+        }
+
+        if (event.getEventType().equals(MouseEvent.MOUSE_RELEASED)) {
+            selectedShape = null;
+        }
+    }
+}

+ 66 - 0
src/main/java/drawing/PaintApplication.java

@@ -0,0 +1,66 @@
+package drawing;
+
+import javafx.application.Application;
+import javafx.event.ActionEvent;
+import javafx.geometry.Insets;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.stage.Stage;
+
+/**
+ * Created by lewandowski on 20/12/2020.
+ */
+public class PaintApplication extends Application {
+
+    public static final int WIDTH = 800;
+    public static final int HEIGHT = 600;
+
+    private Scene scene;
+    private BorderPane root;
+    private DrawingPane drawingPane;
+
+    private Button clearButton;
+    private Button rectangleButton;
+    private Button circleButton;
+
+    @Override
+    public void start(Stage primaryStage) throws Exception {
+        root = new BorderPane();
+        scene = new Scene(root, WIDTH, HEIGHT);
+
+        root.getStylesheets().add(
+                PaintApplication.class.getClassLoader().getResource("style/Paint.css").toExternalForm());
+
+        drawingPane = new DrawingPane();
+        drawingPane.getStyleClass().add("drawingPane");
+        root.setCenter(drawingPane);
+
+        HBox hBox = new HBox();
+        clearButton = new Button("Clear");
+        clearButton.addEventFilter(ActionEvent.ACTION, new ClearButtonHandler(drawingPane));
+
+        rectangleButton = new Button("Rectangle");
+        rectangleButton.addEventFilter(ActionEvent.ACTION, new RectangleButtonHandler(drawingPane));
+        circleButton = new Button("Circle");
+        circleButton.addEventFilter(ActionEvent.ACTION, new EllipseButtonHandler(drawingPane));
+        hBox.getChildren().addAll(clearButton, rectangleButton, circleButton);
+        hBox.setPadding(new Insets(5));
+        hBox.setSpacing(5.0);
+        hBox.getStyleClass().add("toolbar");
+        root.setTop(hBox);
+
+        primaryStage.setTitle("Drawing");
+        primaryStage.setScene(scene);
+        primaryStage.show();
+    }
+
+    public DrawingPane getDrawingPane() {
+        return drawingPane;
+    }
+
+    public static void main(String[] args) {
+        launch(args);
+    }
+}

+ 25 - 0
src/main/java/drawing/RectangleButtonHandler.java

@@ -0,0 +1,25 @@
+package drawing;
+
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.Shape;
+
+/**
+ * Created by lewandowski on 20/12/2020.
+ */
+public class RectangleButtonHandler extends ShapeButtonHandler {
+
+    public RectangleButtonHandler(DrawingPane drawingPane) {
+        super(drawingPane);
+    }
+
+    @Override
+    protected Shape createShape() {
+        double x = Math.min(originX, destinationX);
+        double y = Math.min(originY, destinationY);
+        double width = Math.abs(destinationX - originX);
+        double height = Math.abs(destinationY - originY);
+        Rectangle rectangle = new Rectangle(x, y, width, height);
+        rectangle.getStyleClass().add("rectangle");
+        return rectangle;
+    }
+}

+ 54 - 0
src/main/java/drawing/ShapeButtonHandler.java

@@ -0,0 +1,54 @@
+package drawing;
+
+import javafx.event.ActionEvent;
+import javafx.event.Event;
+import javafx.event.EventHandler;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.shape.Shape;
+
+/**
+ * Created by lewandowski on 20/12/2020.
+ */
+public abstract class ShapeButtonHandler implements EventHandler<Event> {
+
+    private DrawingPane drawingPane;
+    protected double originX;
+    protected double originY;
+    protected double destinationX;
+    protected double destinationY;
+
+    protected Shape shape;
+
+    public ShapeButtonHandler(DrawingPane drawingPane) {
+        this.drawingPane = drawingPane;
+    }
+
+    @Override
+    public void handle(Event event) {
+
+        if (event instanceof ActionEvent) {
+            drawingPane.addEventHandler(MouseEvent.MOUSE_PRESSED, this);
+        }
+
+        if (event instanceof MouseEvent) {
+            if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
+                drawingPane.addEventHandler(MouseEvent.MOUSE_RELEASED, this);
+                originX = ((MouseEvent) event).getX();
+                originY = ((MouseEvent) event).getY();
+            }
+
+            if (event.getEventType().equals(MouseEvent.MOUSE_RELEASED)) {
+                destinationX = ((MouseEvent) event).getX();
+                destinationY = ((MouseEvent) event).getY();
+                shape = createShape();
+                drawingPane.addShape(shape);
+
+                drawingPane.removeEventHandler(MouseEvent.MOUSE_PRESSED, this);
+                drawingPane.removeEventHandler(MouseEvent.MOUSE_RELEASED, this);
+            }
+        }
+    }
+
+    protected abstract Shape createShape();
+
+}

+ 22 - 0
src/main/resources/style/Paint.css

@@ -0,0 +1,22 @@
+
+.toolbar {
+    -fx-border-style: solid;
+    -fx-border-width: 0px 0px 1px 0px;
+    -fx-border-color: darkgrey;
+}
+
+.drawingPane {
+    -fx-background-color: white;
+}
+
+.rectangle {
+    -fx-fill: greenyellow;
+    -fx-stroke: green;
+    -fx-stroke-width: 3;
+}
+
+.ellipse {
+    -fx-fill: yellow;
+    -fx-stroke: orange;
+    -fx-stroke-width: 3;
+}

+ 82 - 0
src/test/java/PaintTest.java

@@ -0,0 +1,82 @@
+import drawing.PaintApplication;
+import javafx.scene.shape.Ellipse;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import org.junit.Test;
+import org.testfx.framework.junit.ApplicationTest;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.*;
+
+public class PaintTest extends ApplicationTest {
+
+    PaintApplication app;
+
+    @Override
+    public void start(Stage stage) {
+        try {
+            app = new PaintApplication();
+            app.start(stage);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void should_draw_circle_programmatically() {
+        interact(() -> {
+                    app.getDrawingPane().addShape(new Ellipse(20, 20, 30, 30));
+                });
+        Iterator it = app.getDrawingPane().getShapes().iterator();
+        assertTrue(it.next() instanceof Ellipse);
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void should_draw_circle() {
+        // given:
+        clickOn("Circle");
+        moveBy(60,60);
+
+        // when:
+        drag().dropBy(30,30);
+        //press(MouseButton.PRIMARY); moveBy(30,30); release(MouseButton.PRIMARY);
+
+        // then:
+        Iterator it = app.getDrawingPane().getShapes().iterator();
+        assertTrue(it.next() instanceof Ellipse);
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void should_draw_rectangle() {
+        // given:
+        clickOn("Rectangle");
+        moveBy(0,60);
+
+        // when:
+        drag().dropBy(70,40);
+
+        // then:
+        Iterator it = app.getDrawingPane().getShapes().iterator();
+        assertTrue(it.next() instanceof Rectangle);
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void should_clear() {
+        // given:
+        clickOn("Rectangle");
+        moveBy(30,60).drag().dropBy(70,40);
+        clickOn("Circle");
+        moveBy(-30,160).drag().dropBy(70,40);
+
+        // when:
+        clickOn("Clear");
+
+        // then:
+        assertFalse(app.getDrawingPane().getShapes().iterator().hasNext());
+    }
+
+}