impl editor

master
Niclas Thobaben 2020-02-28 17:44:24 +01:00
parent 03318a14f4
commit 42c82888d1
10 changed files with 602 additions and 0 deletions

View File

@ -0,0 +1,91 @@
package de.synolo.app.qs;
import de.synolo.app.qs.editor.EditorCanvas;
import de.synolo.app.qs.editor.EditorItem;
import de.synolo.app.qs.editor.EditorLayer;
import de.synolo.app.qs.editor.ItemFactory;
import de.synolo.app.qs.editor.ItemNode;
import de.synolo.lib.fw.app.AbstractApplication;
import javafx.scene.Scene;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class QSApplication extends AbstractApplication<QSContext>{
public static void main(String[] args) {
launch(args);
}
@Override
protected QSContext initContext() {
return new QSContext();
}
@Override
protected void buildApplication(Stage primaryStage) throws Exception {
EditorCanvas cnv = new EditorCanvas();
Scene scene = new Scene(cnv);
primaryStage.setScene(scene);
EditorLayer layer = new EditorLayer(cnv, "My Layer", true, false);
cnv.getLayers().add(layer);
RectangleFactory factory = new RectangleFactory();
cnv.getControl().setItemFactory(factory);
cnv.getLayerSelectionModel().select(0);
System.out.println(cnv.getLayerSelectionModel().getSelectedItems());
primaryStage.show();
}
private static class RectangleFactory implements ItemFactory {
@Override
public EditorItem createItem() {
return new EditorItem() {
@Override
protected void paintShape(GraphicsContext gc) {
System.out.println("xy: " + getX() + " " + getY());
gc.setFill(Color.BEIGE);
gc.fillRect(getX(), getY(), getWidth(), getHeight());
}
@Override
public ItemNode nextNode(double x, double y) {
if(this.nodes.isEmpty()) {
System.out.println(x + " " + y);
ItemNode node = new ItemNode(x, y);
this.nodes.add(node);
this.xProperty.bind(node.xProperty());
this.yProperty.bind(node.yPropert());
node = new ItemNode(x, y);
this.nodes.add(node);
this.widthProperty.bind(node.xProperty().subtract(this.xProperty));
this.heightProperty.bind(node.yPropert().subtract(this.yProperty));
return node;
}else {
return null;
}
}
};
}
}
}

View File

@ -0,0 +1,21 @@
package de.synolo.app.qs;
import java.util.prefs.Preferences;
import de.synolo.lib.fw.app.ApplicationContext;
import de.synolo.lib.fw.utils.TypedProperties;
public class QSContext extends ApplicationContext{
public QSContext() {
this.properties = new TypedProperties();
this.preferences = Preferences.userRoot();
}
@Override
public String getApplicationName() { return "Synolo QS"; }
@Override
public String getApplicationVersion() { return "0.1"; }
}

View File

@ -0,0 +1,75 @@
package de.synolo.app.qs.editor;
import de.synolo.lib.fw.utils.MultipleSelectionModelWrapper;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
public class EditorCanvas extends Pane {
private Canvas canvas;
private EditorControl control;
private ObservableList<EditorLayer> layers = FXCollections.observableArrayList();
private ReadOnlyObjectWrapper<EditorState> stateProperty = new ReadOnlyObjectWrapper<EditorState>(EditorState.INSERTING);
private MultipleSelectionModelWrapper<EditorLayer> layerSelectionModel = new MultipleSelectionModelWrapper<>(this.layers);
public EditorCanvas() {
this.canvas = new Canvas();
this.canvas.widthProperty().bind(widthProperty());
this.canvas.heightProperty().bind(heightProperty());
this.canvas.widthProperty().addListener((v, ov, n) -> paint());
this.canvas.heightProperty().addListener((v, ov, n) -> paint());
this.control = new EditorControl(this);
getChildren().add(this.canvas);
this.layers.addListener((Change<? extends EditorLayer> c) -> paint());
this.stateProperty.addListener((v, ov, nv) -> System.out.println(this.stateProperty.get()));
}
public EditorControl getControl() { return this.control; }
public ObservableList<EditorLayer> getLayers() { return this.layers; }
public MultipleSelectionModel<EditorLayer> getLayerSelectionModel() { return this.layerSelectionModel; }
public ReadOnlyObjectProperty<EditorState> stateProperty() { return this.stateProperty.getReadOnlyProperty(); }
public EditorState getState() { return this.stateProperty.get(); }
void setState(EditorState state) { this.stateProperty.set(state); }
public void paint() {
GraphicsContext gc = this.canvas.getGraphicsContext2D();
double width = this.canvas.getWidth(),
height = this.canvas.getHeight();
gc.setFill(Color.BLACK);
gc.fillRect(0, 0, width, height);
gc.save();
gc.scale(this.control.getZoom(), this.control.getZoom());
gc.translate(-this.control.getOffsetX(), -this.control.getOffsetY());
for(int i = this.layers.size() - 1; i >= 0; i--) {
this.layers.get(i).paint(gc);
}
gc.restore();
System.out.println("paint - " + this.stateProperty.get());
}
}

