Text fields and saving Icons to file

This commit is contained in:
2026-07-27 14:30:25 +02:00
parent 13d3825f9b
commit c68e693359
12 changed files with 510 additions and 35 deletions

25
0.icon.json Normal file
View File

@@ -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
]
}
}

25
1.icon.json Normal file
View File

@@ -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
]
}
}

29
C.icon.json Normal file
View File

@@ -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
]
}
}

29
I.icon.json Normal file
View File

@@ -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
]
}
}

29
R.icon.json Normal file
View File

@@ -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
]
}
}

View File

@@ -22,6 +22,8 @@ class Shape:
def from_data(cls, data: Mapping[str, Any]) -> Shape: def from_data(cls, data: Mapping[str, Any]) -> Shape:
if cls is Shape and data.get("type") == "rectangle": if cls is Shape and data.get("type") == "rectangle":
return Rectangle.from_data(data) 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]) 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]))) 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 @dataclass
class Text(Shape): class Text(Shape):
type: str = field(init=False, default="text") type: str = field(init=False, default="text")
width: float = 100.0 width: float = 32.0
height: float = 100.0 height: float = 16.0
color: str = "#000000ff" color: str = "#000000ff"
bold: bool = False bold: bool = False
italic: bool = False italic: bool = False
size: float = 16.0 size: float = 16.0
text: str = "" 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 @dataclass
class Icon: class Icon:
shapes: dict[ShapeID, Shape] = field(default_factory=dict) shapes: dict[ShapeID, Shape] = field(default_factory=dict)

View File

@@ -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")

View File

@@ -39,10 +39,18 @@
</property> </property>
<addaction name="actionUndo"/> <addaction name="actionUndo"/>
<addaction name="actionRedo"/> <addaction name="actionRedo"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpen_from_File"/>
<addaction name="actionSave_to_File"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionSave"/> <addaction name="actionSave"/>
<addaction name="actionCancel"/> <addaction name="actionCancel"/>
</widget> </widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/> <addaction name="menuEdit"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
@@ -176,6 +184,42 @@
<enum>QAction::MenuRole::NoRole</enum> <enum>QAction::MenuRole::NoRole</enum>
</property> </property>
</action> </action>
<action name="actionSave_to_File">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/document-save-as.png</normaloff>:/icons/icons/document-save-as.png</iconset>
</property>
<property name="text">
<string>Save to File</string>
</property>
<property name="toolTip">
<string>Save icon to a file</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+S</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionOpen_from_File">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/document-open.png</normaloff>:/icons/icons/document-open.png</iconset>
</property>
<property name="text">
<string>Open from File</string>
</property>
<property name="toolTip">
<string>Open icon from File</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+O</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="../../resources/resources.qrc"/> <include location="../../resources/resources.qrc"/>

View File

