Merge branch 'development' of
authorFabian Stemmler <stemmler@in.tum.de>
Wed, 29 May 2019 10:42:50 +0000 (12:42 +0200)
committerFabian Stemmler <stemmler@in.tum.de>
Wed, 29 May 2019 10:42:50 +0000 (12:42 +0200)
https://gitlab.lrz.de/lrr-tum/students/eragp-misim-2019 into development

# Conflicts:
# LogicUI/oldsrc/RSLatchGUIExample.java
# LogicUI/src/era/mi/gui/LogicUIStandalone.java
# LogicUI/src/era/mi/gui/components/GUIManualSwitch.java
# SampleERCP/src/sampleercp/parts/LogicUIPart.java

34 files changed:
LogicUI/oldsrc/GUIMerger.java [new file with mode: 0644]
LogicUI/oldsrc/GUIMux.java [new file with mode: 0644]
LogicUI/oldsrc/GUISplitter.java [new file with mode: 0644]
LogicUI/oldsrc/RSLatchGUIExample.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/ColorHelper.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/LogicUICanvas.java
LogicUI/src/era/mi/gui/LogicUIStandalone.java
LogicUI/src/era/mi/gui/components/BasicGUIComponent.java [deleted file]
LogicUI/src/era/mi/gui/components/GUIAndGate.java [deleted file]
LogicUI/src/era/mi/gui/components/GUIManualSwitch.java
LogicUI/src/era/mi/gui/components/GUIMerger.java [deleted file]
LogicUI/src/era/mi/gui/components/GUIMux.java [deleted file]
LogicUI/src/era/mi/gui/components/GUINotGate.java [deleted file]
LogicUI/src/era/mi/gui/components/GUIOrGate.java [deleted file]
LogicUI/src/era/mi/gui/components/GUISplitter.java [deleted file]
LogicUI/src/era/mi/gui/examples/Playground.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/examples/RSLatchGUIExample.java [deleted file]
LogicUI/src/era/mi/gui/model/ViewModel.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/components/GUIAndGate.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/components/GUIComponent.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/components/GUIManualSwitch.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/components/GUINotGate.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/components/GUIOrGate.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/components/RectangularShapedGUIGate.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/wires/GUIWire.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/wires/MovablePin.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/wires/Pin.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/model/wires/WireCrossPoint.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/modeladapter/ViewLogicModelAdapter.java [new file with mode: 0644]
LogicUI/src/era/mi/gui/wires/GUIWire.java [deleted file]
LogicUI/src/era/mi/gui/wires/WireConnectionPoint.java [deleted file]
SampleERCP/src/sampleercp/parts/LogicUIPart.java
era.mi/src/era/mi/logic/types/BitVectorFormatter.java [new file with mode: 0644]
era.mi/src/era/mi/logic/types/ColorDefinition.java [new file with mode: 0644]