View File

@ -0,0 +1,179 @@
package de.synolo.app.qs.editor;
import de.synolo.lib.fw.utils.Logging;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
public class EditorControl {
private EditorCanvas editor;
private ItemFactory itemFactory;
private double dragX, dragY,
offsetX = 0, offsetY = 0;
private DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0);
private DoubleProperty zoomFactorProperty = new SimpleDoubleProperty(0.05),
maxZoomProperty = new SimpleDoubleProperty(10),
minZoomProperty = new SimpleDoubleProperty(0.05);
private ItemNode selectedNode = null;
public EditorControl(EditorCanvas editor) {
this.editor = editor;
editor.setOnScroll(this::onScroll);
editor.setOnMouseClicked(this::onMouseClicked);
editor.setOnMousePressed(this::onMousePressed);
editor.setOnMouseReleased(this::onMouseReleased);
editor.setOnMouseDragged(this::onMouseDragged);
}
public DoubleProperty zoomProperty() { return this.zoomProperty; }
public double getZoom() { return this.zoomProperty.get(); }
public void setZoom(double zoom) { this.zoomProperty.set(zoom); }
public DoubleProperty zoomFactorProperty() { return this.zoomFactorProperty; }
public double getZoomFactor() { return this.zoomFactorProperty.get(); }
public void setZoomFactor(double factor) { this.zoomFactorProperty.set(factor); }
public DoubleProperty zoomMaxProperty() { return this.maxZoomProperty; }
public double getZoomMax() { return this.maxZoomProperty.get(); }
public void setZoomMax(double max) { this.maxZoomProperty.set(max); }
public DoubleProperty zoomMinProperty() { return this.minZoomProperty; }
public double getZoomMin() { return this.minZoomProperty.get(); }
public void setZoomMin(double min) { this.minZoomProperty.set(min); }
double getOffsetX() { return this.offsetX; }
double getOffsetY() { return this.offsetY; }
public double getWorldX(double screenX) {
return (screenX / getZoom()) + this.offsetX;
}
public double getWorldY(double screenY) {
return (screenY / getZoom()) + this.offsetY;
}
public double getScreenX(double worldX) {
return (worldX - this.offsetX) * getZoom();
}
public double getScreenY(double worldY) {
return (worldY - this.offsetY) * getZoom();
}
public EditorCanvas getEditor() { return this.editor; }
public ItemFactory getItemFactory() { return this.itemFactory; }
public void setItemFactory(ItemFactory factory) { this.itemFactory = factory; }
private void onScroll(ScrollEvent e) {
double scale = getZoom();
if(e.getDeltaY() > 0) {
scale *= (1.0 + getZoomFactor());
}else {
scale *= (1.0 - getZoomFactor());
}
scale = scale >= getZoomMax() ? getZoomMax() :
scale <= getZoomMin() ? getZoomMin() :
scale;
double xBefore = getWorldX(e.getX()),
yBefore = getWorldY(e.getY());
setZoom(scale);
double xAfter = getWorldX(e.getX()),
yAfter = getWorldY(e.getY());
this.offsetX += (xBefore - xAfter);
this.offsetY += (yBefore - yAfter);
editor.paint();
}
private void onMouseClicked(MouseEvent e) {
if(e.isConsumed())
return;
}
private void onMousePressed(MouseEvent e) {
if(e.getButton() == MouseButton.MIDDLE) {
this.dragX = e.getX();
this.dragY = e.getY();
this.editor.setState(EditorState.PANNING);
}
double worldX = getWorldX(e.getX()),
worldY = getWorldY(e.getY());
switch(this.editor.getState()) {
case EDITING:
break;
case INSERTING:
if(this.itemFactory != null) {
EditorLayer layer = this.editor.getLayerSelectionModel().getSelectedItems().get(0);
if(layer != null) {
EditorItem item = this.itemFactory.createItem();
item.setSelected(true);
this.selectedNode = item.nextNode(worldX, worldY);
if(this.selectedNode != null) {
this.editor.setState(EditorState.EDITING);
}
layer.getItems().add(item);
System.out.println("add item");
}else {
Logging.out.warn("No Layer selected!");
}
}
break;
case PANNING:
break;
case ZOOMING:
break;
default:
break;
}
}
private void onMouseReleased(MouseEvent e) {
EditorState state = EditorState.INSERTING;
switch(this.editor.getState()) {
case EDITING:
case INSERTING:
case PANNING:
case ZOOMING:
}
this.editor.setState(state);
this.selectedNode = null;
}
private void onMouseDragged(MouseEvent e) {
double worldX = getWorldX(e.getX()),
worldY = getWorldY(e.getY());
switch(this.editor.getState()) {
case EDITING:
if(this.selectedNode != null) {
this.selectedNode.setX(worldX);
this.selectedNode.setY(worldY);
this.editor.paint();
}
break;
case INSERTING:
break;
case PANNING:
break;
case ZOOMING:
break;
default:
break;
}
if(e.getButton() == MouseButton.MIDDLE) {
this.offsetX -= (e.getX() - this.dragX) / getZoom();
this.offsetY -= (e.getY() - this.dragY) / getZoom();
this.dragX = e.getX();
this.dragY = e.getY();
this.editor.paint();
}
}
}

