import { ScrollView, HorizontalBox, Button } from "std-widgets.slint"; struct Port { x: length, y: length, name: string, } struct Node { x: length, y: length, z: float, ports-width: [length], text: string, in-ports: [Port], out-ports: [Port], } component Node inherits Rectangle { in-out property <[Node]> nodes; in property idx; private property node: nodes[idx]; callback port_clicked(int, bool); x: node.x; y: node.y; width: self.preferred-width; height: self.preferred-height; drop-shadow-blur: 5px; drop-shadow-color: black; border-radius: 8px; VerticalLayout { spacing: -heading.border-width; heading := Rectangle { border-radius: root.border-radius; border-bottom-right-radius: self.border-bottom-left-radius; border-bottom-left-radius: { if node.in-ports.length == 0 && node.out-ports.length == 0 { root.border-radius } else { 0px } }; background: #383838; border-width: 1px; border-color: white.with-alpha(0.15); HorizontalLayout { padding: 5px; spacing: 10px; Text { color: white; text: node.text; } } } Rectangle { background: #303030; border-width: heading.border-width; border-color: heading.border-color; border-bottom-right-radius: root.border-radius; border-bottom-left-radius: self.border-bottom-right-radius; HorizontalLayout { for ports[idx] in [node.in-ports, node.out-ports]: ports-layout := VerticalLayout { private property is-input: idx == 0; alignment: start; padding-top: 5px; padding-bottom: 5px; spacing: 5px; for idx in ports.length: port-layout := HorizontalLayout { alignment: is-input ? start : end; x: 15px / 2 * (is-input ? -1 : 1); Rectangle { area := TouchArea { clicked => { root.port_clicked(idx, is-input); } } border-radius: 3px; background: #f8e45c; min-width: 15px; height: 20px; drop-shadow-blur: 5px; drop-shadow-color: black; HorizontalLayout { padding: 2px; Text { color: black; horizontal-alignment: center; text: ports[idx].name; } } init => { ports[idx].y = // heading.preferred-height// + ports-layout.padding-top// + port-layout.preferred-height / 2// + (ports-layout.spacing + port-layout.preferred-height) * idx; } } } } } } } for idx in node.out-ports.length: Rectangle { init => { node.out-ports[idx].x = root.preferred-width; } } TouchArea { moved => { if self.pressed { nodes[idx].x += self.mouse-x - self.pressed-x; nodes[idx].y += self.mouse-y - self.pressed-y; } } mouse-cursor: move; } } struct Link { x1: length, x2: length, y1: length, y2: length, color: brush, } component Link inherits Path { in property link; viewbox-width: self.width / 1px; viewbox-height: self.height / 1px; stroke: link.color; stroke-width: 5px; MoveTo { x: link.x1 / 1px; y: link.y1 / 1px; } // LineTo { // x: link.x2 / 1px; // y: link.y2 / 1px; // } private property h: link.y2 / 1px - link.y1 / 1px; private property w: link.x2 / 1px - link.x1 / 1px; private property <{ x: int, y: int}> c1: { x: max(abs(h / 2), abs(w / 2)), y: max(h / 4, -w / 4), }; private property <{ x: int, y: int}> c2: { x: max(abs(h / 2), abs(w / 2)), y: min(h / 4, w / 4 + abs(h)), }; CubicTo { x: link.x2 / 1px; y: link.y2 / 1px; control-1-x: link.x1 / 1px + c1.x; control-1-y: link.y1 / 1px + c1.y; control-2-x: link.x2 / 1px - c2.x; control-2-y: link.y2 / 1px - c2.y; } } struct Connection { node1: int, port1: int, node2: int, port2: int, color: brush} export component MainWindow inherits Window { background: #242424; min-width: 800px; min-height: 400px; in-out property window_title <=> self.title; in-out property <[Node]> nodes; in-out property <[Connection]> connections; property <[Link]> links; property PendingConnection: { node1: -1, port1: -1, node2: -1, port2: -1, color: #ffa500 }; callback add_connection_helper(Connection); preferred-height: 500px; preferred-width: 500px; public pure function create-link-object(start: { node: int, port: int}, end: { node: int, port: int}, color: brush) -> Link { { x1: nodes[start.node].x + nodes[start.node].out-ports[start.port].x, y1: nodes[start.node].y + nodes[start.node].out-ports[start.port].y, x2: nodes[end.node].x + nodes[end.node].in-ports[end.port].x, y2: nodes[end.node].y + nodes[end.node].in-ports[end.port].y, color: color, } } VerticalLayout { ScrollView { for c in connections: Link { link: root.create-link-object({ node: c.node1, port: c.port1 }, { node: c.node2, port: c.port2 }, c.color); } for node[idx] in nodes: Node { nodes <=> nodes; idx: idx; port_clicked(port, is-input) => { debug("Node", idx, "Port", port, "Input", is-input); if (root.PendingConnection.node1 == -1) { if (!is-input) { root.PendingConnection.node1 = idx; root.PendingConnection.port1 = port; root.PendingConnection.color = #ffa500; } else { root.PendingConnection.node1 = -1; } } else if (root.PendingConnection.node1 != idx) { if (is-input) { root.PendingConnection.node2 = idx; root.PendingConnection.port2 = port; // Use rust code to add the connection because we can't do it in pure slint // Ugh root.add_connection_helper(root.PendingConnection); } root.PendingConnection.node1 = -1; } } } } Rectangle { height: 10%; } } }