sclass JG22Network is Swingable {
  G22Utils g22utils;
  new MainPanel mainPanel;
  new G22Network network;
  Map<G22NetworkElement, JG22NetworkElement> elementToComponent = syncMap();
  ReliableSingleThread rstUpdate = rstWithPreDelay(0.25, l0 updateImpl);
  
  settable int maxMagneticDistance = 100;
  
  // example instance of the network so we can test it immediately
  settable volatile G22NetworkInstance networkInstance;
  
  // margin on bottom/right
  transient int margin = 50;
  
  // The MainPanel holds the network elements
  class MainPanel extends JPanel {
    *() { super(null); } // no layout manager
    
    // to allow overlapping components
    public bool isOptimizedDrawingEnabled() { false; }

    public Dimension getPreferredSize() {
      Rect r = combinedChildBounds(this);
      if (r == null) r = rect(0, 0, 0, 0);
      //_print("JG22Network.getPreferredSize = " + r);
      ret new Dimension(r.x2()+margin, r.y2()+margin);
    }
    
    void revalidateMe() {
      //_print("JG22Network.revalidateMe");
      //awtLater(0.5, -> {
        revalidateIncludingFullCenterContainer(this);
        repaint();
      //});
    }
    
    public void paintComponent(Graphics g) {
      cast g to Graphics2D;
      
      fillRect(g, 0, 0, getWidth(), getHeight(), Color.white);
      for (cable : network.allCables()) {
        Rect r1 = cable.from.bounds();
        Rect r2 = cable.to.bounds();
        drawLine(g, center(r1), center(r2), Color.black);
      }
    }
  }

  *(G22Utils *g22utils, G22Network *network) {
    network.onChangeAndNow(l0 networkChanged);
    for (element : network.elements)
      visualizeElement(element);
  }
  
  void networkChanged { rstUpdate.trigger(); }
  
  void updateImpl {
    if (networkInstance == null)
      makeInstance();
    visualizePorts();
  }
  
  void visualizePorts { awt {
    // Remove old visualizations
    for (ports : directChildrenOfType(mainPanel, JG22NetworkPort))
      removeFromParent(ports);
      
    // Create new ones
    for (e : network.elements) {
      var c = elementToComponent.get(e);
      if (c == null)
        print("No component for " + e);
      else
        for (port : e.ports) {
          //print("Visualizing port " + port);
          var p = new JG22NetworkPort(g22utils, c, port);
          p.init();
          mainPanel.add(p);
        }
    }
    
    mainPanel.repaint();
  }}
  
  void makeInstance {
    var networkInstance = new G22NetworkInstance(network);
    networkInstance.onDirtyStatesChanged(l0 dirtyStatesChanged);
    networkInstance.init(g22utils);
    networkInstance(networkInstance);
    dirtyStatesChanged();
    print("Network instance made");
  }
  
  void dirtyStatesChanged {
    for (component : values(elementToComponent))
      component.updateDirtyFlag();
  }
  
  void visualizeElement(G22NetworkElement element) swing {
    var c = new JG22NetworkElement(g22utils, this, element);
    elementToComponent.put(element, c);
    mainPanel.add(c.visualize());
    mainPanel.revalidateMe();
  }
  
  void newInstance {
    networkInstance = null;
    rstUpdate.trigger();
  }
  
  void newElement {
    new G22NetworkElement element;
    element.network(network);
    network.elements.add(element);
    network.change();
    visualizeElement(element);
  }
  
  void deleteElement(JG22NetworkElement element) {
    network.elements.remove(element.element);
    network.change();
    mainPanel.remove(element.visualize());
    mainPanel.repaint();
  }
  
  void doMagneticConnections {
    network.doMagneticConnections();
    mainPanel.repaint();
  }
  
  void configureNodes {
    for (element : network.elements)
      element.configureBlueprint(g22utils);
  }
  
  cachedVisualize {
    var magneticDistanceSlider = jLiveValueSlider_int_bothWays(0, maxMagneticDistance, network.varMagneticDistance());
    
    ret withRightAlignedButtons(/*jscroll_center_borderless*/jscroll(mainPanel),
      withLabel("Magnetic auto-connect distance:", jMinWidth(maxMagneticDistance, magneticDistanceSlider)),
      "Do magnetic connections", rThread doMagneticConnections,
      "Configure nodes", rThread configureNodes,
      "New instance", rThread newInstance,
      "New element", rThread newElement);
  }
}