From 3aecf4150fd656f5f5f0d992c475db20ae7df428 Mon Sep 17 00:00:00 2001 From: hneemann Date: Tue, 12 Apr 2016 20:18:42 +0200 Subject: [PATCH] Interactor allows reactions on mouse pressed and mouse released. Added a button --- .../de/neemann/digital/core/io/Button.java | 43 +++++++++ .../digital/draw/elements/VisualElement.java | 43 +++++++-- .../digital/draw/graphics/GraphicSwing.java | 17 +++- .../digital/draw/library/ElementLibrary.java | 6 +- .../digital/draw/shapes/ButtonShape.java | 94 +++++++++++++++++++ .../digital/draw/shapes/DataShape.java | 12 ++- .../digital/draw/shapes/Interactor.java | 20 ++-- .../draw/shapes/InteractorInterface.java | 47 ++++++++++ .../de/neemann/digital/draw/shapes/Shape.java | 2 +- .../digital/draw/shapes/ShapeFactory.java | 6 +- .../gui/components/CircuitComponent.java | 43 +++++++-- 11 files changed, 289 insertions(+), 44 deletions(-) create mode 100644 src/main/java/de/neemann/digital/core/io/Button.java create mode 100644 src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java create mode 100644 src/main/java/de/neemann/digital/draw/shapes/InteractorInterface.java diff --git a/src/main/java/de/neemann/digital/core/io/Button.java b/src/main/java/de/neemann/digital/core/io/Button.java new file mode 100644 index 000000000..54fe8cfd3 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/io/Button.java @@ -0,0 +1,43 @@ +package de.neemann.digital.core.io; + +import de.neemann.digital.core.Model; +import de.neemann.digital.core.NodeException; +import de.neemann.digital.core.ObservableValue; +import de.neemann.digital.core.element.AttributeKey; +import de.neemann.digital.core.element.Element; +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.core.element.ElementTypeDescription; +import de.neemann.digital.lang.Lang; + +/** + * @author hneemann + */ +public class Button implements Element { + + public static final ElementTypeDescription DESCRIPTION = new ElementTypeDescription(Button.class) + .addAttribute(AttributeKey.Rotate) + .addAttribute(AttributeKey.Label); + + private final ObservableValue output; + private final String label; + + public Button(ElementAttributes attributes) { + output = new ObservableValue("out", 1); + label = attributes.get(AttributeKey.Label); + } + + @Override + public void setInputs(ObservableValue... inputs) throws NodeException { + throw new NodeException(Lang.get("err_noInputsAvailable")); + } + + @Override + public ObservableValue[] getOutputs() { + return new ObservableValue[]{output}; + } + + @Override + public void registerNodes(Model model) { + model.addSignal(label, output); + } +} diff --git a/src/main/java/de/neemann/digital/draw/elements/VisualElement.java b/src/main/java/de/neemann/digital/draw/elements/VisualElement.java index 0ee88f28c..3f8cd2bd2 100644 --- a/src/main/java/de/neemann/digital/draw/elements/VisualElement.java +++ b/src/main/java/de/neemann/digital/draw/elements/VisualElement.java @@ -6,10 +6,8 @@ import de.neemann.digital.core.element.AttributeListener; import de.neemann.digital.core.element.Element; import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.draw.graphics.*; -import de.neemann.digital.draw.shapes.Drawable; -import de.neemann.digital.draw.shapes.Interactor; +import de.neemann.digital.draw.shapes.*; import de.neemann.digital.draw.shapes.Shape; -import de.neemann.digital.draw.shapes.ShapeFactory; import de.neemann.digital.gui.components.CircuitComponent; import javax.swing.*; @@ -29,7 +27,7 @@ public class VisualElement implements Drawable, Moveable, AttributeListener { private transient GraphicMinMax minMax; private transient Shape shape; private transient IOState ioState; - private transient Interactor interactor; + private transient InteractorInterface interactor; private transient Element element; private transient ShapeFactory shapeFactory; private Vector pos; @@ -267,20 +265,51 @@ public class VisualElement implements Drawable, Moveable, AttributeListener { } /** - * Is called if this element is clicked by the mouse. - * the call is delegated to the {@link Interactor} of the {@link Shape} + * Is called if this element is clicked with the mouse. + * The call is delegated to the {@link Interactor} of the {@link Shape} * * @param cc the calling {@link CircuitComponent} * @param pos the position * @return true if model is changed */ - public boolean clicked(CircuitComponent cc, Point pos) { + public boolean elementClicked(CircuitComponent cc, Point pos) { if (interactor != null) return interactor.clicked(cc, pos, ioState, element); else return false; } + /** + * Is called if this element is clicked with the mouse. + * The call is delegated to the {@link Interactor} of the {@link Shape} + * + * @param cc the calling {@link CircuitComponent} + * @param pos the position + * @return true if model is changed + */ + public boolean elementPressed(CircuitComponent cc, Point pos) { + if (interactor != null) + return interactor.pressed(cc, pos, ioState, element); + else + return false; + } + + /** + * Is called if this element is clicked with the mouse. + * The call is delegated to the {@link Interactor} of the {@link Shape} + * + * @param cc the calling {@link CircuitComponent} + * @param pos the position + * @return true if model is changed + */ + public boolean elementReleased(CircuitComponent cc, Point pos) { + if (interactor != null) + return interactor.released(cc, pos, ioState, element); + else + return false; + } + + @Override public void attributeChanged(AttributeKey key) { shape = null; diff --git a/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java b/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java index d5c1f08db..85f4562d0 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java +++ b/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java @@ -2,6 +2,8 @@ package de.neemann.digital.draw.graphics; import java.awt.*; import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; /** * Used to draw on a {@link Graphics2D} instance. @@ -32,13 +34,18 @@ public class GraphicSwing implements Graphic { @Override public void drawPolygon(Polygon p, Style style) { applyStyle(style); - java.awt.Polygon poly = new java.awt.Polygon(); + Path2D path = new GeneralPath(); + boolean first = true; for (Vector v : p) - poly.addPoint(v.x, v.y); + if (first) { + first = false; + path.moveTo(v.x, v.y); + } else + path.lineTo(v.x, v.y); - if (style.isFilled()) - gr.fill(poly); - gr.draw(poly); + if (p.isClosed()) + path.closePath(); + gr.draw(path); } @Override diff --git a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java index 5e89b8248..09ffba1c9 100644 --- a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java +++ b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java @@ -10,10 +10,7 @@ import de.neemann.digital.core.flipflops.D_FF; import de.neemann.digital.core.flipflops.JK_FF; import de.neemann.digital.core.flipflops.RS_FF; import de.neemann.digital.core.flipflops.T_FF; -import de.neemann.digital.core.io.Const; -import de.neemann.digital.core.io.In; -import de.neemann.digital.core.io.Out; -import de.neemann.digital.core.io.Probe; +import de.neemann.digital.core.io.*; import de.neemann.digital.core.memory.*; import de.neemann.digital.core.wiring.*; import de.neemann.digital.gui.components.data.DummyElement; @@ -51,6 +48,7 @@ public class ElementLibrary implements Iterable add(In.DESCRIPTION, menu); add(Out.DESCRIPTION, menu); add(Out.LEDDESCRIPTION, menu); + add(Button.DESCRIPTION, menu); add(Probe.DESCRIPTION, menu); add(Clock.DESCRIPTION, menu); add(Reset.DESCRIPTION, menu); diff --git a/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java b/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java new file mode 100644 index 000000000..11d9e3bce --- /dev/null +++ b/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java @@ -0,0 +1,94 @@ +package de.neemann.digital.draw.shapes; + +import de.neemann.digital.core.ObservableValue; +import de.neemann.digital.core.Observer; +import de.neemann.digital.core.element.Element; +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.draw.elements.IOState; +import de.neemann.digital.draw.elements.Pin; +import de.neemann.digital.draw.elements.Pins; +import de.neemann.digital.draw.graphics.*; +import de.neemann.digital.draw.graphics.Polygon; +import de.neemann.digital.gui.components.CircuitComponent; + +import java.awt.*; + +import static de.neemann.digital.draw.shapes.OutputShape.SIZE; + +/** + * @author hneemann + */ +public class ButtonShape implements Shape { + + private static final int HEIGHT = SIZE / 2; + + private final String label; + private IOState ioState; + + public ButtonShape(ElementAttributes attr) { + this.label = attr.getLabel(); + } + + @Override + public Pins getPins() { + return new Pins().add(new Pin(new Vector(0, 0), "out", Pin.Direction.output)); + } + + @Override + public InteractorInterface applyStateMonitor(IOState ioState, Observer guiObserver) { + this.ioState = ioState; + ioState.getOutput(0).addObserverToValue(guiObserver); + return new InteractorInterface() { + @Override + public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { + return false; + } + + @Override + public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element) { + ObservableValue value = ioState.getOutput(0); + value.setValue(1); + return true; + } + + @Override + public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element) { + ObservableValue value = ioState.getOutput(0); + value.setValue(0); + return true; + } + }; + } + + @Override + public void drawTo(Graphic graphic, boolean heighLight) { + boolean down = false; + if (ioState != null) down = ioState.getOutput(0).getBool(); + + if (down) { + graphic.drawPolygon(new Polygon(true) + .add(-SIZE * 2 - 1, -SIZE) + .add(-1, -SIZE) + .add(-1, SIZE) + .add(-SIZE * 2 - 1, SIZE), Style.NORMAL); + } else { + int t = Style.NORMAL.getThickness() / 4; + graphic.drawPolygon(new Polygon(true) + .add(-SIZE * 2 - 1 - HEIGHT, -SIZE - HEIGHT) + .add(-1 - HEIGHT, -SIZE - HEIGHT) + .add(-1, -SIZE) + .add(-1, SIZE) + .add(-SIZE * 2 - 1, SIZE) + .add(-SIZE * 2 - 1 - HEIGHT, SIZE - HEIGHT), Style.NORMAL); + graphic.drawPolygon(new Polygon(false) + .add(-1 - HEIGHT, -SIZE + t - HEIGHT) + .add(-1 - HEIGHT, SIZE - HEIGHT) + .add(t - SIZE * 2 - 1 - HEIGHT, SIZE - HEIGHT), Style.NORMAL); + graphic.drawLine(new Vector(-1 - HEIGHT, SIZE - HEIGHT), new Vector(-1 - t, SIZE - t), Style.NORMAL); + } + + + Vector textPos = new Vector(-SIZE * 3, -4); + graphic.drawText(textPos, textPos.add(1, 0), label, Orientation.RIGHTCENTER, Style.NORMAL); + } +} diff --git a/src/main/java/de/neemann/digital/draw/shapes/DataShape.java b/src/main/java/de/neemann/digital/draw/shapes/DataShape.java index 962d6a5ef..ec370fb2c 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/DataShape.java +++ b/src/main/java/de/neemann/digital/draw/shapes/DataShape.java @@ -4,16 +4,19 @@ import de.neemann.digital.core.Model; import de.neemann.digital.core.ModelEvent; import de.neemann.digital.core.Observer; import de.neemann.digital.core.element.AttributeKey; +import de.neemann.digital.core.element.Element; import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.draw.elements.IOState; import de.neemann.digital.draw.elements.Pins; import de.neemann.digital.draw.graphics.Graphic; import de.neemann.digital.draw.model.ModelDescription; import de.neemann.digital.draw.model.ModelEntry; +import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.OrderMerger; import de.neemann.digital.gui.components.data.DataSet; import de.neemann.digital.gui.components.data.DataSetObserver; +import java.awt.*; import java.util.ArrayList; /** @@ -40,9 +43,12 @@ public class DataShape implements Shape { @Override public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) { - return (cc, pos, ioState1, element) -> { - dataSet.clear(); - return false; + return new Interactor() { + @Override + public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { + dataSet.clear(); + return false; + } }; } diff --git a/src/main/java/de/neemann/digital/draw/shapes/Interactor.java b/src/main/java/de/neemann/digital/draw/shapes/Interactor.java index 1ffa61606..a4241c528 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/Interactor.java +++ b/src/main/java/de/neemann/digital/draw/shapes/Interactor.java @@ -14,15 +14,15 @@ import java.awt.*; * @author hneemann * @see InputShape */ -public interface Interactor { +public abstract class Interactor implements InteractorInterface { - /** - * Called if clicked on running model - * - * @param cc the CircuitComponent - * @param pos the popuplocation on screen - * @param ioState the state of the element - * @return true if model is changed - */ - boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element); + @Override + public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element) { + return false; + } + + @Override + public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element) { + return false; + } } diff --git a/src/main/java/de/neemann/digital/draw/shapes/InteractorInterface.java b/src/main/java/de/neemann/digital/draw/shapes/InteractorInterface.java new file mode 100644 index 000000000..fcda42143 --- /dev/null +++ b/src/main/java/de/neemann/digital/draw/shapes/InteractorInterface.java @@ -0,0 +1,47 @@ +package de.neemann.digital.draw.shapes; + +import de.neemann.digital.core.element.Element; +import de.neemann.digital.draw.elements.IOState; +import de.neemann.digital.gui.components.CircuitComponent; + +import java.awt.*; + +/** + * The VisualParts InteractorInterface instance is called if the element is clicked + * during execution. So the User can interact with the element during execution. + * Used at the InputShape to let the user toggle the inputs state. + * + * @author hneemann + * @see InputShape + */ +public interface InteractorInterface { + /** + * Called if clicked on running model + * + * @param cc the CircuitComponent + * @param pos the popuplocation on screen + * @param ioState the state of the element + * @return true if model is changed + */ + boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element); + + /** + * Called mouse is pressed on running model + * + * @param cc the CircuitComponent + * @param pos the popuplocation on screen + * @param ioState the state of the element + * @return true if model is changed + */ + boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element); + + /** + * Called mouse is released on running model + * + * @param cc the CircuitComponent + * @param pos the popuplocation on screen + * @param ioState the state of the element + * @return true if model is changed + */ + boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element); +} diff --git a/src/main/java/de/neemann/digital/draw/shapes/Shape.java b/src/main/java/de/neemann/digital/draw/shapes/Shape.java index 844498943..ea8f25ce9 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/Shape.java +++ b/src/main/java/de/neemann/digital/draw/shapes/Shape.java @@ -34,7 +34,7 @@ public interface Shape extends Drawable { * @param guiObserver can be used to update the GUI by calling hasChanged, maybe null * @return the interactor is called if the shape is clicked during running mode, maybe null */ - Interactor applyStateMonitor(IOState ioState, Observer guiObserver); + InteractorInterface applyStateMonitor(IOState ioState, Observer guiObserver); /** * Allows the shape to make its drawing dependent of the model. diff --git a/src/main/java/de/neemann/digital/draw/shapes/ShapeFactory.java b/src/main/java/de/neemann/digital/draw/shapes/ShapeFactory.java index 2b4b8f55e..0b3a923b4 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/ShapeFactory.java +++ b/src/main/java/de/neemann/digital/draw/shapes/ShapeFactory.java @@ -6,10 +6,7 @@ import de.neemann.digital.core.basic.*; import de.neemann.digital.core.element.AttributeKey; import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.core.element.ElementTypeDescription; -import de.neemann.digital.core.io.Const; -import de.neemann.digital.core.io.In; -import de.neemann.digital.core.io.Out; -import de.neemann.digital.core.io.Probe; +import de.neemann.digital.core.io.*; import de.neemann.digital.core.memory.RAMDualPort; import de.neemann.digital.core.memory.RAMSinglePort; import de.neemann.digital.core.wiring.*; @@ -48,6 +45,7 @@ public final class ShapeFactory { map.put(Const.DESCRIPTION.getName(), ConstShape::new); map.put(Out.DESCRIPTION.getName(), OutputShape::new); map.put(Out.LEDDESCRIPTION.getName(), LEDShape::new); + map.put(Button.DESCRIPTION.getName(), ButtonShape::new); map.put(Probe.DESCRIPTION.getName(), ProbeShape::new); map.put(Clock.DESCRIPTION.getName(), ClockShape::new); map.put(Out.SEVENDESCRIPTION.getName(), SevenSegShape::new); diff --git a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java index a3a5a4c37..932bd3fc6 100644 --- a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java +++ b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java @@ -242,6 +242,7 @@ public class CircuitComponent extends JComponent { /** * Sets a circuit to this component + * * @param circuit the circuit */ public void setCircuit(Circuit circuit) { @@ -709,25 +710,47 @@ public class CircuitComponent extends JComponent { } } + private interface Actor { + boolean interact(CircuitComponent cc, Point p); + } + private final class MouseControllerRun extends MouseController { private MouseControllerRun(Cursor cursor) { super(cursor); } + @Override + void pressed(MouseEvent e) { + VisualElement ve = circuit.getElementAt(getPosVector(e)); + if (ve != null) + interact(e, ve::elementPressed); + } + + @Override + void released(MouseEvent e) { + VisualElement ve = circuit.getElementAt(getPosVector(e)); + if (ve != null) + interact(e, ve::elementReleased); + } + @Override void clicked(MouseEvent e) { VisualElement ve = circuit.getElementAt(getPosVector(e)); - if (ve != null) { - Point p = new Point(e.getX(), e.getY()); - SwingUtilities.convertPointToScreen(p, CircuitComponent.this); - boolean modelHasChanged = ve.clicked(CircuitComponent.this, p); - if (modelHasChanged) { - if (manualChangeObserver != null) - manualChangeObserver.hasChanged(); - } else - repaint(); - } + if (ve != null) + interact(e, ve::elementClicked); + } + + + private void interact(MouseEvent e, Actor actor) { + Point p = new Point(e.getX(), e.getY()); + SwingUtilities.convertPointToScreen(p, CircuitComponent.this); + boolean modelHasChanged = actor.interact(CircuitComponent.this, p); + if (modelHasChanged) { + if (manualChangeObserver != null) + manualChangeObserver.hasChanged(); + } else + repaint(); } }