@@ -4,14 +4,18 @@ from copy import deepcopy
from PySide6.QtCore import QEvent, QObject, Qt, Signal from PySide6.QtCore import QEvent, QObject, Qt, Signal
from PySide6.QtGui import QKeySequence, QPainter, QShortcut, QUndoCommand, QUndoStack, QWheelEvent, QShowEvent 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_core.models import Port, PortID, SignalDirection
from bedit_gui.models import Icon, Shape, ShapeID 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.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 from bedit_gui.views.shape_options_dialog import ShapeOptionsDialog
logger = get_logger(__name__)
class ChangeIconDraftCommand(QUndoCommand): class ChangeIconDraftCommand(QUndoCommand):
def __init__(self, editor: IconEditorWindow, icon: Icon, text: str) -> None: def __init__(self, editor: IconEditorWindow, icon: Icon, text: str) -> None:
@@ -102,10 +106,11 @@ class IconEditorWindow(QMainWindow):
self.setWindowTitle("Icon Editor") self.setWindowTitle("Icon Editor")
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
self._icon = deepcopy(icon) self._icon = deepcopy(icon)
self._ports = deepcopy(ports)
self.scene = IconGraphicsScene(self) self.scene = IconGraphicsScene(self)
self.scene.set_ports(ports) self.scene.set_ports(self._ports)
self._ensure_port_positions(ports) self._ensure_port_positions(self._ports)
self.scene.set_icon(self._icon) self.scene.set_icon(self._icon)
self.scene.port_moved.connect(self._port_moved) self.scene.port_moved.connect(self._port_moved)
self.scene.shape_created.connect(self._shape_created) self.scene.shape_created.connect(self._shape_created)
@@ -118,6 +123,8 @@ class IconEditorWindow(QMainWindow):
self.ui.graphicsView.viewport().installEventFilter(self) self.ui.graphicsView.viewport().installEventFilter(self)
self.ui.actionAdd_Rectangle.setCheckable(True) self.ui.actionAdd_Rectangle.setCheckable(True)
self.ui.actionAdd_Rectangle.triggered.connect(self._start_rectangle_tool) 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 = QShortcut(QKeySequence(Qt.Key.Key_Escape), self)
self.cancel_tool_shortcut.activated.connect(self.scene.cancel_creation_tool) self.cancel_tool_shortcut.activated.connect(self.scene.cancel_creation_tool)
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key.Key_Delete), self) 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.actionUndo.triggered.connect(self.undo_stack.undo)
self.ui.actionRedo.triggered.connect(self.undo_stack.redo) self.ui.actionRedo.triggered.connect(self.undo_stack.redo)
self.ui.actionSave.triggered.connect(self.save) 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.ui.actionCancel.triggered.connect(self.close)
self.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled) self.undo_stack.canUndoChanged.connect(self.ui.actionUndo.setEnabled)
self.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled) self.undo_stack.canRedoChanged.connect(self.ui.actionRedo.setEnabled)
@@ -157,6 +166,32 @@ class IconEditorWindow(QMainWindow):
self.saved.emit(self.icon()) self.saved.emit(self.icon())
self.close() 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: def zoom_in(self) -> None:
self._zoom(self.zoom_step) self._zoom(self.zoom_step)
@@ -187,6 +222,7 @@ class IconEditorWindow(QMainWindow):
def _set_icon(self, icon: Icon) -> None: def _set_icon(self, icon: Icon) -> None:
self._icon = deepcopy(icon) self._icon = deepcopy(icon)
self._ensure_port_positions(self._ports)
self.scene.set_icon(self._icon) self.scene.set_icon(self._icon)
self.icon_changed.emit(self.icon()) self.icon_changed.emit(self.icon())
@@ -268,10 +304,18 @@ class IconEditorWindow(QMainWindow):
def _start_rectangle_tool(self) -> None: def _start_rectangle_tool(self) -> None:
layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1 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)) 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: 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 cursor = Qt.CursorShape.CrossCursor if active else Qt.CursorShape.ArrowCursor
self.ui.graphicsView.viewport().setCursor(cursor) self.ui.graphicsView.viewport().setCursor(cursor)

View File