diff --git a/LogicUI/oldsrc/GUIMerger.java b/LogicUI/oldsrc/GUIMerger.java
new file mode 100644 (file)
index 0000000..64fd0aa
--- /dev/null
@@ -0,0 +1,79 @@
+package era.mi.gui.components;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import era.mi.logic.components.Merger;
+import era.mi.logic.wires.Wire.ReadEnd;
+import era.mi.logic.wires.Wire.ReadWriteEnd;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class GUIMerger extends Merger implements GUIComponent
+{
+       private final int inputCount;
+       private final double height;
+       private final List<ReadEnd> connectedWireEnds;
+       private final List<Point> WireEndConnectionPoints;
+
+       public GUIMerger(ReadWriteEnd union, ReadEnd... inputs)
+       {
+               super(union, inputs);
+
+               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
+               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
+
+               this.inputCount = inputs.length;
+               this.height = (inputCount - 1) * 10;
+
+               {
+                       connectedWireEndsModifiable.addAll(Arrays.asList(inputs));
+                       double inputHeight = 0;
+                       for (int i = 0; i < inputCount; i++, inputHeight += 10)
+                               WireEndConnectionPointsModifiable.add(new Point(0, inputHeight));
+               }
+
+               connectedWireEndsModifiable.add(union);
+               WireEndConnectionPointsModifiable.add(new Point(20, height / 2));
+
+               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
+               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
+       }
+
+       @Override
+       public Rectangle getBounds()
+       {
+               return new Rectangle(0, 0, 20, height);
+       }
+
+       @Override
+       public void render(GeneralGC gc)
+       {
+               double inputHeight = 0;
+               for (int i = 0; i < inputCount; i++, inputHeight += 10)
+                       gc.drawLine(0, inputHeight, 10, inputHeight);
+               gc.drawLine(10, 0, 10, height);
+               gc.drawLine(10, height / 2, 20, height / 2);
+       }
+
+       @Override
+       public int getConnectedWireEndsCount()
+       {
+               return connectedWireEnds.size();
+       }
+
+       @Override
+       public ReadEnd getConnectedWireEnd(int connectionIndex)
+       {
+               return connectedWireEnds.get(connectionIndex);
+       }
+
+       @Override
+       public Point getWireEndConnectionPoint(int connectionI)
+       {
+               return WireEndConnectionPoints.get(connectionI);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/oldsrc/GUIMux.java b/LogicUI/oldsrc/GUIMux.java
new file mode 100644 (file)
index 0000000..d004d90
--- /dev/null
@@ -0,0 +1,80 @@
+package era.mi.gui.components;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import era.mi.logic.components.Mux;
+import era.mi.logic.wires.Wire.ReadEnd;
+import era.mi.logic.wires.Wire.ReadWriteEnd;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class GUIMux extends Mux implements GUIComponent
+{
+       private final double height;
+       private final List<ReadEnd> connectedWireEnds;
+       private final List<Point> WireEndConnectionPoints;
+
+       public GUIMux(int processTime, ReadWriteEnd out, ReadEnd select, ReadEnd... inputs)
+       {
+               super(processTime, out, select, inputs);
+
+               double height = inputs.length * 5;
+               if (height < 10)
+                       height = 10;
+               this.height = height;
+
+               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
+               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
+
+               connectedWireEndsModifiable.add(out);
+               WireEndConnectionPointsModifiable.add(new Point(20, 10 + height / 2));
+
+               connectedWireEndsModifiable.add(select);
+               WireEndConnectionPointsModifiable.add(new Point(10, 5));
+
+               {
+                       connectedWireEndsModifiable.addAll(Arrays.asList(inputs));
+                       double inputHeightIncrement = (height + 20) / inputs.length;
+                       double inputHeight = inputHeightIncrement / 2;
+                       for (int i = 0; i < inputs.length; i++, inputHeight += inputHeightIncrement)
+                               WireEndConnectionPointsModifiable.add(new Point(0, inputHeight));
+               }
+
+               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
+               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
+       }
+
+       @Override
+       public Rectangle getBounds()
+       {
+               return new Rectangle(0, 0, 20, height + 20);
+       }
+
+       @Override
+       public void render(GeneralGC gc)
+       {
+               gc.drawPolygon(new double[] { 0, 0, 20, 10, 20, height + 10, 0, height + 20 });
+       }
+
+       @Override
+       public int getConnectedWireEndsCount()
+       {
+               return connectedWireEnds.size();
+       }
+
+       @Override
+       public ReadEnd getConnectedWireEnd(int connectionIndex)
+       {
+               return connectedWireEnds.get(connectionIndex);
+       }
+
+       @Override
+       public Point getWireEndConnectionPoint(int connectionI)
+       {
+               return WireEndConnectionPoints.get(connectionI);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/oldsrc/GUISplitter.java b/LogicUI/oldsrc/GUISplitter.java
new file mode 100644 (file)
index 0000000..1bff424
--- /dev/null
@@ -0,0 +1,72 @@
+package era.mi.gui.components;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import era.mi.gui.ViewModel;
+import era.mi.logic.components.Splitter;
+import era.mi.logic.wires.Wire.ReadEnd;
+import era.mi.logic.wires.Wire.ReadWriteEnd;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class GUISplitter extends GUIComponent
+{
+       public GUISplitter(ViewModel model)
+       {
+               super(model);
+
+               this.outputCount = outputs.length;
+               this.height = (outputCount - 1) * 10;
+
+               connectedWireEndsModifiable.add(input);
+               WireEndConnectionPointsModifiable.add(new Point(0, height / 2));
+
+               {
+                       connectedWireEndsModifiable.addAll(Arrays.asList(outputs));
+                       double outputHeight = 0;
+                       for (int i = 0; i < outputCount; i++, outputHeight += 10)
+                               WireEndConnectionPointsModifiable.add(new Point(20, outputHeight));
+               }
+
+               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
+               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
+       }
+
+       @Override
+       public Rectangle getBounds()
+       {
+               return new Rectangle(0, 0, 20, height);
+       }
+
+       @Override
+       public void render(GeneralGC gc)
+       {
+               gc.drawLine(0, height / 2, 10, height / 2);
+               gc.drawLine(10, 0, 10, height);
+               double outputHeight = 0;
+               for (int i = 0; i < outputCount; i++, outputHeight += 10)
+                       gc.drawLine(10, outputHeight, 20, outputHeight);
+       }
+
+       @Override
+       public int getConnectedWireEndsCount()
+       {
+               return connectedWireEnds.size();
+       }
+
+       @Override
+       public ReadEnd getConnectedWireEnd(int connectionIndex)
+       {
+               return connectedWireEnds.get(connectionIndex);
+       }
+
+       @Override
+       public Point getWireEndConnectionPoint(int connectionI)
+       {
+               return WireEndConnectionPoints.get(connectionI);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/oldsrc/RSLatchGUIExample.java b/LogicUI/oldsrc/RSLatchGUIExample.java
new file mode 100644 (file)
index 0000000..5c104d7
--- /dev/null
@@ -0,0 +1,62 @@
+package era.mi.gui.examples;
+
+import era.mi.gui.LogicUICanvas;
+import era.mi.gui.LogicUIStandalone;
+import era.mi.gui.components.GUIManualSwitch;
+import era.mi.gui.components.GUINotGate;
+import era.mi.gui.components.GUIOrGateOld;
+import era.mi.gui.wires.WireConnectionPoint;
+import era.mi.logic.timeline.Timeline;
+import era.mi.logic.wires.Wire;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+
+public class RSLatchGUIExample
+{
+       private static final int WIRE_DELAY = 10;
+       private static final int OR_DELAY = 50;
+       private static final int NOT_DELAY = 50;
+
+       public static void main(String[] args)
+       {
+               Timeline t = new Timeline(11);
+               t.setTimeFunction(() -> System.currentTimeMillis()); // real time simulation
+               LogicUIStandalone ui = new LogicUIStandalone(t);
+               addComponentsAndWires(ui.getLogicUICanvas(), t);
+               ui.run();
+       }
+
+       public static void addComponentsAndWires(LogicUICanvas ui, Timeline t)
+       {
+               Wire r = new Wire(t, 1, WIRE_DELAY);
+               Wire s = new Wire(t, 1, WIRE_DELAY);
+               Wire t2 = new Wire(t, 1, WIRE_DELAY);
+               Wire t1 = new Wire(t, 1, WIRE_DELAY);
+               Wire q = new Wire(t, 1, WIRE_DELAY);
+               Wire nq = new Wire(t, 1, WIRE_DELAY);
+
+               GUIManualSwitch rIn = ui.addComponent(new GUIManualSwitch(t, r.createReadWriteEnd()), 100, 100);
+               GUIManualSwitch sIn = ui.addComponent(new GUIManualSwitch(t, s.createReadWriteEnd()), 100, 200);
+               GUIOrGateOld or1 = ui.addComponent(new GUIOrGateOld(t, OR_DELAY, t1.createReadWriteEnd(), r.createReadOnlyEnd(), nq.createReadOnlyEnd()),
+                               160, 102.5);
+               GUIOrGateOld or2 = ui.addComponent(new GUIOrGateOld(t, OR_DELAY, t2.createReadWriteEnd(), q.createReadOnlyEnd(), s.createReadOnlyEnd()),
+                               160, 192.5);
+               GUINotGate not1 = ui.addComponent(new GUINotGate(t, NOT_DELAY, t1.createReadOnlyEnd(), q.createReadWriteEnd()), 200, 107.5);
+               GUINotGate not2 = ui.addComponent(new GUINotGate(t, NOT_DELAY, t2.createReadOnlyEnd(), nq.createReadWriteEnd()), 200, 197.5);
+
+               WireConnectionPoint p1 = ui.addComponent(new WireConnectionPoint(q, 3), 250, 112.5);
+               WireConnectionPoint p2 = ui.addComponent(new WireConnectionPoint(nq, 3), 250, 202.5);
+               WireConnectionPoint o1 = ui.addComponent(new WireConnectionPoint(q, 1), 270, 112.5);
+               WireConnectionPoint o2 = ui.addComponent(new WireConnectionPoint(nq, 1), 270, 202.5);
+
+               ui.addWire(rIn, 0, or1, 0);
+               ui.addWire(sIn, 0, or2, 1);
+               ui.addWire(or1, 2, not1, 0);
+               ui.addWire(or2, 2, not2, 0);
+               ui.addWire(not1, 1, p1, 0);
+               ui.addWire(not2, 1, p2, 0);
+               ui.addWire(p1, 1, or2, 0, new Point(250, 130), new Point(140, 185), new Point(140, 197.5));
+               ui.addWire(p2, 1, or1, 1, new Point(250, 185), new Point(140, 130), new Point(140, 117.5));
+               ui.addWire(p1, 2, o1, 0);
+               ui.addWire(p2, 2, o2, 0);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/ColorHelper.java b/LogicUI/src/era/mi/gui/ColorHelper.java
new file mode 100644 (file)
index 0000000..ff6a495
--- /dev/null
@@ -0,0 +1,91 @@
+package era.mi.gui;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Device;
+
+import era.mi.logic.types.ColorDefinition;
+import era.mi.logic.types.ColorDefinition.BuiltInColor;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+
+//TODO replace with a proper ColorManager
+public class ColorHelper
+{
+       public static void executeWithDifferentForeground(GeneralGC gc, ColorDefinition col, Runnable exec)
+       {
+               executeWithDifferentColor(gc.getDevice(), col, gc::getForeground, gc::setForeground, exec);
+       }
+
+       public static void executeWithDifferentBackground(GeneralGC gc, ColorDefinition col, Runnable exec)
+       {
+               executeWithDifferentColor(gc.getDevice(), col, gc::getBackground, gc::setBackground, exec);
+       }
+
+       private static void executeWithDifferentColor(Device device, ColorDefinition col, Supplier<Color> getColor, Consumer<Color> setColor,
+                       Runnable exec)
+       {
+               Color oldColor = getColor.get();
+               boolean isNoSystemColor = col.builtInColor == null;
+               Color newColor;
+               if (isNoSystemColor)
+                       newColor = new Color(device, col.r, col.g, col.b);
+               else
+                       newColor = device.getSystemColor(ColorHelper.toSWTColorConstant(col.builtInColor));
+               setColor.accept(newColor);
+
+               exec.run();
+
+               setColor.accept(oldColor);
+               if (isNoSystemColor)
+                       newColor.dispose();
+       }
+
+       public static int toSWTColorConstant(BuiltInColor col)
+       {
+               switch (col)
+               {
+               case COLOR_BLACK:
+                       return SWT.COLOR_BLACK;
+               case COLOR_BLUE:
+                       return SWT.COLOR_BLUE;
+               case COLOR_CYAN:
+                       return SWT.COLOR_CYAN;
+               case COLOR_DARK_BLUE:
+                       return SWT.COLOR_DARK_BLUE;
+               case COLOR_DARK_CYAN:
+                       return SWT.COLOR_DARK_CYAN;
+               case COLOR_DARK_GRAY:
+                       return SWT.COLOR_DARK_GRAY;
+               case COLOR_DARK_GREEN:
+                       return SWT.COLOR_DARK_GREEN;
+               case COLOR_DARK_MAGENTA:
+                       return SWT.COLOR_DARK_MAGENTA;
+               case COLOR_DARK_RED:
+                       return SWT.COLOR_DARK_RED;
+               case COLOR_DARK_YELLOW:
+                       return SWT.COLOR_DARK_YELLOW;
+               case COLOR_GRAY:
+                       return SWT.COLOR_GRAY;
+               case COLOR_GREEN:
+                       return SWT.COLOR_GREEN;
+               case COLOR_MAGENTA:
+                       return SWT.COLOR_MAGENTA;
+               case COLOR_RED:
+                       return SWT.COLOR_RED;
+               case COLOR_WHITE:
+                       return SWT.COLOR_WHITE;
+               case COLOR_YELLOW:
+                       return SWT.COLOR_YELLOW;
+               default:
+                       throw new IllegalArgumentException("Unknown enum constant: " + col);
+               }
+       }
+
+       private ColorHelper()
+       {
+               throw new UnsupportedOperationException("No instances of ColorHelper");
+       }
+}
\ No newline at end of file
index fd6d4da..2115e7b 100644 (file)
@@ -1,19 +1,17 @@
 package era.mi.gui;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.function.Consumer;
 
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Event;
 
-import era.mi.gui.components.BasicGUIComponent;
-import era.mi.gui.wires.GUIWire;
+import era.mi.gui.model.ViewModel;
+import era.mi.gui.model.components.GUIComponent;
+import era.mi.gui.model.wires.Pin;
 import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.gcs.TranslatedGC;
 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
 import net.haspamelodica.swt.helper.zoomablecanvas.ZoomableCanvas;
 
 /**
@@ -23,53 +21,70 @@ import net.haspamelodica.swt.helper.zoomablecanvas.ZoomableCanvas;
  */
 public class LogicUICanvas extends ZoomableCanvas
 {
-       private final Set<BasicGUIComponent> components;
-       private final Map<BasicGUIComponent, Point> componentPositions;
-       private final Set<GUIWire> wires;
+       private final ViewModel model;
 
-       public LogicUICanvas(Composite parent, int style)
+       public LogicUICanvas(Composite parent, int style, ViewModel model)
        {
                super(parent, style);
 
-               components = new HashSet<>();
-               componentPositions = new HashMap<>();
-               wires = new HashSet<>();
+               this.model = model;
 
-               addZoomedRenderer(gc -> components.forEach(c -> drawComponent(gc, c)));
-               addZoomedRenderer(gc -> wires.forEach(w -> w.render(gc)));
-               addListener(SWT.MouseDown, this::mouseDown);
-       }
-
-       /**
-        * Add a component to be drawn. Returns the given component for convenience.
-        * 
-        * @author Daniel Kirschten
-        */
-       public <C extends BasicGUIComponent> C addComponent(C component, double x, double y)
-       {
-               components.add(component);
-               componentPositions.put(component, new Point(x, y));
-               return component;
-       }
+               Consumer<Object> redrawConsumer = o -> redrawThreadsafe();
+               Consumer<Pin> pinAddedListener = p ->
+               {
+                       p.addPinMovedListener(redrawConsumer);
+                       redrawThreadsafe();
+               };
+               Consumer<Pin> pinRemovedListener = p ->
+               {
+                       p.removePinMovedListener(redrawConsumer);
+                       redrawThreadsafe();
+               };
+               model.addComponentAddedListener(c ->
+               {
+                       c.addComponentChangedListener(redrawConsumer);
+                       c.addComponentMovedListener(redrawConsumer);
+                       c.addPinAddedListener(pinAddedListener);
+                       c.addPinRemovedListener(pinRemovedListener);
+                       redrawThreadsafe();
+               });
+               model.addComponentRemovedListener(c ->
+               {
+                       c.removeComponentChangedListener(redrawConsumer);
+                       c.removeComponentMovedListener(redrawConsumer);
+                       c.removePinAddedListener(pinAddedListener);
+                       c.removePinRemovedListener(pinRemovedListener);
+                       redrawThreadsafe();
+               });
+               model.addWireAddedListener(w ->
+               {
+                       w.addWireChangedListener(redrawConsumer);
+                       redrawThreadsafe();
+               });
+               model.addWireRemovedListener(w ->
+               {
+                       w.removeWireChangedListener(redrawConsumer);
+                       redrawThreadsafe();
+               });
 
-       /**
-        * Add a graphical wire between the given connection points of the given components. The given components have to be added and the given
-        * connection points have to be connected logically first.
-        * 
-        * @author Daniel Kirschten
-        */
-       public void addWire(BasicGUIComponent component1, int component1ConnectionIndex, BasicGUIComponent component2,
-                       int component2ConnectionIndex, Point... path)
-       {
-               wires.add(new GUIWire(this::redrawThreadsafe, component1, component1ConnectionIndex, componentPositions.get(component1), component2,
-                               component2ConnectionIndex, componentPositions.get(component2), path));
+               addZoomedRenderer(gc ->
+               {
+                       Rectangle visibleRegion = new Rectangle(offX, offY, gW / zoom, gH / zoom);
+                       model.getComponents().forEach(c -> drawComponent(gc, c, visibleRegion));
+               });
+               addZoomedRenderer(gc -> model.getWires().forEach(w -> w.render(gc)));
+               addListener(SWT.MouseDown, this::mouseDown);
        }
 
-       private void drawComponent(GeneralGC gc, BasicGUIComponent component)
+       private void drawComponent(GeneralGC gc, GUIComponent component, Rectangle visibleRegion)
        {
-               TranslatedGC tgc = new TranslatedGC(gc, componentPositions.get(component));
-               component.render(tgc);
-               tgc.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLUE));
+               component.render(gc, visibleRegion);
+               gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_CYAN));
+               for (Pin p : component.getPins())
+               {
+                       Point pos = p.getPos();
+                       gc.fillOval(pos.x - 1, pos.y - 1, 2, 2);
+               }
        }
 
        private void mouseDown(Event e)
@@ -77,11 +92,10 @@ public class LogicUICanvas extends ZoomableCanvas
                if (e.button == 1)
                {
                        Point click = displayToWorldCoords(e.x, e.y);
-                       for (BasicGUIComponent component : components)
-                               if (component.getBounds().translate(componentPositions.get(component)).contains(click))
+                       for (GUIComponent component : model.getComponents())
+                               if (component.getBounds().contains(click) && component.clicked(click.x, click.y))
                                {
-                                       if (component.clicked(click.x, click.y))
-                                               redraw();
+                                       redraw();
                                        break;
                                }
                }
index 2febb5c..a471166 100644 (file)
@@ -7,7 +7,7 @@ import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
 
-import era.mi.logic.timeline.Timeline;
+import era.mi.gui.model.ViewModel;
 import net.haspamelodica.swt.helper.zoomablecanvas.helper.ZoomableCanvasOverlay;
 import net.haspamelodica.swt.helper.zoomablecanvas.helper.ZoomableCanvasUserInput;
 
@@ -18,18 +18,19 @@ import net.haspamelodica.swt.helper.zoomablecanvas.helper.ZoomableCanvasUserInpu
  */
 public class LogicUIStandalone
 {
+       private ViewModel model;
+
        private final Display display;
        private final Shell shell;
        private final LogicUICanvas ui;
-       private Timeline timeline;
 
-       public LogicUIStandalone(Timeline timeline)
+       public LogicUIStandalone(ViewModel model)
        {
-               this.timeline = timeline;
+               this.model = model;
                display = new Display();
                shell = new Shell(display);
                shell.setLayout(new FillLayout());
-               ui = new LogicUICanvas(shell, SWT.NONE);
+               ui = new LogicUICanvas(shell, SWT.NONE, model);
 
                ZoomableCanvasUserInput userInput = new ZoomableCanvasUserInput(ui);
                userInput.buttonDrag = 3;
@@ -49,39 +50,40 @@ public class LogicUIStandalone
        public void run()
        {
                AtomicBoolean running = new AtomicBoolean(true);
-               Thread simulationThread = new Thread(() ->
-               {
-                       while (running.get())
-                       {
-                               // always execute to keep timeline from "hanging behind" for too long
-                               timeline.executeUntil(timeline.laterThan(System.currentTimeMillis()), System.currentTimeMillis() + 10);
-                               long sleepTime;
-                               if (timeline.hasNext())
-                                       sleepTime = timeline.nextEventTime() - System.currentTimeMillis();
-                               else
-                                       sleepTime = 10;
-                               try
-                               {
-                                       if (sleepTime > 0)
-                                               Thread.sleep(sleepTime);
-                               }
-                               catch (InterruptedException e)
-                               {
-                               } // it is normal execution flow to be interrupted
-                       }
-               });
-               simulationThread.start();
-               timeline.addEventAddedListener(event ->
-               {
-                       if (event.getTiming() <= System.currentTimeMillis())
-                               simulationThread.interrupt();
-               });
+//             Thread simulationThread = new Thread(() ->
+//             {
+//                     while (running.get())
+//                     {
+//                             // always execute to keep timeline from "hanging behind" for too long
+//                             timeline.executeUntil(timeline.laterThan(System.currentTimeMillis()), System.currentTimeMillis() + 10);         
+//                             model.timeline.executeUpTo(System.currentTimeMillis(), System.currentTimeMillis() + 10);
+//                             long sleepTime;
+//                             if (model.timeline.hasNext())
+//                                     sleepTime = model.timeline.nextEventTime() - System.currentTimeMillis();
+//                             else
+//                                     sleepTime = 10;
+//                             try
+//                             {
+//                                     if (sleepTime > 0)
+//                                             Thread.sleep(sleepTime);
+//                             }
+//                             catch (InterruptedException e)
+//                             {
+//                             } // it is normal execution flow to be interrupted
+//                     }
+//             });
+//             simulationThread.start();
+//             model.timeline.addEventAddedListener(event ->
+//             {
+//                     if (event.getTiming() <= System.currentTimeMillis())
+//                             simulationThread.interrupt();
+//             });
 
                shell.open();
                while (!shell.isDisposed())
                        if (!display.readAndDispatch())
                                display.sleep();
                running.set(false);
-               simulationThread.interrupt();
+//             simulationThread.interrupt();
        }
 }
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/components/BasicGUIComponent.java b/LogicUI/src/era/mi/gui/components/BasicGUIComponent.java
deleted file mode 100644 (file)
index 2d27685..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package era.mi.gui.components;
-
-import era.mi.logic.wires.Wire.ReadEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public interface BasicGUIComponent
-{
-       /**
-        * Render this component to the given gc, at coordinates (0, 0).
-        */
-       public void render(GeneralGC gc);
-
-       /**
-        * Returns the bounds of this component. Used for calculating which component is clicked.
-        */
-       public Rectangle getBounds();
-
-       /**
-        * Called when this component is clicked. Relative coordinates of the click are given. Returns true if this component has to be redrawn.
-        */
-       public default boolean clicked(double x, double y)
-       {
-               return false;
-       }
-
-       // TODO this code will be replaced by code in BasicComponent.
-       /**
-        * Returns how many wire arrays are connected to this component. (Connections are static - they can't be removed and no new ones can be
-        * added)
-        */
-       public int getConnectedWireEndsCount();
-
-       /**
-        * Returns the n-th wire array connected to this component.
-        */
-       public ReadEnd getConnectedWireEnd(int connectionIndex);
-
-       /**
-        * Returns relative coordinates where the n-th wire array is connected to this component.
-        */
-       public Point getWireEndConnectionPoint(int connectionIndex);
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/components/GUIAndGate.java b/LogicUI/src/era/mi/gui/components/GUIAndGate.java
deleted file mode 100644 (file)
index c76d65a..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package era.mi.gui.components;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import era.mi.logic.components.gates.AndGate;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Font;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUIAndGate extends AndGate implements BasicGUIComponent
-{
-       private static final String LABEL = "&";
-
-       private final int inputCount;
-       private final double height;
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> wireEndConnectionPoints;
-
-       public GUIAndGate(Timeline timeline, int processTime, ReadWriteEnd out, ReadEnd... in)
-       {
-               super(timeline, processTime, out, in);
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> wireEndConnectionPointsModifiable = new ArrayList<>();
-
-               this.inputCount = in.length;
-               this.height = inputCount * 10;
-
-               {
-                       connectedWireEndsModifiable.addAll(Arrays.asList(in));
-                       double inputHeight = 5;
-                       for (int i = 0; i < inputCount; i++, inputHeight += 10)
-                               wireEndConnectionPointsModifiable.add(new Point(0, inputHeight));
-               }
-
-               connectedWireEndsModifiable.add(out);
-               wireEndConnectionPointsModifiable.add(new Point(20, height / 2));
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.wireEndConnectionPoints = Collections.unmodifiableList(wireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, height);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               gc.drawRectangle(0, 0, 20, height);
-               Font oldFont = gc.getFont();
-               Font labelFont = new Font(oldFont.getName(), 5, oldFont.getStyle());
-               gc.setFont(labelFont);
-               Point textExtent = gc.textExtent(LABEL);
-               gc.drawText(LABEL, 10 - textExtent.x / 2, (height - textExtent.y) / 2, true);
-               gc.setFont(oldFont);
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return wireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
index fde7bbd..bcfa464 100644 (file)
@@ -1,96 +1,6 @@
 package era.mi.gui.components;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import era.mi.logic.components.ManualSwitch;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.types.Bit;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Font;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUIManualSwitch extends ManualSwitch implements BasicGUIComponent
+public class GUIManualSwitch
 {
-       private static final Map<Bit, String> bitNames;
-       static
-       {
-               Map<Bit, String> bitNamesModifiable = new HashMap<>();
-               bitNamesModifiable.put(Bit.ONE, "1");
-               bitNamesModifiable.put(Bit.ZERO, "0");
-               bitNamesModifiable.put(Bit.Z, "Z");
-               bitNamesModifiable.put(Bit.U, "U");
-               bitNamesModifiable.put(Bit.X, "X");
-               bitNames = Collections.unmodifiableMap(bitNamesModifiable);
-       }
-
-       private final ReadEnd we;
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> wireEndConnectionPoints;
-
-       public GUIManualSwitch(Timeline timeline, ReadWriteEnd output)
-       {
-               super(timeline, output);
-
-               this.we = output;
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> wireEndConnectionPointsModifiable = new ArrayList<>();
-
-               connectedWireEndsModifiable.add(output);
-               wireEndConnectionPointsModifiable.add(new Point(20, 7.5));
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.wireEndConnectionPoints = Collections.unmodifiableList(wireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, 15);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               gc.drawRectangle(0, 0, 20, 15);
-               String label = bitNames.get(we.getValue());
-               Font oldFont = gc.getFont();
-               Font labelFont = new Font(oldFont.getName(), 6, oldFont.getStyle());
-               gc.setFont(labelFont);
-               Point textExtent = gc.textExtent(label);
-               gc.drawText(label, 10 - textExtent.x / 2, 7.5 - textExtent.y / 2, true);
-               gc.setFont(oldFont);
-       }
-
-       @Override
-       public boolean clicked(double x, double y)
-       {
-               timeline.addEvent((e) -> toggle(), (int) (System.currentTimeMillis() - timeline.getSimulationTime()));
-               return true;
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
 
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return wireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
+}
diff --git a/LogicUI/src/era/mi/gui/components/GUIMerger.java b/LogicUI/src/era/mi/gui/components/GUIMerger.java
deleted file mode 100644 (file)
index 240855c..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package era.mi.gui.components;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import era.mi.logic.components.Merger;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUIMerger extends Merger implements BasicGUIComponent
-{
-       private final int inputCount;
-       private final double height;
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> WireEndConnectionPoints;
-
-       public GUIMerger(Timeline timeline, ReadWriteEnd union, ReadEnd... inputs)
-       {
-               super(timeline, union, inputs);
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
-
-               this.inputCount = inputs.length;
-               this.height = (inputCount - 1) * 10;
-
-               {
-                       connectedWireEndsModifiable.addAll(Arrays.asList(inputs));
-                       double inputHeight = 0;
-                       for (int i = 0; i < inputCount; i++, inputHeight += 10)
-                               WireEndConnectionPointsModifiable.add(new Point(0, inputHeight));
-               }
-
-               connectedWireEndsModifiable.add(union);
-               WireEndConnectionPointsModifiable.add(new Point(20, height / 2));
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, height);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               double inputHeight = 0;
-               for (int i = 0; i < inputCount; i++, inputHeight += 10)
-                       gc.drawLine(0, inputHeight, 10, inputHeight);
-               gc.drawLine(10, 0, 10, height);
-               gc.drawLine(10, height / 2, 20, height / 2);
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return WireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/components/GUIMux.java b/LogicUI/src/era/mi/gui/components/GUIMux.java
deleted file mode 100644 (file)
index 0eba8f5..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package era.mi.gui.components;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import era.mi.logic.components.Mux;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUIMux extends Mux implements BasicGUIComponent
-{
-       private final double height;
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> WireEndConnectionPoints;
-
-       public GUIMux(Timeline timeline, int processTime, ReadWriteEnd out, ReadEnd select, ReadEnd... inputs)
-       {
-               super(timeline, processTime, out, select, inputs);
-
-               double height = inputs.length * 5;
-               if (height < 10)
-                       height = 10;
-               this.height = height;
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
-
-               connectedWireEndsModifiable.add(out);
-               WireEndConnectionPointsModifiable.add(new Point(20, 10 + height / 2));
-
-               connectedWireEndsModifiable.add(select);
-               WireEndConnectionPointsModifiable.add(new Point(10, 5));
-
-               {
-                       connectedWireEndsModifiable.addAll(Arrays.asList(inputs));
-                       double inputHeightIncrement = (height + 20) / inputs.length;
-                       double inputHeight = inputHeightIncrement / 2;
-                       for (int i = 0; i < inputs.length; i++, inputHeight += inputHeightIncrement)
-                               WireEndConnectionPointsModifiable.add(new Point(0, inputHeight));
-               }
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, height + 20);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               gc.drawPolygon(new double[] { 0, 0, 20, 10, 20, height + 10, 0, height + 20 });
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return WireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/components/GUINotGate.java b/LogicUI/src/era/mi/gui/components/GUINotGate.java
deleted file mode 100644 (file)
index fa4730a..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package era.mi.gui.components;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import era.mi.logic.components.gates.NotGate;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Font;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUINotGate extends NotGate implements BasicGUIComponent
-{
-       private static final String LABEL = "\u22651";// >=1
-
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> WireEndConnectionPoints;
-
-       public GUINotGate(Timeline timeline, int processTime, ReadEnd in, ReadWriteEnd out)
-       {
-               super(timeline, processTime, in, out);
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
-
-               connectedWireEndsModifiable.add(in);
-               WireEndConnectionPointsModifiable.add(new Point(0, 5));
-
-               connectedWireEndsModifiable.add(out);
-               WireEndConnectionPointsModifiable.add(new Point(20, 5));
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, 10);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               gc.drawRectangle(0, 0, 17, 10);
-               Font oldFont = gc.getFont();
-               Font labelFont = new Font(oldFont.getName(), 5, oldFont.getStyle());
-               gc.setFont(labelFont);
-               Point textExtent = gc.textExtent(LABEL);
-               gc.drawText(LABEL, 8.5 - textExtent.x / 2, 5 - textExtent.y / 2, true);
-               gc.setFont(oldFont);
-               gc.drawOval(17, 3.5, 3, 3);
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return WireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/components/GUIOrGate.java b/LogicUI/src/era/mi/gui/components/GUIOrGate.java
deleted file mode 100644 (file)
index 549cb40..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package era.mi.gui.components;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import era.mi.logic.components.gates.OrGate;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Font;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUIOrGate extends OrGate implements BasicGUIComponent
-{
-       private static final String LABEL = "\u22651";// >=1
-
-       private final int inputCount;
-       private final double height;
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> WireEndConnectionPoints;
-
-       public GUIOrGate(Timeline timeline, int processTime, ReadWriteEnd out, ReadEnd... in)
-       {
-               super(timeline, processTime, out, in);
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
-
-               this.inputCount = in.length;
-               this.height = inputCount * 10;
-
-               {
-                       connectedWireEndsModifiable.addAll(Arrays.asList(in));
-                       double inputHeight = 5;
-                       for (int i = 0; i < inputCount; i++, inputHeight += 10)
-                               WireEndConnectionPointsModifiable.add(new Point(0, inputHeight));
-               }
-
-               connectedWireEndsModifiable.add(out);
-               WireEndConnectionPointsModifiable.add(new Point(20, height / 2));
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, height);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               gc.drawRectangle(0, 0, 20, height);
-               Font oldFont = gc.getFont();
-               Font labelFont = new Font(oldFont.getName(), 5, oldFont.getStyle());
-               gc.setFont(labelFont);
-               Point textExtent = gc.textExtent(LABEL);
-               gc.drawText(LABEL, 10 - textExtent.x / 2, (height - textExtent.y) / 2, true);
-               gc.setFont(oldFont);
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return WireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/components/GUISplitter.java b/LogicUI/src/era/mi/gui/components/GUISplitter.java
deleted file mode 100644 (file)
index 6873e63..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package era.mi.gui.components;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import era.mi.logic.components.Splitter;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire.ReadEnd;
-import era.mi.logic.wires.Wire.ReadWriteEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class GUISplitter extends Splitter implements BasicGUIComponent
-{
-       private final int outputCount;
-       private final double height;
-       private final List<ReadEnd> connectedWireEnds;
-       private final List<Point> WireEndConnectionPoints;
-
-       public GUISplitter(Timeline timeline, ReadEnd input, ReadWriteEnd... outputs)
-       {
-               super(timeline, input, outputs);
-
-               List<ReadEnd> connectedWireEndsModifiable = new ArrayList<>();
-               List<Point> WireEndConnectionPointsModifiable = new ArrayList<>();
-
-               this.outputCount = outputs.length;
-               this.height = (outputCount - 1) * 10;
-
-               connectedWireEndsModifiable.add(input);
-               WireEndConnectionPointsModifiable.add(new Point(0, height / 2));
-
-               {
-                       connectedWireEndsModifiable.addAll(Arrays.asList(outputs));
-                       double outputHeight = 0;
-                       for (int i = 0; i < outputCount; i++, outputHeight += 10)
-                               WireEndConnectionPointsModifiable.add(new Point(20, outputHeight));
-               }
-
-               this.connectedWireEnds = Collections.unmodifiableList(connectedWireEndsModifiable);
-               this.WireEndConnectionPoints = Collections.unmodifiableList(WireEndConnectionPointsModifiable);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 20, height);
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               gc.drawLine(0, height / 2, 10, height / 2);
-               gc.drawLine(10, 0, 10, height);
-               double outputHeight = 0;
-               for (int i = 0; i < outputCount; i++, outputHeight += 10)
-                       gc.drawLine(10, outputHeight, 20, outputHeight);
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return connectedWireEnds.size();
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return connectedWireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionI)
-       {
-               return WireEndConnectionPoints.get(connectionI);
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/examples/Playground.java b/LogicUI/src/era/mi/gui/examples/Playground.java
new file mode 100644 (file)
index 0000000..78524b7
--- /dev/null
@@ -0,0 +1,37 @@
+package era.mi.gui.examples;
+
+import org.eclipse.swt.SWT;
+
+import era.mi.gui.LogicUIStandalone;
+import era.mi.gui.model.ViewModel;
+import era.mi.gui.model.components.GUIAndGate;
+import era.mi.gui.model.components.GUINotGate;
+import era.mi.gui.model.wires.GUIWire;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+
+public class Playground
+{
+       private static final int WIRE_DELAY = 10;
+       private static final int OR_DELAY = 50;
+       private static final int NOT_DELAY = 50;
+
+       public static void main(String[] args)
+       {
+               ViewModel model = new ViewModel();
+               LogicUIStandalone ui = new LogicUIStandalone(model);
+               addComponentsAndWires(ui, model);
+               ui.run();
+       }
+
+       public static void addComponentsAndWires(LogicUIStandalone ui, ViewModel model)
+       {
+               GUIAndGate andGate = new GUIAndGate(model);
+               andGate.moveTo(10, 10);
+               GUINotGate notGate = new GUINotGate(model);
+               notGate.moveTo(10, 40);
+
+               new GUIWire(model, andGate.getPins().get(0), notGate.getPins().get(1), new Point(20, 50));
+
+               ui.getLogicUICanvas().addListener(SWT.KeyDown, e -> notGate.moveTo(150, 10));
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/examples/RSLatchGUIExample.java b/LogicUI/src/era/mi/gui/examples/RSLatchGUIExample.java
deleted file mode 100644 (file)
index 76a10f8..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package era.mi.gui.examples;
-
-import era.mi.gui.LogicUICanvas;
-import era.mi.gui.LogicUIStandalone;
-import era.mi.gui.components.GUIManualSwitch;
-import era.mi.gui.components.GUINotGate;
-import era.mi.gui.components.GUIOrGate;
-import era.mi.gui.wires.WireConnectionPoint;
-import era.mi.logic.timeline.Timeline;
-import era.mi.logic.wires.Wire;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-
-public class RSLatchGUIExample
-{
-       private static final int WIRE_DELAY = 10;
-       private static final int OR_DELAY = 50;
-       private static final int NOT_DELAY = 50;
-
-       public static void main(String[] args)
-       {
-               Timeline t = new Timeline(11);
-               t.setTimeFunction(() -> System.currentTimeMillis()); // real time simulation
-               LogicUIStandalone ui = new LogicUIStandalone(t);
-               addComponentsAndWires(ui.getLogicUICanvas(), t);
-               ui.run();
-       }
-
-       public static void addComponentsAndWires(LogicUICanvas ui, Timeline t)
-       {
-               Wire r = new Wire(t, 1, WIRE_DELAY);
-               Wire s = new Wire(t, 1, WIRE_DELAY);
-               Wire t2 = new Wire(t, 1, WIRE_DELAY);
-               Wire t1 = new Wire(t, 1, WIRE_DELAY);
-               Wire q = new Wire(t, 1, WIRE_DELAY);
-               Wire nq = new Wire(t, 1, WIRE_DELAY);
-
-               GUIManualSwitch rIn = ui.addComponent(new GUIManualSwitch(t, r.createReadWriteEnd()), 100, 100);
-               GUIManualSwitch sIn = ui.addComponent(new GUIManualSwitch(t, s.createReadWriteEnd()), 100, 200);
-               GUIOrGate or1 = ui.addComponent(new GUIOrGate(t, OR_DELAY, t1.createReadWriteEnd(), r.createReadOnlyEnd(), nq.createReadOnlyEnd()),
-                               160, 102.5);
-               GUIOrGate or2 = ui.addComponent(new GUIOrGate(t, OR_DELAY, t2.createReadWriteEnd(), q.createReadOnlyEnd(), s.createReadOnlyEnd()),
-                               160, 192.5);
-               GUINotGate not1 = ui.addComponent(new GUINotGate(t, NOT_DELAY, t1.createReadOnlyEnd(), q.createReadWriteEnd()), 200, 107.5);
-               GUINotGate not2 = ui.addComponent(new GUINotGate(t, NOT_DELAY, t2.createReadOnlyEnd(), nq.createReadWriteEnd()), 200, 197.5);
-
-               WireConnectionPoint p1 = ui.addComponent(new WireConnectionPoint(q, 3), 250, 112.5);
-               WireConnectionPoint p2 = ui.addComponent(new WireConnectionPoint(nq, 3), 250, 202.5);
-               WireConnectionPoint o1 = ui.addComponent(new WireConnectionPoint(q, 1), 270, 112.5);
-               WireConnectionPoint o2 = ui.addComponent(new WireConnectionPoint(nq, 1), 270, 202.5);
-
-               ui.addWire(rIn, 0, or1, 0);
-               ui.addWire(sIn, 0, or2, 1);
-               ui.addWire(or1, 2, not1, 0);
-               ui.addWire(or2, 2, not2, 0);
-               ui.addWire(not1, 1, p1, 0);
-               ui.addWire(not2, 1, p2, 0);
-               ui.addWire(p1, 1, or2, 0, new Point(250, 130), new Point(140, 185), new Point(140, 197.5));
-               ui.addWire(p2, 1, or1, 1, new Point(250, 185), new Point(140, 130), new Point(140, 117.5));
-               ui.addWire(p1, 2, o1, 0);
-               ui.addWire(p2, 2, o2, 0);
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/ViewModel.java b/LogicUI/src/era/mi/gui/model/ViewModel.java
new file mode 100644 (file)
index 0000000..7eb55c1
--- /dev/null
@@ -0,0 +1,110 @@
+package era.mi.gui.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+import era.mi.gui.model.components.GUIComponent;
+import era.mi.gui.model.wires.GUIWire;
+
+public class ViewModel
+{
+       private final List<GUIComponent> components;
+       private final List<GUIComponent> componentsUnmodifiable;
+       private final List<GUIWire> wires;
+       private final List<GUIWire> wiresUnmodifiable;
+
+       private final List<Consumer<? super GUIComponent>> componentAddedListeners;
+       private final List<Consumer<? super GUIComponent>> componentRemovedListeners;
+       private final List<Consumer<? super GUIWire>> wireAddedListeners;
+       private final List<Consumer<? super GUIWire>> wireRemovedListeners;
+
+       public ViewModel()
+       {
+               components = new ArrayList<>();
+               componentsUnmodifiable = Collections.unmodifiableList(components);
+               wires = new ArrayList<>();
+               wiresUnmodifiable = Collections.unmodifiableList(wires);
+
+               componentAddedListeners = new ArrayList<>();
+               componentRemovedListeners = new ArrayList<>();
+               wireAddedListeners = new ArrayList<>();
+               wireRemovedListeners = new ArrayList<>();
+       }
+
+       /**
+        * Adds the given component to the list of components and calls all componentAddedListeners. Don't call this method from application
+        * code as it is automatically called in GUIComponent::new.
+        */
+       public void componentCreated(GUIComponent component)
+       {
+               if (components.contains(component))
+                       throw new IllegalStateException("Don't add the same component twice!");
+               components.add(component);
+               callComponentAddedListeners(component);
+       }
+
+       /**
+        * Removes the given component from the list of components and calls all componentRemovedListeners. Don't call this method from
+        * application code as it is automatically called in GUIComponent::destroy.
+        */
+       public void componentDestroyed(GUIComponent component)
+       {
+               if (!components.contains(component))
+                       throw new IllegalStateException("Don't remove the same component twice!");
+               components.remove(component);
+               callComponentRemovedListeners(component);
+       }
+
+       /**
+        * Adds the given component to the list of components and calls all componentAddedListeners. Don't call this method from application
+        * code as it is automatically called in GUIComponent::new.
+        */
+       public void wireCreated(GUIWire wire)
+       {
+               if (wires.contains(wire))
+                       throw new IllegalStateException("Don't add the same wire twice!");
+               wires.add(wire);
+               callWireAddedListeners(wire);
+       }
+
+       /**
+        * Removes the given component from the list of components and calls all componentRemovedListeners. Don't call this method from
+        * application code as it is automatically called in GUIComponent::destroy.
+        */
+       public void wireDestroyed(GUIWire wire)
+       {
+               if (!wires.contains(wire))
+                       throw new IllegalStateException("Don't remove the same wire twice!");
+               wires.remove(wire);
+               callWireRemovedListeners(wire);
+       }
+
+       public List<GUIComponent> getComponents()
+       {
+               return componentsUnmodifiable;
+       }
+
+       public List<GUIWire> getWires()
+       {
+               return wiresUnmodifiable;
+       }
+
+       // @formatter:off
+       public void addComponentAddedListener     (Consumer<? super GUIComponent> listener){componentAddedListeners  .add   (listener);}
+       public void addComponentRemovedListener   (Consumer<? super GUIComponent> listener){componentRemovedListeners.add   (listener);}
+       public void addWireAddedListener          (Consumer<? super GUIWire     > listener){wireAddedListeners       .add   (listener);}
+       public void addWireRemovedListener        (Consumer<? super GUIWire     > listener){wireRemovedListeners     .add   (listener);}
+
+       public void removeComponentAddedListener  (Consumer<? super GUIComponent> listener){componentAddedListeners  .remove(listener);}
+       public void removeComponentRemovedListener(Consumer<? super GUIComponent> listener){componentRemovedListeners.remove(listener);}
+       public void removeWireAddedListener       (Consumer<? super GUIWire     > listener){wireAddedListeners       .remove(listener);}
+       public void removeWireRemovedListener     (Consumer<? super GUIWire     > listener){wireRemovedListeners     .remove(listener);}
+
+       private void callComponentAddedListeners  (GUIComponent c) {componentAddedListeners  .forEach(l -> l.accept(c));}
+       private void callComponentRemovedListeners(GUIComponent c) {componentRemovedListeners.forEach(l -> l.accept(c));}
+       private void callWireAddedListeners       (GUIWire w     ) {wireAddedListeners       .forEach(l -> l.accept(w));}
+       private void callWireRemovedListeners     (GUIWire w     ) {wireRemovedListeners     .forEach(l -> l.accept(w));}
+       // @formatter:on
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/components/GUIAndGate.java b/LogicUI/src/era/mi/gui/model/components/GUIAndGate.java
new file mode 100644 (file)
index 0000000..4f69d36
--- /dev/null
@@ -0,0 +1,12 @@
+package era.mi.gui.model.components;
+
+import era.mi.gui.model.ViewModel;
+
+public class GUIAndGate extends RectangularShapedGUIGate
+{
+       public GUIAndGate(ViewModel model)
+       {
+               super(model, "&", false);
+               setInputCount(2);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/components/GUIComponent.java b/LogicUI/src/era/mi/gui/model/components/GUIComponent.java
new file mode 100644 (file)
index 0000000..dc06461
--- /dev/null
@@ -0,0 +1,118 @@
+package era.mi.gui.model.components;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+import era.mi.gui.model.ViewModel;
+import era.mi.gui.model.wires.Pin;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public abstract class GUIComponent
+{
+       protected final ViewModel model;
+       private final Rectangle bounds;
+       private final List<Pin> pins;
+       protected final List<Pin> pinsUnmodifiable;
+
+       private final List<Consumer<? super GUIComponent>> componentChangedListeners;
+       private final List<Consumer<? super GUIComponent>> componentMovedListeners;
+       private final List<Consumer<? super Pin>> pinAddedListeners;
+       private final List<Consumer<? super Pin>> pinRemovedListeners;
+
+       public GUIComponent(ViewModel model)
+       {
+               this.model = model;
+               this.bounds = new Rectangle(0, 0, 0, 0);
+               this.pins = new ArrayList<>();
+               this.pinsUnmodifiable = Collections.unmodifiableList(pins);
+
+               this.componentChangedListeners = new ArrayList<>();
+               this.componentMovedListeners = new ArrayList<>();
+               this.pinAddedListeners = new ArrayList<>();
+               this.pinRemovedListeners = new ArrayList<>();
+
+               model.componentCreated(this);
+       }
+
+       public void destroy()
+       {
+               pins.forEach(p -> pinRemovedListeners.forEach(l -> l.accept(p)));
+               model.componentDestroyed(this);
+       }
+
+       public void moveTo(double x, double y)
+       {
+               bounds.x = x;
+               bounds.y = y;
+               callComponentMovedListeners();
+       }
+
+       /**
+        * Returns the bounds of this component. Used for calculating which component is clicked.
+        */
+       public Rectangle getBounds()
+       {
+               return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
+       }
+
+       /**
+        * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
+        * click.
+        */
+       public boolean clicked(double x, double y)
+       {
+               return false;
+       }
+
+       /**
+        * Returns a list of pins of this component.
+        */
+       public List<Pin> getPins()
+       {
+               return pinsUnmodifiable;
+       }
+
+       // @formatter:off
+       public void addComponentChangedListener   (Consumer<? super GUIComponent> listener) {componentChangedListeners.add   (listener);}
+       public void addComponentMovedListener     (Consumer<? super GUIComponent> listener) {componentMovedListeners  .add   (listener);}
+       public void addPinAddedListener           (Consumer<? super Pin         > listener) {pinAddedListeners        .add   (listener);}
+       public void addPinRemovedListener         (Consumer<? super Pin         > listener) {pinRemovedListeners      .add   (listener);}
+
+       public void removeComponentChangedListener(Consumer<? super GUIComponent> listener) {componentChangedListeners.remove(listener);}
+       public void removeComponentMovedListener  (Consumer<? super GUIComponent> listener) {componentMovedListeners  .remove(listener);}
+       public void removePinAddedListener        (Consumer<? super Pin         > listener) {pinAddedListeners        .remove(listener);}
+       public void removePinRemovedListener      (Consumer<? super Pin         > listener) {pinRemovedListeners      .remove(listener);}
+
+       protected void callComponentChangedListeners(     ) {componentChangedListeners.forEach(l -> l.accept(this));}
+       private   void callComponentMovedListeners  (     ) {componentMovedListeners  .forEach(l -> l.accept(this));}
+       private   void callPinAddedListeners        (Pin p) {pinAddedListeners        .forEach(l -> l.accept(p   ));}
+       private   void callPinRemovedListeners      (Pin p) {pinRemovedListeners      .forEach(l -> l.accept(p   ));}
+       // @form  atter:on
+
+       /**
+        * Render this component to the given gc.
+        */
+       public abstract void render(GeneralGC gc, Rectangle visibleRegion);
+
+       protected void setSize(double width, double height)
+       {
+               bounds.width = width;
+               bounds.height = height;
+               callComponentChangedListeners();
+       }
+
+       protected void addPin(Pin pin)
+       {
+               pins.add(pin);
+               callPinAddedListeners(pin);
+       }
+
+       protected void removePin(Pin pin)
+       {
+               pins.remove(pin);
+               callPinRemovedListeners(pin);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/components/GUIManualSwitch.java b/LogicUI/src/era/mi/gui/model/components/GUIManualSwitch.java
new file mode 100644 (file)
index 0000000..870a55a
--- /dev/null
@@ -0,0 +1,56 @@
+package era.mi.gui.model.components;
+
+import era.mi.gui.model.ViewModel;
+import era.mi.gui.model.wires.Pin;
+import era.mi.logic.components.ManualSwitch;
+import era.mi.logic.types.BitVectorFormatter;
+import era.mi.logic.wires.Wire.ReadEnd;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Font;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class GUIManualSwitch extends GUIComponent
+{
+       private static final double width = 20;
+       private static final double height = 15;
+       private static final double fontHeight = 5;
+
+       private ManualSwitch logicSwitch;
+       private ReadEnd end;
+
+       public GUIManualSwitch(ViewModel model)
+       {
+               super(model);
+               setSize(width, height);
+               addPin(new Pin(this, width, height / 2));
+       }
+
+       @Override
+       public void render(GeneralGC gc, Rectangle visibleRegion)
+       {
+               gc.drawRectangle(0, 0, width, height);
+               String label = BitVectorFormatter.formatValueAsString(end);
+               Font oldFont = gc.getFont();
+               Font labelFont = new Font(oldFont.getName(), fontHeight, oldFont.getStyle());
+               gc.setFont(labelFont);
+               Point textExtent = gc.textExtent(label);
+               gc.drawText(label, (width - textExtent.x) / 2, (height - textExtent.y) / 2, true);
+               gc.setFont(oldFont);
+       }
+
+       public void setLogicModelBinding(ManualSwitch logicSwitch, ReadEnd end)
+       {
+               this.logicSwitch = logicSwitch;
+               this.end = end;
+               // TODO when ManualSwitch supports it, add listeners
+               end.addObserver((i, o) -> callComponentChangedListeners());
+       }
+
+       @Override
+       public boolean clicked(double x, double y)
+       {
+               logicSwitch.toggle();
+               return true;
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/components/GUINotGate.java b/LogicUI/src/era/mi/gui/model/components/GUINotGate.java
new file mode 100644 (file)
index 0000000..5f41592
--- /dev/null
@@ -0,0 +1,12 @@
+package era.mi.gui.model.components;
+
+import era.mi.gui.model.ViewModel;
+
+public class GUINotGate extends RectangularShapedGUIGate
+{
+       public GUINotGate(ViewModel model)
+       {
+               super(model, "1", true);
+               setInputCount(1);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/components/GUIOrGate.java b/LogicUI/src/era/mi/gui/model/components/GUIOrGate.java
new file mode 100644 (file)
index 0000000..df64bba
--- /dev/null
@@ -0,0 +1,12 @@
+package era.mi.gui.model.components;
+
+import era.mi.gui.model.ViewModel;
+
+public class GUIOrGate extends RectangularShapedGUIGate
+{
+       public GUIOrGate(ViewModel model)
+       {
+               super(model, "\u22651", false);// ">=1"
+               setInputCount(2);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/components/RectangularShapedGUIGate.java b/LogicUI/src/era/mi/gui/model/components/RectangularShapedGUIGate.java
new file mode 100644 (file)
index 0000000..c7d0b34
--- /dev/null
@@ -0,0 +1,74 @@
+package era.mi.gui.model.components;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import era.mi.gui.model.ViewModel;
+import era.mi.gui.model.wires.MovablePin;
+import era.mi.gui.model.wires.Pin;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Font;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class RectangularShapedGUIGate extends GUIComponent
+{
+       private static final double width = 20;
+       private static final double pinDistance = 10;
+       private static final double fontHeight = 5;
+       private static final double invertedCircleDiam = 3.5;
+
+       private final String label;
+       private final boolean isInverted;
+       private final double rectWidth;
+
+       private MovablePin outputPin;
+       private final List<Pin> inputPins;
+
+       protected RectangularShapedGUIGate(ViewModel model, String label, boolean isInverted)
+       {
+               super(model);
+               this.label = label;
+               this.isInverted = isInverted;
+               this.rectWidth = width - (isInverted ? invertedCircleDiam : 0);
+               this.outputPin = new MovablePin(this, width, 0);
+               addPin(outputPin);
+               this.inputPins = new ArrayList<>();
+               setInputCount(1);
+       }
+
+       protected void setInputCount(int inputCount)
+       {
+               int oldInputCount = inputPins.size();
+               setSize(width, inputCount * pinDistance);
+               if (oldInputCount > inputCount)
+                       while (inputPins.size() > inputCount)
+                               removePin(inputPins.get(inputCount));
+               else if (oldInputCount < inputCount)
+                       for (int i = oldInputCount; i < inputCount; i++)
+                       {
+                               Pin pin = new Pin(this, 0, pinDistance / 2 + i * pinDistance);
+                               inputPins.add(pin);
+                               addPin(pin);
+                       }
+               outputPin.setRelPos(width, inputCount * pinDistance / 2);
+       }
+
+       @Override
+       public void render(GeneralGC gc, Rectangle visibleRegion)
+       {
+               double posX = getBounds().x;
+               double posY = getBounds().y;
+
+               double height = inputPins.size() * pinDistance;
+               gc.drawRectangle(posX, posY, rectWidth, height);
+               Font oldFont = gc.getFont();
+               Font labelFont = new Font(oldFont.getName(), fontHeight, oldFont.getStyle());
+               gc.setFont(labelFont);
+               Point textExtent = gc.textExtent(label);
+               gc.drawText(label, posX + (rectWidth - textExtent.x) / 2, posY + (height - textExtent.y) / 2, true);
+               gc.setFont(oldFont);
+               if (isInverted)
+                       gc.drawOval(posX + rectWidth, posY + (height - invertedCircleDiam) / 2, invertedCircleDiam, invertedCircleDiam);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/wires/GUIWire.java b/LogicUI/src/era/mi/gui/model/wires/GUIWire.java
new file mode 100644 (file)
index 0000000..c2f03ee
--- /dev/null
@@ -0,0 +1,86 @@
+package era.mi.gui.model.wires;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import era.mi.gui.ColorHelper;
+import era.mi.gui.model.ViewModel;
+import era.mi.logic.types.BitVectorFormatter;
+import era.mi.logic.wires.Wire.ReadEnd;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+
+public class GUIWire
+{
+       private final ViewModel model;
+       private Pin pin1;
+       private Pin pin2;
+       private double[] path;
+
+       private final List<Consumer<? super GUIWire>> wireChangedListeners;
+
+       private ReadEnd end;
+
+       public GUIWire(ViewModel model, Pin pin1, Pin pin2, Point... path)
+       {
+               this.model = model;
+               this.path = new double[path.length * 2 + 4];
+               for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2)
+               {
+                       this.path[dstI + 0] = path[srcI].x;
+                       this.path[dstI + 1] = path[srcI].y;
+               }
+
+               this.pin1 = pin1;
+               this.pin2 = pin2;
+
+               wireChangedListeners = new ArrayList<>();
+
+               pin1.addPinMovedListener(p -> pin1Moved());
+               pin2.addPinMovedListener(p -> pin2Moved());
+               pin1Moved();
+               pin2Moved();
+
+               model.wireCreated(this);
+       }
+
+       private void pin1Moved()
+       {
+               Point pos = pin1.getPos();
+               this.path[0] = pos.x;
+               this.path[1] = pos.y;
+       }
+
+       private void pin2Moved()
+       {
+               Point pos = pin2.getPos();
+               this.path[this.path.length - 2] = pos.x;
+               this.path[this.path.length - 1] = pos.y;
+       }
+
+       public void destroy()
+       {
+               model.wireDestroyed(this);
+       }
+
+       public void render(GeneralGC gc)
+       {
+               ColorHelper.executeWithDifferentForeground(gc, BitVectorFormatter.formatAsColor(end), () -> gc.drawPolyline(path));
+       }
+
+       public void setLogicModelBinding(ReadEnd end)
+       {
+               this.end = end;
+               end.addObserver((i, o) -> callWireChangedListeners());
+       }
+
+       // @formatter:off
+       public void addWireChangedListener   (Consumer<? super GUIWire> listener) {wireChangedListeners.add   (listener);}
+
+       public void removeWireChangedListener(Consumer<? super GUIWire> listener) {wireChangedListeners.remove(listener);}
+
+       private void callWireChangedListeners() {wireChangedListeners.forEach(l -> l.accept(this));}
+       // @formatter:on
+
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/wires/MovablePin.java b/LogicUI/src/era/mi/gui/model/wires/MovablePin.java
new file mode 100644 (file)
index 0000000..673b257
--- /dev/null
@@ -0,0 +1,17 @@
+package era.mi.gui.model.wires;
+
+import era.mi.gui.model.components.GUIComponent;
+
+public class MovablePin extends Pin
+{
+       public MovablePin(GUIComponent component, double relX, double relY)
+       {
+               super(component, relX, relY);
+       }
+
+       @Override
+       public void setRelPos(double relX, double relY)
+       {
+               super.setRelPos(relX, relY);
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/wires/Pin.java b/LogicUI/src/era/mi/gui/model/wires/Pin.java
new file mode 100644 (file)
index 0000000..44ea374
--- /dev/null
@@ -0,0 +1,66 @@
+package era.mi.gui.model.wires;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import era.mi.gui.model.components.GUIComponent;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class Pin
+{
+       public final GUIComponent component;
+
+       protected double relX;
+       protected double relY;
+
+       private final List<Consumer<? super Pin>> pinMovedListeners;
+
+       public Pin(GUIComponent component, double relX, double relY)
+       {
+               this.component = component;
+               this.relX = relX;
+               this.relY = relY;
+
+               this.pinMovedListeners = new ArrayList<>();
+
+               component.addComponentMovedListener(c -> callPinMovedListeners());
+       }
+
+       public double getRelX()
+       {
+               return relX;
+       }
+
+       public double getRelY()
+       {
+               return relY;
+       }
+
+       public Point getRelPos()
+       {
+               return new Point(relX, relY);
+       }
+
+       public Point getPos()
+       {
+               Rectangle componentBounds = component.getBounds();
+               return new Point(relX + componentBounds.x, relY + componentBounds.y);
+       }
+
+       // @formatter:off
+       public void addPinMovedListener   (Consumer<? super Pin> listener){pinMovedListeners.add   (listener);}
+
+       public void removePinMovedListener(Consumer<? super Pin> listener){pinMovedListeners.remove(listener);}
+
+       private void callPinMovedListeners() {pinMovedListeners.forEach(l -> l.accept(this));}
+       // @formatter:on
+
+       protected void setRelPos(double relX, double relY)
+       {
+               this.relX = relX;
+               this.relY = relY;
+               callPinMovedListeners();
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/model/wires/WireCrossPoint.java b/LogicUI/src/era/mi/gui/model/wires/WireCrossPoint.java
new file mode 100644 (file)
index 0000000..ebe30c3
--- /dev/null
@@ -0,0 +1,33 @@
+package era.mi.gui.model.wires;
+
+import era.mi.gui.ColorHelper;
+import era.mi.gui.model.ViewModel;
+import era.mi.gui.model.components.GUIComponent;
+import era.mi.logic.types.BitVectorFormatter;
+import era.mi.logic.wires.Wire.ReadEnd;
+import net.haspamelodica.swt.helper.gcs.GeneralGC;
+import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
+
+public class WireCrossPoint extends GUIComponent
+{
+       private ReadEnd end;
+
+       public WireCrossPoint(ViewModel model)
+       {
+               super(model);
+               setSize(0, 0);
+               addPin(new Pin(this, 0, 0));
+       }
+
+       @Override
+       public void render(GeneralGC gc, Rectangle visibleRegion)
+       {
+               ColorHelper.executeWithDifferentBackground(gc, BitVectorFormatter.formatAsColor(end), () -> gc.fillOval(-1, -1, 2, 2));
+       }
+
+       public void setLogicModelBinding(ReadEnd end)
+       {
+               this.end = end;
+               end.addObserver((i, o) -> callComponentChangedListeners());
+       }
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/modeladapter/ViewLogicModelAdapter.java b/LogicUI/src/era/mi/gui/modeladapter/ViewLogicModelAdapter.java
new file mode 100644 (file)
index 0000000..f005a80
--- /dev/null
@@ -0,0 +1,6 @@
+package era.mi.gui.modeladapter;
+
+public class ViewLogicModelAdapter
+{
+
+}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/wires/GUIWire.java b/LogicUI/src/era/mi/gui/wires/GUIWire.java
deleted file mode 100644 (file)
index 53e122a..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package era.mi.gui.wires;
-
-import java.util.Objects;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-
-import era.mi.gui.components.BasicGUIComponent;
-import era.mi.logic.types.Bit;
-import era.mi.logic.wires.Wire;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-
-public class GUIWire
-{
-       private final Wire wire;
-       private final double[] path;
-
-       public GUIWire(Runnable redraw, BasicGUIComponent component1, int component1ConnectionIndex, Point component1Pos,
-                       BasicGUIComponent component2, int component2ConnectionIndex, Point component2Pos, Point... path)
-       {
-               this.wire = component1.getConnectedWireEnd(component1ConnectionIndex).getWire();
-               if (!Objects.equals(wire, component2.getConnectedWireEnd(component2ConnectionIndex).getWire()))
-                       throw new IllegalArgumentException("Given connection points are not connected!");
-               this.path = new double[path.length * 2 + 4];
-               Point component1ConnectionPoint = component1.getWireEndConnectionPoint(component1ConnectionIndex);
-               this.path[0] = component1Pos.x + component1ConnectionPoint.x;
-               this.path[1] = component1Pos.y + component1ConnectionPoint.y;
-               for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2)
-               {
-                       this.path[dstI + 0] = path[srcI].x;
-                       this.path[dstI + 1] = path[srcI].y;
-               }
-               Point component2ConnectionPoint = component2.getWireEndConnectionPoint(component2ConnectionIndex);
-               this.path[this.path.length - 2] = component2Pos.x + component2ConnectionPoint.x;
-               this.path[this.path.length - 1] = component2Pos.y + component2ConnectionPoint.y;
-
-               wire.createReadOnlyEnd().addObserver((initiator, oldValues) -> redraw.run());
-       }
-
-       public void render(GeneralGC gc)
-       {
-               Color oldFG = gc.getForeground();
-               if (wire.length == 1)
-                       gc.setForeground(gc.getDevice().getSystemColor(getSWTColorConstantForBit(wire.getValue())));
-               gc.drawPolyline(path);
-               gc.setForeground(oldFG);
-       }
-
-       public static int getSWTColorConstantForBit(Bit bit)
-       {
-               switch (bit)
-               {
-               case ONE:
-                       return SWT.COLOR_GREEN;
-               case ZERO:
-                       return SWT.COLOR_BLUE;
-               case Z:
-                       return SWT.COLOR_BLACK;
-               case U:
-               case X:
-                       return SWT.COLOR_RED;
-               default:
-                       throw new IllegalArgumentException("Unknown enum constant: " + bit);
-               }
-       }
-}
\ No newline at end of file
diff --git a/LogicUI/src/era/mi/gui/wires/WireConnectionPoint.java b/LogicUI/src/era/mi/gui/wires/WireConnectionPoint.java
deleted file mode 100644 (file)
index df82a73..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package era.mi.gui.wires;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.eclipse.swt.graphics.Color;
-
-import era.mi.gui.components.BasicGUIComponent;
-import era.mi.logic.wires.Wire;
-import era.mi.logic.wires.Wire.ReadEnd;
-import net.haspamelodica.swt.helper.gcs.GeneralGC;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
-import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
-
-public class WireConnectionPoint implements BasicGUIComponent
-{
-       private final Wire wire;
-       private final List<ReadEnd> wireEnds;
-       private final int wiresCrossing;
-
-       public WireConnectionPoint(Wire wire, int wiresCrossing)
-       {
-               this.wire = wire;
-               List<ReadEnd> wireEndsModifiable = new ArrayList<>();
-               for (int i = 0; i < wiresCrossing; i++)
-                       wireEndsModifiable.add(wire.createReadOnlyEnd());
-               wireEnds = Collections.unmodifiableList(wireEndsModifiable);
-               this.wiresCrossing = wiresCrossing;
-       }
-
-       @Override
-       public void render(GeneralGC gc)
-       {
-               Color oldBG = gc.getBackground();
-               if (wire.length == 1)
-                       gc.setBackground(gc.getDevice().getSystemColor(GUIWire.getSWTColorConstantForBit(wire.getValue())));
-               gc.fillOval(-1, -1, 2, 2);
-               gc.setBackground(oldBG);
-       }
-
-       @Override
-       public Rectangle getBounds()
-       {
-               return new Rectangle(0, 0, 0, 0);
-       }
-
-       @Override
-       public int getConnectedWireEndsCount()
-       {
-               return wiresCrossing;
-       }
-
-       @Override
-       public ReadEnd getConnectedWireEnd(int connectionIndex)
-       {
-               return wireEnds.get(connectionIndex);
-       }
-
-       @Override
-       public Point getWireEndConnectionPoint(int connectionIndex)
-       {
-               return new Point(0, 0);
-       }
-}
\ No newline at end of file
index ee1d775..fa18e2d 100644 (file)
@@ -8,8 +8,7 @@ import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;
 
 import era.mi.gui.LogicUICanvas;
-import era.mi.gui.examples.RSLatchGUIExample;
-import era.mi.logic.timeline.Timeline;
+import era.mi.gui.model.ViewModel;
 import net.haspamelodica.swt.helper.zoomablecanvas.helper.ZoomableCanvasUserInput;
 
 public class LogicUIPart
@@ -20,41 +19,41 @@ public class LogicUIPart
        @PostConstruct
        public void create(Composite parent)
        {
-               Timeline timeline = new Timeline(11);
-               LogicUICanvas ui = new LogicUICanvas(parent, SWT.NONE);
-               RSLatchGUIExample.addComponentsAndWires(ui, timeline);
+               ViewModel model = new ViewModel();
+               LogicUICanvas ui = new LogicUICanvas(parent, SWT.NONE, model);
+//             RSLatchGUIExample.addComponentsAndWires(ui, timeline);
                ui.addTransformListener((x, y, z) -> part.setDirty(z < 1));
                ZoomableCanvasUserInput userInput = new ZoomableCanvasUserInput(ui);
                userInput.buttonDrag = 3;
                userInput.buttonZoom = 2;
                userInput.enableUserInput();
-               Thread simulationThread = new Thread(() ->
-               {
-                       // TODO find a better condition
-                       while (!ui.isDisposed())
-                       {
-                               // always execute to keep timeline from "hanging behind" for too long
-                               timeline.executeUntil(timeline.laterThan(System.currentTimeMillis()), System.currentTimeMillis() + 10);
-                               long sleepTime;
-                               if (timeline.hasNext())
-                                       sleepTime = timeline.nextEventTime() - System.currentTimeMillis();
-                               else
-                                       sleepTime = 10;
-                               try
-                               {
-                                       if (sleepTime > 0)
-                                               Thread.sleep(sleepTime);
-                               }
-                               catch (InterruptedException e)
-                               {
-                               } // it is normal execution flow to be interrupted
-                       }
-               });
-               simulationThread.start();
-               timeline.addEventAddedListener(event ->
-               {
-                       if (event.getTiming() <= System.currentTimeMillis())
-                               simulationThread.interrupt();
-               });
+//             Thread simulationThread = new Thread(() ->
+//             {
+//                     // TODO find a better condition
+//                     while (!ui.isDisposed())
+//                     {
+//                             // always execute to keep timeline from "hanging behind" for too long
+//                             timeline.executeUpTo(System.currentTimeMillis(), System.currentTimeMillis() + 10);
+//                             long sleepTime;
+//                             if (timeline.hasNext())
+//                                     sleepTime = timeline.nextEventTime() - System.currentTimeMillis();
+//                             else
+//                                     sleepTime = 10;
+//                             try
+//                             {
+//                                     if (sleepTime > 0)
+//                                             Thread.sleep(sleepTime);
+//                             }
+//                             catch (InterruptedException e)
+//                             {
+//                             } // it is normal execution flow to be interrupted
+//                     }
+//             });
+//             simulationThread.start();
+//             timeline.addEventAddedListener(event ->
+//             {
+//                     if (event.getTiming() <= System.currentTimeMillis())
+//                             simulationThread.interrupt();
+//             });
        }
 }
\ No newline at end of file
diff --git a/era.mi/src/era/mi/logic/types/BitVectorFormatter.java b/era.mi/src/era/mi/logic/types/BitVectorFormatter.java
new file mode 100644 (file)
index 0000000..6dab1d7
--- /dev/null
@@ -0,0 +1,53 @@
+package era.mi.logic.types;
+
+import era.mi.logic.types.ColorDefinition.BuiltInColor;
+import era.mi.logic.wires.Wire.ReadEnd;
+
+public class BitVectorFormatter
+{
+       public static String formatValueAsString(ReadEnd end)
+       {
+               return formatAsString(end == null ? null : end.getValues());
+       }
+
+       public static String formatAsString(BitVector bitVector)
+       {
+               if (bitVector == null)
+                       return "null";
+               else
+                       return bitVector.toString();
+       }
+
+       public static ColorDefinition formatAsColor(ReadEnd end)
+       {
+               return formatAsColor(end == null ? null : end.getValues());
+       }
+
+       public static ColorDefinition formatAsColor(BitVector bitVector)
+       {
+               // TODO maybe find a color assignment for multiple-bit bit vectors?
+               if (bitVector == null || bitVector.length() != 1)
+                       return new ColorDefinition(BuiltInColor.COLOR_BLACK);
+               else
+                       switch (bitVector.getBit(0))
+                       {
+                       case ONE:
+                               return new ColorDefinition(BuiltInColor.COLOR_GREEN);
+                       case U:
+                               return new ColorDefinition(BuiltInColor.COLOR_CYAN);
+                       case X:
+                               return new ColorDefinition(BuiltInColor.COLOR_RED);
+                       case Z:
+                               return new ColorDefinition(BuiltInColor.COLOR_YELLOW);
+                       case ZERO:
+                               return new ColorDefinition(BuiltInColor.COLOR_GRAY);
+                       default:
+                               throw new IllegalArgumentException("Unknown enum constant: " + bitVector.getBit(0));
+                       }
+       }
+
+       private BitVectorFormatter()
+       {
+               throw new UnsupportedOperationException("No BitVectorFormatter instances");
+       }
+}
\ No newline at end of file
diff --git a/era.mi/src/era/mi/logic/types/ColorDefinition.java b/era.mi/src/era/mi/logic/types/ColorDefinition.java
new file mode 100644 (file)
index 0000000..b9e851f
--- /dev/null
@@ -0,0 +1,54 @@
+package era.mi.logic.types;
+
+/**
+ * A way to define a color with the possibility to use colors built into the system (called "system colors" in SWT).
+ * <p>
+ * A {@link ColorDefinition} is defined either by a {@link BuiltInColor} constant, in which case <code>r==g==b==-1</code>, or by red / green
+ * / blue components, in which case <code>builtInColor==null</code>
+ */
+public class ColorDefinition
+{
+       /**
+        * The built-in color constant defining this color.
+        */
+       public final ColorDefinition.BuiltInColor builtInColor;
+       /**
+        * The red color component defining this color.
+        */
+       public final int r;
+       /**
+        * The green color component defining this color.
+        */
+       public final int g;
+       /**
+        * The blue color component defining this color.
+        */
+       public final int b;
+
+       public ColorDefinition(ColorDefinition.BuiltInColor col)
+       {
+               if (col == null)
+                       throw new IllegalArgumentException("Illegal built-in color: " + col);
+               this.builtInColor = col;
+               this.r = -1;
+               this.g = -1;
+               this.b = -1;
+       }
+
+       public ColorDefinition(int r, int g, int b)
+       {
+               if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
+                       throw new IllegalArgumentException("Illegal color components: r=" + r + "; g=" + g + "; b=" + b);
+               this.builtInColor = null;
+               this.r = r;
+               this.g = g;
+               this.b = b;
+       }
+
+       public static enum BuiltInColor
+       {
+               COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_DARK_RED, COLOR_GREEN, COLOR_DARK_GREEN, COLOR_YELLOW, COLOR_DARK_YELLOW, COLOR_BLUE,
+               COLOR_DARK_BLUE, COLOR_MAGENTA, COLOR_DARK_MAGENTA, COLOR_CYAN, COLOR_DARK_CYAN, COLOR_GRAY, COLOR_DARK_GRAY;
+       }
+
+}
\ No newline at end of file