From 6dea9d9f2d5f6e2c3c340f72de6f0f013c5d17a1 Mon Sep 17 00:00:00 2001 From: hneemann Date: Wed, 5 Apr 2017 21:21:25 +0200 Subject: [PATCH] added a simple rotary encoder --- .../neemann/digital/core/io/RotEncoder.java | 51 +++++++ .../digital/draw/elements/VisualElement.java | 38 +++-- .../digital/draw/library/ElementLibrary.java | 1 + .../digital/draw/shapes/ButtonShape.java | 5 + .../digital/draw/shapes/Interactor.java | 7 + .../draw/shapes/InteractorInterface.java | 14 ++ .../digital/draw/shapes/RotEncoderShape.java | 130 ++++++++++++++++++ .../digital/draw/shapes/ShapeFactory.java | 1 + .../gui/components/CircuitComponent.java | 18 ++- src/main/resources/lang/lang_de.xml | 4 + src/main/resources/lang/lang_en.xml | 4 + 11 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 src/main/java/de/neemann/digital/core/io/RotEncoder.java create mode 100644 src/main/java/de/neemann/digital/draw/shapes/RotEncoderShape.java diff --git a/src/main/java/de/neemann/digital/core/io/RotEncoder.java b/src/main/java/de/neemann/digital/core/io/RotEncoder.java new file mode 100644 index 000000000..752d6adbe --- /dev/null +++ b/src/main/java/de/neemann/digital/core/io/RotEncoder.java @@ -0,0 +1,51 @@ +package de.neemann.digital.core.io; + +import de.neemann.digital.core.*; +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.core.element.Keys; +import de.neemann.digital.lang.Lang; + +/** + * The Button + * + * @author hneemann + */ +public class RotEncoder implements Element { + + /** + * The Button description + */ + public static final ElementTypeDescription DESCRIPTION = new ElementTypeDescription(RotEncoder.class) + .addAttribute(Keys.ROTATE) + .addAttribute(Keys.LABEL); + + private final ObservableValue outA; + private final ObservableValue outB; + + /** + * Creates a new instance + * + * @param attributes the attributes + */ + public RotEncoder(ElementAttributes attributes) { + outA= new ObservableValue("A", 1).setPinDescription(DESCRIPTION); + outB= new ObservableValue("B", 1).setPinDescription(DESCRIPTION); + } + + @Override + public void setInputs(ObservableValues inputs) throws NodeException { + throw new NodeException(Lang.get("err_noInputsAvailable")); + } + + @Override + public ObservableValues getOutputs() { + return new ObservableValues(outA, outB); + } + + @Override + public void registerNodes(Model model) { + } + +} 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 b4f7d5a9a..f9f9ab599 100644 --- a/src/main/java/de/neemann/digital/draw/elements/VisualElement.java +++ b/src/main/java/de/neemann/digital/draw/elements/VisualElement.java @@ -297,11 +297,12 @@ public class VisualElement implements Drawable, Moveable, AttributeListener { * 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 + * @param cc the calling {@link CircuitComponent} + * @param pos the position + * @param posInComponent position in CircuitComponent * @return true if model is changed */ - public boolean elementClicked(CircuitComponent cc, Point pos, Sync modelSync) { + public boolean elementClicked(CircuitComponent cc, Point pos, Vector posInComponent, Sync modelSync) { if (interactor != null) return interactor.clicked(cc, pos, ioState, element, modelSync); else @@ -312,11 +313,12 @@ public class VisualElement implements Drawable, Moveable, AttributeListener { * 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 + * @param cc the calling {@link CircuitComponent} + * @param pos the position + * @param posInComponent position in CircuitComponent * @return true if model is changed */ - public boolean elementPressed(CircuitComponent cc, Point pos, Sync modelSync) { + public boolean elementPressed(CircuitComponent cc, Point pos, Vector posInComponent, Sync modelSync) { if (interactor != null) return interactor.pressed(cc, pos, ioState, element, modelSync); else @@ -327,17 +329,35 @@ public class VisualElement implements Drawable, Moveable, AttributeListener { * 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 + * @param cc the calling {@link CircuitComponent} + * @param pos the position + * @param posInComponent position in CircuitComponent * @return true if model is changed */ - public boolean elementReleased(CircuitComponent cc, Point pos, Sync modelSync) { + public boolean elementReleased(CircuitComponent cc, Point pos, Vector posInComponent, Sync modelSync) { if (interactor != null) return interactor.released(cc, pos, ioState, element, modelSync); else return false; } + /** + * Is called if the mouse is dragged on this element. + * The call is delegated to the {@link Interactor} of the {@link Shape} + * + * @param cc the calling {@link CircuitComponent} + * @param pos the position + * @param posInComponent position in CircuitComponent + * @return true if model is changed + */ + public boolean elementDragged(CircuitComponent cc, Point pos, Vector posInComponent, Sync modelSync) { + if (interactor != null) + return interactor.dragged(cc, posInComponent, transform, ioState, element, modelSync); + else + return false; + } + + @Override public String toString() { String lab = elementAttributes.getCleanLabel(); 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 d72189528..bb3394084 100644 --- a/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java +++ b/src/main/java/de/neemann/digital/draw/library/ElementLibrary.java @@ -87,6 +87,7 @@ public class ElementLibrary implements Iterable node.add(Probe.DESCRIPTION); node.add(Out.SEVENDESCRIPTION); node.add(Out.SEVENHEXDESCRIPTION); + node.add(RotEncoder.DESCRIPTION); node.add(DummyElement.DATADESCRIPTION); node.add(DummyElement.TEXTDESCRIPTION); node.add(Keyboard.DESCRIPTION); diff --git a/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java b/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java index c58d41934..f957a012a 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java +++ b/src/main/java/de/neemann/digital/draw/shapes/ButtonShape.java @@ -74,6 +74,11 @@ public class ButtonShape implements Shape { }); return true; } + + @Override + public boolean dragged(CircuitComponent cc, Vector pos, Transform trans, IOState ioState, Element element, Sync modelSync) { + 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 b01db8646..ba2ec2acb 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/Interactor.java +++ b/src/main/java/de/neemann/digital/draw/shapes/Interactor.java @@ -2,6 +2,8 @@ package de.neemann.digital.draw.shapes; import de.neemann.digital.core.element.Element; import de.neemann.digital.draw.elements.IOState; +import de.neemann.digital.draw.graphics.Transform; +import de.neemann.digital.draw.graphics.Vector; import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.sync.Sync; @@ -26,4 +28,9 @@ public abstract class Interactor implements InteractorInterface { public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) { return false; } + + @Override + public boolean dragged(CircuitComponent cc, Vector pos, Transform transform, IOState ioState, Element element, Sync modelSync) { + 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 index 83ce3ed51..384f583b5 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/InteractorInterface.java +++ b/src/main/java/de/neemann/digital/draw/shapes/InteractorInterface.java @@ -2,6 +2,8 @@ package de.neemann.digital.draw.shapes; import de.neemann.digital.core.element.Element; import de.neemann.digital.draw.elements.IOState; +import de.neemann.digital.draw.graphics.Transform; +import de.neemann.digital.draw.graphics.Vector; import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.sync.Sync; @@ -45,4 +47,16 @@ public interface InteractorInterface { * @return true if model is changed */ boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync); + + /** + * Called mouse is dragged on running model + * + * @param cc the CircuitComponent + * @param pos the position in the containing component + * @param transform transformation to transform shape coordinates to the containing component + * @param ioState the state of the element + * @return true if model is changed + */ + boolean dragged(CircuitComponent cc, Vector pos, Transform transform, IOState ioState, Element element, Sync modelSync); + } diff --git a/src/main/java/de/neemann/digital/draw/shapes/RotEncoderShape.java b/src/main/java/de/neemann/digital/draw/shapes/RotEncoderShape.java new file mode 100644 index 000000000..777111496 --- /dev/null +++ b/src/main/java/de/neemann/digital/draw/shapes/RotEncoderShape.java @@ -0,0 +1,130 @@ +package de.neemann.digital.draw.shapes; + +import de.neemann.digital.core.Observer; +import de.neemann.digital.core.element.Element; +import de.neemann.digital.core.element.ElementAttributes; +import de.neemann.digital.core.element.PinDescriptions; +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 de.neemann.digital.gui.sync.Sync; + +import java.awt.*; + +import static de.neemann.digital.draw.shapes.GenericShape.SIZE; +import static de.neemann.digital.draw.shapes.GenericShape.SIZE2; + + +/** + * The rotary encoder shape + * + * @author hneemann + */ +public class RotEncoderShape implements Shape { + private final String label; + private final PinDescriptions outputs; + private int state; + + /** + * Creates a new instance + * + * @param attr the attributes + * @param inputs the inputs + * @param outputs the outputs + */ + public RotEncoderShape(ElementAttributes attr, PinDescriptions inputs, PinDescriptions outputs) { + this.outputs = outputs; + this.label = attr.getLabel(); + } + + @Override + public Pins getPins() { + return new Pins() + .add(new Pin(new Vector(SIZE * 3, 0), outputs.get(0))) + .add(new Pin(new Vector(SIZE * 3, SIZE), outputs.get(1))); + } + + @Override + public InteractorInterface applyStateMonitor(IOState ioState, Observer guiObserver) { + ioState.getOutput(0).addObserverToValue(guiObserver); + return new InteractorInterface() { + + private int initialState; + private boolean initial; + + @Override + public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) { + return false; + } + + @Override + public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) { + initial = true; + return false; + } + + @Override + public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) { + return false; + } + + @Override + public boolean dragged(CircuitComponent cc, Vector pos, Transform trans, IOState ioState, Element element, Sync modelSync) { + if (ioState != null) { + Vector p = pos.sub(trans.transform(new Vector(SIZE2, SIZE2))); + final int dist = p.x * p.x + p.y * p.y; + if (dist > 100 && dist < 900) { + int s = (int) (Math.atan2(p.y, p.x) / Math.PI * 16); + if (initial) { + initialState = s; + initial = false; + } else { + // somewhat unusual but ensures that every step is visible to the model. + int ds = 0; + if (s > initialState) ds = 1; + else if (s < initialState) ds = -1; + initialState = s; + if (ds != 0) { + state += ds; + modelSync.access(() -> { + boolean a = ((state / 2) & 1) != 0; + boolean b = (((state + 1) / 2) & 1) != 0; + ioState.getOutput(0).setBool(a); + ioState.getOutput(1).setBool(b); + }); + return true; + } + } + } else + initial = true; + } + return false; + } + }; + } + + @Override + public void drawTo(Graphic graphic, boolean heighLight) { + graphic.drawPolygon(new Polygon(true) + .add(SIZE * 3, -SIZE) + .add(SIZE * 3, SIZE * 2) + .add(-SIZE, SIZE * 2) + .add(-SIZE, -SIZE), Style.NORMAL); + + graphic.drawCircle(new Vector(-SIZE, -SIZE), new Vector(SIZE * 2, SIZE * 2), Style.NORMAL); + graphic.drawCircle(new Vector(-SIZE2, -SIZE2), new Vector(SIZE + SIZE2, SIZE + SIZE2), Style.THIN); + + final double alpha = state / 16.0 * Math.PI; + int x = (int) ((SIZE + 1) * Math.cos(alpha)); + int y = (int) ((SIZE + 1) * Math.sin(alpha)); + + graphic.drawLine(new Vector(SIZE2, SIZE2), new Vector(SIZE2 + x, SIZE2 + y), Style.NORMAL); + + Vector textPos = new Vector(SIZE, SIZE * 2 + 4); + graphic.drawText(textPos, textPos.add(1, 0), label, Orientation.CENTERTOP, Style.NORMAL); + } + +} 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 b5bad53d9..53b9d3c2d 100644 --- a/src/main/java/de/neemann/digital/draw/shapes/ShapeFactory.java +++ b/src/main/java/de/neemann/digital/draw/shapes/ShapeFactory.java @@ -97,6 +97,7 @@ public final class ShapeFactory { map.put(Out.SEVENDESCRIPTION.getName(), SevenSegShape::new); map.put(Out.SEVENHEXDESCRIPTION.getName(), SevenSegHexShape::new); map.put(DummyElement.DATADESCRIPTION.getName(), DataShape::new); + map.put(RotEncoder.DESCRIPTION.getName(), RotEncoderShape::new); map.put(Break.DESCRIPTION.getName(), BreakShape::new); map.put(Delay.DESCRIPTION.getName(), (attributes, inputs, outputs) -> new DelayShape()); 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 319d1ef0b..4a878ae09 100644 --- a/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java +++ b/src/main/java/de/neemann/digital/gui/components/CircuitComponent.java @@ -1287,11 +1287,13 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe private interface Actor { - boolean interact(CircuitComponent cc, Point p, Sync modelSync); + boolean interact(CircuitComponent cc, Point p, Vector posInComponent, Sync modelSync); } private final class MouseControllerRun extends MouseController { + private boolean dragHandled; + private MouseControllerRun(Cursor cursor) { super(cursor); } @@ -1299,8 +1301,11 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe @Override void pressed(MouseEvent e) { VisualElement ve = getInteractableElementAt(e); - if (ve != null) + if (ve != null) { interact(e, ve::elementPressed); + dragHandled = true; + } else + dragHandled = false; } private VisualElement getInteractableElementAt(MouseEvent e) { @@ -1326,11 +1331,18 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe interact(e, ve::elementClicked); } + @Override + boolean dragged(MouseEvent e) { + VisualElement ve = getInteractableElementAt(e); + if (ve != null) + interact(e, ve::elementDragged); + return dragHandled; + } 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, modelSync); + boolean modelHasChanged = actor.interact(CircuitComponent.this, p, getPosVector(e), modelSync); if (modelHasChanged) { if (manualChangeObserver != null) manualChangeObserver.hasChanged(); diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 2b8d01971..58579c074 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -372,6 +372,10 @@ Die gesammte Speichergröße beträgt damit damit dx*dy*2 Speicherworte.Gate Source Drain + Drehencoder + Drehencoder zur zustandsfreien Erfassung von Drehbewegungen. + Encodersignal A + Encodersignal B Fehler Flipflop hat keine Bezeichnung! diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index d92333b23..4c00bcebb 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -359,6 +359,10 @@ Gate Source Drain + Rotary Encoder + Rotary encoder for stateless detecting rotational movements. + encoder signal A + encoder signal B Error D-flip-flop has no label set