@@ -5,11 +5,11 @@ from copy import deepcopy
from typing import Protocol from typing import Protocol
from PySide6.QtCore import QObject, QPointF, QRectF, Qt, Signal 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 PySide6.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget
from bedit_core.models import Port, PortID, SignalDirection 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): class ShapeCreationTool(Protocol):
@@ -36,7 +36,7 @@ class RectangleCreationTool:
position = self._bounded(position) position = self._bounded(position)
self.preview.setRect(QRectF(self.start, position).normalized()) 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: if self.start is None:
return None return None
position = self._bounded(position) position = self._bounded(position)
@@ -44,6 +44,9 @@ class RectangleCreationTool:
self.cancel() self.cancel()
if rect.width() < 1 or rect.height() < 1: if rect.width() < 1 or rect.height() < 1:
return None 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()) return Rectangle(layer=self.layer, pos=(round(rect.x()), round(rect.y())), width=rect.width(), height=rect.height())
def cancel(self) -> None: def cancel(self) -> None:
@@ -59,6 +62,11 @@ class RectangleCreationTool:
return QPointF(x, y) 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): class ShapeGraphicsItem(QGraphicsPathItem):
handle_size = 6.0 handle_size = 6.0
@@ -173,6 +181,48 @@ class RectangleGraphicsItem(ShapeGraphicsItem):
self.setPath(path) 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): class PortGraphicsItem(QGraphicsRectItem):
size = 16.0 size = 16.0
@@ -304,9 +354,10 @@ class IconGraphicsScene(QGraphicsScene):
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
def _add_shape_item(self, shape_id: ShapeID, shape: Shape) -> None: def _add_shape_item(self, shape_id: ShapeID, shape: Shape) -> None:
if not isinstance(shape, Rectangle): if isinstance(shape, Rectangle):
return
self.addItem(RectangleGraphicsItem(shape_id, shape, self)) self.addItem(RectangleGraphicsItem(shape_id, shape, self))
elif isinstance(shape, Text):
self.addItem(TextGraphicsItem(shape_id, shape, self))
@staticmethod @staticmethod
def _pen(shape: Rectangle) -> QPen: def _pen(shape: Rectangle) -> QPen:

View File

@@ -3,9 +3,9 @@ from __future__ import annotations
from copy import deepcopy from copy import deepcopy
from PySide6.QtCore import QRectF 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 from bedit_gui.views.color_button import ColorButton
@@ -34,6 +34,8 @@ class ShapeOptionsDialog(QDialog):
if isinstance(shape, Rectangle): if isinstance(shape, Rectangle):
self._add_rectangle_fields(shape) self._add_rectangle_fields(shape)
elif isinstance(shape, Text):
self._add_text_fields(shape)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(self.accept) buttons.accepted.connect(self.accept)
@@ -43,16 +45,19 @@ class ShapeOptionsDialog(QDialog):
layout.addWidget(buttons) layout.addWidget(buttons)
def shape(self) -> Shape: def shape(self) -> Shape:
if not isinstance(self._shape, Rectangle): 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 = deepcopy(self._shape)
shape.layer = self.layer.value() shape.layer = self.layer.value()
shape.pos = (self.x.value(), self.y.value()) shape.pos = (self.x.value(), self.y.value())
return shape 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())
def _add_rectangle_fields(self, shape: Rectangle) -> None: def _add_rectangle_fields(self, shape: Rectangle) -> None:
self.width = QSpinBox() self.width = QSpinBox()
self.width.setRange(1, round(self._scene_rect.width())) self.width.setRange(1, round(self._scene_rect.width()))
@@ -79,3 +84,27 @@ class ShapeOptionsDialog(QDialog):
self.form.addRow("Corner radius", self.corner_radius) self.form.addRow("Corner radius", self.corner_radius)
self.form.addRow("Line color", self.line_color) self.form.addRow("Line color", self.line_color)
self.form.addRow("Fill color", self.fill_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)

View File

@@ -402,21 +402,164 @@
"icons": { "icons": {
"43b3aee6-0b38-429b-9c6d-d38fc097297f": { "43b3aee6-0b38-429b-9c6d-d38fc097297f": {
"shapes": { "shapes": {
"7b93f7ba-d38e-4503-a64f-beb9fc915285": { "2a5bf670-c821-442b-9d46-1b07faa70722": {
"layer": 0, "layer": 0,
"type": "rectangle", "type": "text",
"pos": [ "pos": [
-40, -32,
-40 -32
], ],
"width": 80, "width": 64.0,
"height": 80, "height": 64.0,
"line_type": "solid", "color": "#000000ff",
"line_thickness": 1.0, "bold": true,
"corner_radius": 5.0, "italic": false,
"line_color": "#000000ff", "size": 64.0,
"fill_color": "#c8c8c8ff" "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
]
} }
} }
} }