View File

@ -0,0 +1,82 @@
package de.synolo.app.qs.editor;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public abstract class EditorItem {
private StringProperty nameProperty = new SimpleStringProperty("");
private BooleanProperty selectedProperty = new SimpleBooleanProperty(false);
protected DoubleProperty xProperty = new SimpleDoubleProperty(),
yProperty = new SimpleDoubleProperty(),
widthProperty = new SimpleDoubleProperty(),
heightProperty = new SimpleDoubleProperty();
protected List<ItemNode> nodes = new ArrayList<>();
public EditorItem() {
}
public StringProperty nameProperty() { return this.nameProperty; }
public String getName() { return this.nameProperty.get(); }
public void setName(String name) { this.nameProperty.set(name); }
public BooleanProperty selectedProperty() { return this.selectedProperty; }
public boolean isSelected() { return this.selectedProperty.get(); }
public void setSelected(boolean selected) { this.selectedProperty.set(selected); }
public DoubleProperty xProperty() { return this.xProperty; }
public double getX() { return this.xProperty.get(); }
public void setX(double x) { this.xProperty.set(x); }
public DoubleProperty yProperty() { return this.yProperty; }
public double getY() { return this.yProperty.get(); }
public void setY(double y) { this.yProperty.set(y); }
public DoubleProperty widthProperty() { return this.widthProperty; }
public double getWidth() { return this.widthProperty.get(); }
public void setWidth(double width) { this.widthProperty.set(width); }
public DoubleProperty heightProperty() { return this.heightProperty; }
public double getHeight() { return this.heightProperty.get(); }
public void setHeight(double height) { this.heightProperty.set(height); }
public boolean containsPoint(double x, double y) {
double tx = getX(),
ty = getY(),
width = getWidth(),
height = getHeight();
return x >= tx && x <= tx + width
&& y >= ty && ty <= ty + height;
}
public void paint(GraphicsContext gc) {
paintShape(gc);
if(isSelected()) {
gc.setFill(Color.YELLOW);
for(ItemNode node : this.nodes) {
gc.fillRect(node.getX()-10, node.getY()-10, 20, 20);
}
}
}
protected abstract void paintShape(GraphicsContext gc);
public abstract ItemNode nextNode(double x, double y);
}

