diff --git a/0.icon.json b/0.icon.json
new file mode 100644
index 0000000..5575d4c
--- /dev/null
+++ b/0.icon.json
@@ -0,0 +1,25 @@
+{
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "0"
+ }
+ },
+ "port_positions": {
+ "497b1f74-1186-471f-976a-36b07a451caf": [
+ -8,
+ -8
+ ]
+ }
+}
diff --git a/1.icon.json b/1.icon.json
new file mode 100644
index 0000000..f6c0182
--- /dev/null
+++ b/1.icon.json
@@ -0,0 +1,25 @@
+{
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "1"
+ }
+ },
+ "port_positions": {
+ "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3": [
+ -8,
+ -8
+ ]
+ }
+}
diff --git a/C.icon.json b/C.icon.json
new file mode 100644
index 0000000..8122bbb
--- /dev/null
+++ b/C.icon.json
@@ -0,0 +1,29 @@
+{
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64.0,
+ "height": 64.0,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "C"
+ }
+ },
+ "port_positions": {
+ "c87881f1-23b4-4a69-b586-8e3c7bf6e21e": [
+ -8,
+ -8
+ ],
+ "c62de8eb-e13b-4849-bea5-c8cc5332269e": [
+ 16,
+ -32
+ ]
+ }
+}
diff --git a/I.icon.json b/I.icon.json
new file mode 100644
index 0000000..ed73013
--- /dev/null
+++ b/I.icon.json
@@ -0,0 +1,29 @@
+{
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "I"
+ }
+ },
+ "port_positions": {
+ "0b9036b1-e4e4-437e-8c35-f5bb391b6cbd": [
+ -8,
+ -8
+ ],
+ "fb25f35d-4c0c-4cfa-92d4-1a18a71e2c11": [
+ 16,
+ -32
+ ]
+ }
+}
diff --git a/R.icon.json b/R.icon.json
new file mode 100644
index 0000000..a1a464b
--- /dev/null
+++ b/R.icon.json
@@ -0,0 +1,29 @@
+{
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "R"
+ }
+ },
+ "port_positions": {
+ "0ac7d4ea-77f7-4c0d-8e37-406ef66e4740": [
+ -8,
+ -8
+ ],
+ "d1f5aab2-7bff-4b1e-8697-98fb6c3d8f02": [
+ 16,
+ -32
+ ]
+ }
+}
diff --git a/src/bedit_gui/models.py b/src/bedit_gui/models.py
index ba517a7..e5daddc 100644
--- a/src/bedit_gui/models.py
+++ b/src/bedit_gui/models.py
@@ -22,6 +22,8 @@ class Shape:
def from_data(cls, data: Mapping[str, Any]) -> Shape:
if cls is Shape and data.get("type") == "rectangle":
return Rectangle.from_data(data)
+ if cls is Shape and data.get("type") == "text":
+ return Text.from_data(data)
pos = data.get("pos", [0, 0])
return cls(layer=int(data.get("layer", 0)), type=data.get("type"), pos=(int(pos[0]), int(pos[1])))
@@ -59,14 +61,22 @@ class Rectangle(Shape):
@dataclass
class Text(Shape):
type: str = field(init=False, default="text")
- width: float = 100.0
- height: float = 100.0
+ width: float = 32.0
+ height: float = 16.0
color: str = "#000000ff"
bold: bool = False
italic: bool = False
size: float = 16.0
text: str = ""
+ @classmethod
+ def from_data(cls, data: Mapping[str, Any]) -> Text:
+ pos = data.get("pos", [0, 0])
+ return cls(layer=int(data.get("layer", 0)), pos=(int(pos[0]), int(pos[1])), width=float(data.get("width", 32.0)), height=float(data.get("height", 16.0)), color=str(data.get("color", "#000000ff")), bold=bool(data.get("bold", False)), italic=bool(data.get("italic", False)), size=float(data.get("size", 16.0)), text=str(data.get("text", "")))
+
+ def to_data(self) -> dict[str, Any]:
+ return {**super().to_data(), "width": self.width, "height": self.height, "color": self.color, "bold": self.bold, "italic": self.italic, "size": self.size, "text": self.text}
+
@dataclass
class Icon:
shapes: dict[ShapeID, Shape] = field(default_factory=dict)
diff --git a/src/bedit_gui/services/icon_files.py b/src/bedit_gui/services/icon_files.py
new file mode 100644
index 0000000..cc4e23d
--- /dev/null
+++ b/src/bedit_gui/services/icon_files.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+from bedit_gui.models import Icon
+
+
+def load(path: str | Path) -> Icon:
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
+ if not isinstance(data, dict):
+ raise TypeError("icon file must contain a JSON object")
+ return Icon.from_data(data)
+
+
+def save(icon: Icon, path: str | Path) -> None:
+ Path(path).write_text(json.dumps(icon.to_data(), indent=2) + "\n", encoding="utf-8")
diff --git a/src/bedit_gui/ui/forms/icon_editor_window.ui b/src/bedit_gui/ui/forms/icon_editor_window.ui
index a4b1a30..6b0804f 100644
--- a/src/bedit_gui/ui/forms/icon_editor_window.ui
+++ b/src/bedit_gui/ui/forms/icon_editor_window.ui
@@ -39,10 +39,18 @@
+
+
+
@@ -176,6 +184,42 @@
QAction::MenuRole::NoRole
+
+
+
+ :/icons/icons/document-save-as.png:/icons/icons/document-save-as.png
+
+
+ Save to File
+
+
+ Save icon to a file
+
+
+ Ctrl+Shift+S
+
+
+ QAction::MenuRole::NoRole
+
+
+
+
+
+ :/icons/icons/document-open.png:/icons/icons/document-open.png
+
+
+ Open from File
+
+
+ Open icon from File
+
+
+ Ctrl+Shift+O
+
+
+ QAction::MenuRole::NoRole
+
+
diff --git a/src/bedit_gui/views/icon_editor_window.py b/src/bedit_gui/views/icon_editor_window.py
index 044dc65..c298bbf 100644
--- a/src/bedit_gui/views/icon_editor_window.py
+++ b/src/bedit_gui/views/icon_editor_window.py
@@ -4,14 +4,18 @@ from copy import deepcopy
from PySide6.QtCore import QEvent, QObject, Qt, Signal
from PySide6.QtGui import QKeySequence, QPainter, QShortcut, QUndoCommand, QUndoStack, QWheelEvent, QShowEvent
-from PySide6.QtWidgets import QDialog, QGraphicsView, QMainWindow, QWidget
+from PySide6.QtWidgets import QDialog, QFileDialog, QGraphicsView, QMainWindow, QMessageBox, QWidget
from bedit_core.models import Port, PortID, SignalDirection
from bedit_gui.models import Icon, Shape, ShapeID
+from bedit_gui.services import icon_files
+from bedit_gui.services.application_logging import get_logger
from bedit_gui.ui.generated.ui_icon_editor_window import Ui_iconEditor
-from bedit_gui.views.icon_graphics_scene import IconGraphicsScene, RectangleCreationTool
+from bedit_gui.views.icon_graphics_scene import IconGraphicsScene, RectangleCreationTool, TextCreationTool
from bedit_gui.views.shape_options_dialog import ShapeOptionsDialog
+logger = get_logger(__name__)
+
class ChangeIconDraftCommand(QUndoCommand):
def __init__(self, editor: IconEditorWindow, icon: Icon, text: str) -> None:
@@ -102,10 +106,11 @@ class IconEditorWindow(QMainWindow):
self.setWindowTitle("Icon Editor")
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
self._icon = deepcopy(icon)
+ self._ports = deepcopy(ports)
self.scene = IconGraphicsScene(self)
- self.scene.set_ports(ports)
- self._ensure_port_positions(ports)
+ self.scene.set_ports(self._ports)
+ self._ensure_port_positions(self._ports)
self.scene.set_icon(self._icon)
self.scene.port_moved.connect(self._port_moved)
self.scene.shape_created.connect(self._shape_created)
@@ -118,6 +123,8 @@ class IconEditorWindow(QMainWindow):
self.ui.graphicsView.viewport().installEventFilter(self)
self.ui.actionAdd_Rectangle.setCheckable(True)
self.ui.actionAdd_Rectangle.triggered.connect(self._start_rectangle_tool)
+ self.ui.actionAdd_Text.setCheckable(True)
+ self.ui.actionAdd_Text.triggered.connect(self._start_text_tool)
self.cancel_tool_shortcut = QShortcut(QKeySequence(Qt.Key.Key_Escape), self)
self.cancel_tool_shortcut.activated.connect(self.scene.cancel_creation_tool)
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key.Key_Delete), self)
@@ -135,6 +142,8 @@ class IconEditorWindow(QMainWindow):
self.ui.actionUndo.triggered.connect(self.undo_stack.undo)
self.ui.actionRedo.triggered.connect(self.undo_stack.redo)
self.ui.actionSave.triggered.connect(self.save)
+ self.ui.actionSave_to_File.triggered.connect(self.save_to_file)
+ self.ui.actionOpen_from_File.triggered.connect(self.open_from_file)
self.ui.actionCancel.triggered.connect(self.close)
self.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
self.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
@@ -157,6 +166,32 @@ class IconEditorWindow(QMainWindow):
self.saved.emit(self.icon())
self.close()
+ def save_to_file(self) -> None:
+ file_name, _ = QFileDialog.getSaveFileName(self, "Save Icon", "", "JSON files (*.json)")
+ if not file_name:
+ return
+ path = file_name if file_name.lower().endswith(".json") else f"{file_name}.json"
+ try:
+ icon_files.save(self._icon, path)
+ except (OSError, TypeError, ValueError) as exc:
+ logger.exception("Could not save icon to %s", path)
+ QMessageBox.critical(self, "Could not save icon", str(exc))
+ return
+ logger.info("Saved icon to: %s", path)
+
+ def open_from_file(self) -> None:
+ file_name, _ = QFileDialog.getOpenFileName(self, "Open Icon", "", "JSON files (*.json)")
+ if not file_name:
+ return
+ try:
+ icon = icon_files.load(file_name)
+ except (OSError, TypeError, ValueError) as exc:
+ logger.exception("Could not open icon from %s", file_name)
+ QMessageBox.critical(self, "Could not open icon", str(exc))
+ return
+ self.apply_change(icon, "Load icon from file")
+ logger.info("Loaded icon from: %s", file_name)
+
def zoom_in(self) -> None:
self._zoom(self.zoom_step)
@@ -187,6 +222,7 @@ class IconEditorWindow(QMainWindow):
def _set_icon(self, icon: Icon) -> None:
self._icon = deepcopy(icon)
+ self._ensure_port_positions(self._ports)
self.scene.set_icon(self._icon)
self.icon_changed.emit(self.icon())
@@ -268,10 +304,18 @@ class IconEditorWindow(QMainWindow):
def _start_rectangle_tool(self) -> None:
layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1
+ self.ui.actionAdd_Text.setChecked(False)
self.scene.set_creation_tool(RectangleCreationTool(self.scene, layer))
+ def _start_text_tool(self) -> None:
+ layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1
+ self.ui.actionAdd_Rectangle.setChecked(False)
+ self.scene.set_creation_tool(TextCreationTool(self.scene, layer))
+
def _tool_active_changed(self, active: bool) -> None:
- self.ui.actionAdd_Rectangle.setChecked(active)
+ if not active:
+ self.ui.actionAdd_Rectangle.setChecked(False)
+ self.ui.actionAdd_Text.setChecked(False)
cursor = Qt.CursorShape.CrossCursor if active else Qt.CursorShape.ArrowCursor
self.ui.graphicsView.viewport().setCursor(cursor)
diff --git a/src/bedit_gui/views/icon_graphics_scene.py b/src/bedit_gui/views/icon_graphics_scene.py
index 54cf96a..1254461 100644
--- a/src/bedit_gui/views/icon_graphics_scene.py
+++ b/src/bedit_gui/views/icon_graphics_scene.py
@@ -5,11 +5,11 @@ from copy import deepcopy
from typing import Protocol
from PySide6.QtCore import QObject, QPointF, QRectF, Qt, Signal
-from PySide6.QtGui import QBrush, QColor, QPainter, QPainterPath, QPen
+from PySide6.QtGui import QBrush, QColor, QFont, QPainter, QPainterPath, QPen
from PySide6.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget
from bedit_core.models import Port, PortID, SignalDirection
-from bedit_gui.models import Icon, LineType, Rectangle, Shape, ShapeID
+from bedit_gui.models import Icon, LineType, Rectangle, Shape, ShapeID, Text
class ShapeCreationTool(Protocol):
@@ -36,7 +36,7 @@ class RectangleCreationTool:
position = self._bounded(position)
self.preview.setRect(QRectF(self.start, position).normalized())
- def finish(self, position: QPointF) -> Rectangle | None:
+ def finish(self, position: QPointF) -> Shape | None:
if self.start is None:
return None
position = self._bounded(position)
@@ -44,6 +44,9 @@ class RectangleCreationTool:
self.cancel()
if rect.width() < 1 or rect.height() < 1:
return None
+ return self.create_shape(rect)
+
+ def create_shape(self, rect: QRectF) -> Shape:
return Rectangle(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height())
def cancel(self) -> None:
@@ -59,6 +62,11 @@ class RectangleCreationTool:
return QPointF(x, y)
+class TextCreationTool(RectangleCreationTool):
+ def create_shape(self, rect: QRectF) -> Shape:
+ return Text(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height(), text="Text")
+
+
class ShapeGraphicsItem(QGraphicsPathItem):
handle_size = 6.0
@@ -173,6 +181,48 @@ class RectangleGraphicsItem(ShapeGraphicsItem):
self.setPath(path)
+class TextGraphicsItem(ShapeGraphicsItem):
+ def __init__(self, shape_id: ShapeID, shape: Text, scene: IconGraphicsScene) -> None:
+ super().__init__(shape_id, shape, scene)
+ self.text = deepcopy(shape)
+ self.setPos(shape.pos[0], shape.pos[1])
+ self._set_size(shape.width, shape.height)
+ self.setPen(QPen(Qt.PenStyle.NoPen))
+ self.setBrush(QBrush(QColor(0, 0, 0, 0)))
+ self.setZValue(shape.layer)
+
+ def current_shape(self) -> Text:
+ shape = deepcopy(self.text)
+ shape.pos = (round(self.pos().x()), round(self.pos().y()))
+ rect = self.path().boundingRect()
+ shape.width = rect.width()
+ shape.height = rect.height()
+ return shape
+
+ def resize_to(self, position: QPointF) -> None:
+ bounds = self.icon_scene.sceneRect()
+ width = max(1.0, min(bounds.right(), round(position.x())) - self.pos().x())
+ height = max(1.0, min(bounds.bottom(), round(position.y())) - self.pos().y())
+ self._set_size(width, height)
+
+ def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget | None = None) -> None:
+ super().paint(painter, option, widget)
+ font = QFont()
+ font.setPixelSize(max(1, round(self.text.size)))
+ font.setBold(self.text.bold)
+ font.setItalic(self.text.italic)
+ painter.setFont(font)
+ painter.setPen(self.icon_scene._color(self.text.color))
+ painter.setClipRect(self.path().boundingRect())
+ painter.drawText(self.path().boundingRect(), Qt.AlignmentFlag.AlignCenter | Qt.TextFlag.TextWordWrap, self.text.text)
+
+ def _set_size(self, width: float, height: float) -> None:
+ self.prepareGeometryChange()
+ path = QPainterPath()
+ path.addRect(QRectF(0, 0, width, height))
+ self.setPath(path)
+
+
class PortGraphicsItem(QGraphicsRectItem):
size = 16.0
@@ -304,9 +354,10 @@ class IconGraphicsScene(QGraphicsScene):
super().mouseReleaseEvent(event)
def _add_shape_item(self, shape_id: ShapeID, shape: Shape) -> None:
- if not isinstance(shape, Rectangle):
- return
- self.addItem(RectangleGraphicsItem(shape_id, shape, self))
+ if isinstance(shape, Rectangle):
+ self.addItem(RectangleGraphicsItem(shape_id, shape, self))
+ elif isinstance(shape, Text):
+ self.addItem(TextGraphicsItem(shape_id, shape, self))
@staticmethod
def _pen(shape: Rectangle) -> QPen:
diff --git a/src/bedit_gui/views/shape_options_dialog.py b/src/bedit_gui/views/shape_options_dialog.py
index ee547a7..4b89a1e 100644
--- a/src/bedit_gui/views/shape_options_dialog.py
+++ b/src/bedit_gui/views/shape_options_dialog.py
@@ -3,9 +3,9 @@ from __future__ import annotations
from copy import deepcopy
from PySide6.QtCore import QRectF
-from PySide6.QtWidgets import QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox, QFormLayout, QLabel, QSpinBox, QVBoxLayout, QWidget
+from PySide6.QtWidgets import QCheckBox, QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox, QFormLayout, QLabel, QLineEdit, QSpinBox, QVBoxLayout, QWidget
-from bedit_gui.models import LineType, Rectangle, Shape
+from bedit_gui.models import LineType, Rectangle, Shape, Text
from bedit_gui.views.color_button import ColorButton
@@ -34,6 +34,8 @@ class ShapeOptionsDialog(QDialog):
if isinstance(shape, Rectangle):
self._add_rectangle_fields(shape)
+ elif isinstance(shape, Text):
+ self._add_text_fields(shape)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(self.accept)
@@ -43,15 +45,18 @@ class ShapeOptionsDialog(QDialog):
layout.addWidget(buttons)
def shape(self) -> Shape:
- if not isinstance(self._shape, Rectangle):
- shape = deepcopy(self._shape)
- shape.layer = self.layer.value()
- shape.pos = (self.x.value(), self.y.value())
- return shape
-
- width = min(self.width.value(), self._scene_rect.right() - self.x.value())
- height = min(self.height.value(), self._scene_rect.bottom() - self.y.value())
- return Rectangle(layer=self.layer.value(), pos=(self.x.value(), self.y.value()), width=width, height=height, line_type=self.line_type.currentData(), line_thickness=self.line_thickness.value(), corner_radius=self.corner_radius.value(), line_color=self.line_color.color(), fill_color=self.fill_color.color())
+ if isinstance(self._shape, Rectangle):
+ width = min(self.width.value(), self._scene_rect.right() - self.x.value())
+ height = min(self.height.value(), self._scene_rect.bottom() - self.y.value())
+ return Rectangle(layer=self.layer.value(), pos=(self.x.value(), self.y.value()), width=width, height=height, line_type=self.line_type.currentData(), line_thickness=self.line_thickness.value(), corner_radius=self.corner_radius.value(), line_color=self.line_color.color(), fill_color=self.fill_color.color())
+ if isinstance(self._shape, Text):
+ width = min(self.width.value(), self._scene_rect.right() - self.x.value())
+ height = min(self.height.value(), self._scene_rect.bottom() - self.y.value())
+ return Text(layer=self.layer.value(), pos=(self.x.value(), self.y.value()), width=width, height=height, color=self.color.color(), bold=self.bold.isChecked(), italic=self.italic.isChecked(), size=self.size.value(), text=self.text.text())
+ shape = deepcopy(self._shape)
+ shape.layer = self.layer.value()
+ shape.pos = (self.x.value(), self.y.value())
+ return shape
def _add_rectangle_fields(self, shape: Rectangle) -> None:
self.width = QSpinBox()
@@ -79,3 +84,27 @@ class ShapeOptionsDialog(QDialog):
self.form.addRow("Corner radius", self.corner_radius)
self.form.addRow("Line color", self.line_color)
self.form.addRow("Fill color", self.fill_color)
+
+ def _add_text_fields(self, shape: Text) -> None:
+ self.width = QSpinBox()
+ self.width.setRange(1, round(self._scene_rect.width()))
+ self.width.setValue(round(shape.width))
+ self.height = QSpinBox()
+ self.height.setRange(1, round(self._scene_rect.height()))
+ self.height.setValue(round(shape.height))
+ self.color = ColorButton(shape.color)
+ self.bold = QCheckBox()
+ self.bold.setChecked(shape.bold)
+ self.italic = QCheckBox()
+ self.italic.setChecked(shape.italic)
+ self.size = QDoubleSpinBox()
+ self.size.setRange(1, 1000)
+ self.size.setValue(shape.size)
+ self.text = QLineEdit(shape.text)
+ self.form.addRow("Width", self.width)
+ self.form.addRow("Height", self.height)
+ self.form.addRow("Color", self.color)
+ self.form.addRow("Bold", self.bold)
+ self.form.addRow("Italic", self.italic)
+ self.form.addRow("Size", self.size)
+ self.form.addRow("Text", self.text)
diff --git a/untitled.bedit.json b/untitled.bedit.json
index fda7e86..6f97a47 100644
--- a/untitled.bedit.json
+++ b/untitled.bedit.json
@@ -402,21 +402,164 @@
"icons": {
"43b3aee6-0b38-429b-9c6d-d38fc097297f": {
"shapes": {
- "7b93f7ba-d38e-4503-a64f-beb9fc915285": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0,
- "type": "rectangle",
+ "type": "text",
"pos": [
- -40,
- -40
+ -32,
+ -32
],
- "width": 80,
- "height": 80,
- "line_type": "solid",
- "line_thickness": 1.0,
- "corner_radius": 5.0,
- "line_color": "#000000ff",
- "fill_color": "#c8c8c8ff"
+ "width": 64.0,
+ "height": 64.0,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "C"
}
+ },
+ "port_positions": {
+ "c87881f1-23b4-4a69-b586-8e3c7bf6e21e": [
+ -8,
+ -8
+ ],
+ "c62de8eb-e13b-4849-bea5-c8cc5332269e": [
+ 16,
+ -32
+ ]
+ }
+ },
+ "5a8b2e8d-489f-467b-8282-7e344dfad576": {
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "R"
+ }
+ },
+ "port_positions": {
+ "0ac7d4ea-77f7-4c0d-8e37-406ef66e4740": [
+ -8,
+ -8
+ ],
+ "d1f5aab2-7bff-4b1e-8697-98fb6c3d8f02": [
+ 16,
+ -32
+ ]
+ }
+ },
+ "77701f68-b14c-4b98-8929-c5fba0261962": {
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64.0,
+ "height": 64.0,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "R"
+ }
+ },
+ "port_positions": {
+ "4a3f69f2-b305-4305-92e3-3998634ff226": [
+ -8,
+ -8
+ ]
+ }
+ },
+ "033b930e-bf79-403a-8b0b-a159f3c81ce9": {
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "I"
+ }
+ },
+ "port_positions": {
+ "0b9036b1-e4e4-437e-8c35-f5bb391b6cbd": [
+ -8,
+ -8
+ ],
+ "fb25f35d-4c0c-4cfa-92d4-1a18a71e2c11": [
+ 16,
+ -32
+ ]
+ }
+ },
+ "9ee2f42b-5ed1-446d-8790-c2a1df6b61d3": {
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "1"
+ }
+ },
+ "port_positions": {
+ "4306701a-6b1b-4d19-b8ff-45dbfa04f2d3": [
+ -8,
+ -8
+ ]
+ }
+ },
+ "9f392c28-6bef-4b10-9305-93e250747007": {
+ "shapes": {
+ "2a5bf670-c821-442b-9d46-1b07faa70722": {
+ "layer": 0,
+ "type": "text",
+ "pos": [
+ -32,
+ -32
+ ],
+ "width": 64,
+ "height": 64,
+ "color": "#000000ff",
+ "bold": true,
+ "italic": false,
+ "size": 64.0,
+ "text": "0"
+ }
+ },
+ "port_positions": {
+ "497b1f74-1186-471f-976a-36b07a451caf": [
+ -8,
+ -8
+ ]
}
}
}