View File

@ -0,0 +1,56 @@
package de.synolo.app.qs.editor;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.canvas.GraphicsContext;
public class EditorLayer {
private EditorCanvas parent;
private StringProperty nameProperty = new SimpleStringProperty("Layer");
private BooleanProperty visibleProperty = new SimpleBooleanProperty(true),
lockedProperty = new SimpleBooleanProperty(false);
private ObservableList<EditorItem> items = FXCollections.observableArrayList();
public EditorLayer(EditorCanvas parent, String name, boolean visible, boolean locked) {
setName(name);
setVisible(visible);
setLocked(locked);
this.parent = parent;
this.visibleProperty.addListener((v, ov, nv) -> this.parent.paint());
this.items.addListener((Change<? extends EditorItem> c) -> this.parent.paint());
}
public StringProperty nameProperty() { return this.nameProperty; }
public String getName() { return this.nameProperty.get(); }
public void setName(String name) { this.nameProperty.set(name); }
public BooleanProperty visibleProperty() { return this.visibleProperty; }
public boolean isVisible() { return this.visibleProperty.get(); }
public void setVisible(boolean visible) { this.visibleProperty.set(visible); }
public BooleanProperty lockedProperty() { return this.lockedProperty; }
public boolean isLocked() { return this.lockedProperty.get(); }
public void setLocked(boolean locked) { this.lockedProperty.set(locked); }
public EditorCanvas getParent() { return this.parent; }
public ObservableList<EditorItem> getItems() { return this.items; }
void paint(GraphicsContext gc){
if(isVisible()) {
for(EditorItem item : this.items) {
item.paint(gc);
}
}
}
}

View File

@ -0,0 +1,8 @@
package de.synolo.app.qs.editor;
public enum EditorState {
INSERTING,
EDITING,
PANNING,
ZOOMING
}

View File

@ -0,0 +1,7 @@
package de.synolo.app.qs.editor;
public interface ItemFactory {
public EditorItem createItem();
}

View File

@ -0,0 +1,45 @@
package de.synolo.app.qs.editor;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
public class ItemNode {
public static final int PADDING = 10;
private boolean lockXAxis = false,
lockYAxis = false;
private DoubleProperty xProperty = new SimpleDoubleProperty(),
yProperty = new SimpleDoubleProperty();
public ItemNode(double x, double y) {
setX(x);
setY(y);
}
public DoubleProperty xProperty() { return this.xProperty; }
public double getX() { return this.xProperty.get(); }
public void setX(double x) { this.xProperty.set(x); }
public DoubleProperty yPropert() { return this.yProperty; }
public double getY() { return this.yProperty.get(); }
public void setY(double y) { this.yProperty.set(y); }
public boolean isLockedXAxis() { return this.lockXAxis; }
public void setLockedXAxis(boolean lock) { this.lockXAxis = lock; }
public boolean isLockedYAxis() { return this.lockYAxis; }
public void setLockedYAxis(boolean lock) { this.lockYAxis = lock; }
public boolean isInBounds(double x, double y) {
boolean containsX = false,
containsY = false;
containsX = x >= getX()-PADDING && x <= getX() + PADDING;
containsY = y >= getY()-PADDING && y <= getY() + PADDING;
return containsX && containsY;
}
}

View File

@ -0,0 +1,38 @@
package de.synolo.app_qs;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
/**
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName )
{
super( testName );
}
/**
* @return the suite of tests being tested
*/
public static Test suite()
{
return new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}