From 035cbdf53fc5c722b046537c6fbf38b216e1871d Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Mon, 27 Jul 2026 14:03:06 +0200 Subject: [PATCH] Rectangles in incons --- .vscode/launch.json | 5 +- src/bedit_gui/application.py | 21 ++- src/bedit_gui/models.py | 6 +- src/bedit_gui/views/color_button.py | 42 +++++ src/bedit_gui/views/icon_editor_window.py | 82 ++++++++- src/bedit_gui/views/icon_graphics_scene.py | 182 ++++++++++++++++++-- src/bedit_gui/views/shape_options_dialog.py | 81 +++++++++ untitled.bedit.json | 31 +--- 8 files changed, 403 insertions(+), 47 deletions(-) create mode 100644 src/bedit_gui/views/color_button.py create mode 100644 src/bedit_gui/views/shape_options_dialog.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 8782940..e975683 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,10 @@ "env": { "QT_QPA_PLATFORMTHEME": "qt6ct", "QT_QPA_PLATFORM": "xcb" - } + }, + "args": [ + "-f", "${workspaceFolder}/untitled.bedit.json" + ] } ] } \ No newline at end of file diff --git a/src/bedit_gui/application.py b/src/bedit_gui/application.py index a687706..8c568b3 100644 --- a/src/bedit_gui/application.py +++ b/src/bedit_gui/application.py @@ -1,6 +1,7 @@ from __future__ import annotations import sys +import argparse from PySide6.QtWidgets import QApplication @@ -15,8 +16,22 @@ from bedit_gui.documents import Document from bedit_gui.services.application_settings import ApplicationSettings from bedit_gui.views.main_window import MainWindow +def parse_arguments(): + parser = argparse.ArgumentParser(exit_on_error=False) + + parser.add_argument( + "-f", "--file", type=str, help="Path to a file to open", default=None, required=False + ) + + return parser.parse_args() def main() -> int: + try: + args = parse_arguments() + except argparse.ArgumentError as e: + print(e.message) + return 1 + app = QApplication(sys.argv) app.setOrganizationName("BEdit") @@ -36,7 +51,11 @@ def main() -> int: window_state_controller = WindowStateController(app, window) window_state_controller.restore() - document.new() + if args.file: + document.open(args.file) + else: + document.new() + window.showMaximized() return app.exec() diff --git a/src/bedit_gui/models.py b/src/bedit_gui/models.py index 551463d..7ff1ad8 100644 --- a/src/bedit_gui/models.py +++ b/src/bedit_gui/models.py @@ -45,13 +45,13 @@ class Rectangle(Shape): line_type: LineType = LineType.SOLID line_thickness: float = 1.0 corner_radius: float = 0.0 - line_color: str = "#000000" - fill_color: str = "#ffffff" + line_color: str = "#000000ff" + fill_color: str = "#ffffff00" @classmethod def from_data(cls, data: Mapping[str, Any]) -> Rectangle: 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", 100.0)), height=float(data.get("height", 100.0)), line_type=LineType(data.get("line_type", "solid")), line_thickness=float(data.get("line_thickness", 1.0)), corner_radius=float(data.get("corner_radius", 0.0)), line_color=str(data.get("line_color", "#000000")), fill_color=str(data.get("fill_color", "#ffffff"))) + return cls(layer=int(data.get("layer", 0)), pos=(int(pos[0]), int(pos[1])), width=float(data.get("width", 100.0)), height=float(data.get("height", 100.0)), line_type=LineType(data.get("line_type", "solid")), line_thickness=float(data.get("line_thickness", 1.0)), corner_radius=float(data.get("corner_radius", 0.0)), line_color=str(data.get("line_color", "#000000ff")), fill_color=str(data.get("fill_color", "#ffffff00"))) def to_data(self) -> dict[str, Any]: return {**super().to_data(), "width": self.width, "height": self.height, "line_type": self.line_type.value, "line_thickness": self.line_thickness, "corner_radius": self.corner_radius, "line_color": self.line_color, "fill_color": self.fill_color} diff --git a/src/bedit_gui/views/color_button.py b/src/bedit_gui/views/color_button.py new file mode 100644 index 0000000..8f0fa8e --- /dev/null +++ b/src/bedit_gui/views/color_button.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from PySide6.QtGui import QColor +from PySide6.QtWidgets import QColorDialog, QPushButton, QWidget + + +class ColorButton(QPushButton): + def __init__(self, color: str, parent: QWidget | None = None) -> None: + super().__init__(parent) + self.setObjectName("colorButton") + self._color = self._from_rgba(color) + self.clicked.connect(self._select_color) + self._update_display() + + def color(self) -> str: + return f"#{self._color.red():02x}{self._color.green():02x}{self._color.blue():02x}{self._color.alpha():02x}" + + def set_color(self, color: str) -> None: + self._color = self._from_rgba(color) + self._update_display() + + def _select_color(self) -> None: + parent = self.window() + dialog = QColorDialog(self._color, parent if parent is not self else None) + dialog.setOption(QColorDialog.ColorDialogOption.ShowAlphaChannel) + if dialog.exec() == QColorDialog.DialogCode.Accepted: + self._color = dialog.selectedColor() + self._update_display() + + def _update_display(self) -> None: + self.setText(self.color()) + foreground = "#000000" if self._color.lightness() > 127 or self._color.alpha() < 128 else "#ffffff" + self.setStyleSheet(f"QPushButton#colorButton {{ background-color: rgba({self._color.red()}, {self._color.green()}, {self._color.blue()}, {self._color.alpha()}); color: {foreground}; }}") + + @staticmethod + def _from_rgba(value: str) -> QColor: + color = value.removeprefix("#") + if len(color) == 6: + color += "ff" + if len(color) == 8: + return QColor(int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16), int(color[6:8], 16)) + return QColor(value) diff --git a/src/bedit_gui/views/icon_editor_window.py b/src/bedit_gui/views/icon_editor_window.py index 7b9b3e1..3987998 100644 --- a/src/bedit_gui/views/icon_editor_window.py +++ b/src/bedit_gui/views/icon_editor_window.py @@ -3,12 +3,13 @@ from __future__ import annotations from copy import deepcopy from PySide6.QtCore import QEvent, QObject, Qt, Signal -from PySide6.QtGui import QKeySequence, QPainter, QShortcut, QUndoCommand, QUndoStack, QWheelEvent -from PySide6.QtWidgets import QGraphicsView, QMainWindow, QWidget +from PySide6.QtGui import QKeySequence, QPainter, QShortcut, QUndoCommand, QUndoStack, QWheelEvent, QShowEvent +from PySide6.QtWidgets import QDialog, QGraphicsView, QMainWindow, QWidget from bedit_gui.models import Icon, Shape, ShapeID 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.shape_options_dialog import ShapeOptionsDialog class ChangeIconDraftCommand(QUndoCommand): @@ -39,6 +40,35 @@ class AddShapeCommand(QUndoCommand): self.editor._remove_shape(self.shape_id) +class ChangeShapeCommand(QUndoCommand): + def __init__(self, editor: IconEditorWindow, shape_id: ShapeID, old_shape: Shape, new_shape: Shape) -> None: + super().__init__(f"Change {new_shape.type}") + self.editor = editor + self.shape_id = shape_id + self.old_shape = deepcopy(old_shape) + self.new_shape = deepcopy(new_shape) + + def redo(self) -> None: + self.editor._change_shape(self.shape_id, self.new_shape) + + def undo(self) -> None: + self.editor._change_shape(self.shape_id, self.old_shape) + + +class DeleteShapesCommand(QUndoCommand): + def __init__(self, editor: IconEditorWindow, shapes: dict[ShapeID, Shape]) -> None: + text = "Delete shape" if len(shapes) == 1 else "Delete shapes" + super().__init__(text) + self.editor = editor + self.shapes = deepcopy(shapes) + + def redo(self) -> None: + self.editor._remove_shapes(self.shapes) + + def undo(self) -> None: + self.editor._restore_shapes(self.shapes) + + class IconEditorWindow(QMainWindow): """Independent icon editing session with its own undo stack.""" @@ -60,6 +90,8 @@ class IconEditorWindow(QMainWindow): self.scene = IconGraphicsScene(self) self.scene.set_icon(self._icon) self.scene.shape_created.connect(self._shape_created) + self.scene.shape_changed.connect(self._shape_changed) + self.scene.shape_options_requested.connect(self._show_shape_options) self.scene.tool_active_changed.connect(self._tool_active_changed) self.ui.graphicsView.setScene(self.scene) self.ui.graphicsView.setRenderHint(QPainter.RenderHint.Antialiasing) @@ -69,6 +101,8 @@ class IconEditorWindow(QMainWindow): self.ui.actionAdd_Rectangle.triggered.connect(self._start_rectangle_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) + self.delete_shortcut.activated.connect(self._delete_selected_shapes) self.zoom_in_shortcut = QShortcut(QKeySequence("Ctrl++"), self) self.zoom_in_alt_shortcut = QShortcut(QKeySequence("Ctrl+="), self) self.zoom_out_shortcut = QShortcut(QKeySequence("Ctrl+-"), self) @@ -90,6 +124,10 @@ class IconEditorWindow(QMainWindow): self.ui.actionUndo.setEnabled(False) self.ui.actionRedo.setEnabled(False) + def showEvent(self, event: QShowEvent) -> None: + super().showEvent(event) + self.fit_scene() + def icon(self) -> Icon: return deepcopy(self._icon) @@ -107,7 +145,10 @@ class IconEditorWindow(QMainWindow): self._zoom(1 / self.zoom_step) def reset_zoom(self) -> None: - self.ui.graphicsView.resetTransform() + self.fit_scene() + + def fit_scene(self) -> None: + self.ui.graphicsView.fitInView(self.scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio) def eventFilter(self, watched: QObject, event: QEvent) -> bool: if watched is self.ui.graphicsView.viewport() and isinstance(event, QWheelEvent): @@ -140,9 +181,44 @@ class IconEditorWindow(QMainWindow): self.scene.set_icon(self._icon) self.icon_changed.emit(self.icon()) + def _remove_shapes(self, shapes: dict[ShapeID, Shape]) -> None: + for shape_id in shapes: + self._icon.shapes.pop(shape_id, None) + self.scene.set_icon(self._icon) + self.icon_changed.emit(self.icon()) + + def _restore_shapes(self, shapes: dict[ShapeID, Shape]) -> None: + self._icon.shapes.update(deepcopy(shapes)) + self.scene.set_icon(self._icon) + self.icon_changed.emit(self.icon()) + + def _change_shape(self, shape_id: ShapeID, shape: Shape) -> None: + self._icon.shapes[shape_id] = deepcopy(shape) + self.scene.set_icon(self._icon) + self.icon_changed.emit(self.icon()) + def _shape_created(self, shape: Shape) -> None: self.undo_stack.push(AddShapeCommand(self, ShapeID(), shape)) + def _shape_changed(self, shape_id: ShapeID, old_shape: Shape, new_shape: Shape) -> None: + self.undo_stack.push(ChangeShapeCommand(self, shape_id, old_shape, new_shape)) + + def _delete_selected_shapes(self) -> None: + shape_ids = self.scene.selected_shape_ids() + shapes = {shape_id: self._icon.shapes[shape_id] for shape_id in shape_ids} + if shapes: + self.undo_stack.push(DeleteShapesCommand(self, shapes)) + + def _show_shape_options(self, shape_id: ShapeID) -> None: + old_shape = self._icon.shapes.get(shape_id) + if old_shape is None: + return + dialog = ShapeOptionsDialog(old_shape, self.scene.sceneRect(), self) + if dialog.exec() == QDialog.DialogCode.Accepted: + new_shape = dialog.shape() + if new_shape != old_shape: + self.undo_stack.push(ChangeShapeCommand(self, shape_id, old_shape, new_shape)) + def _start_rectangle_tool(self) -> None: layer = max((shape.layer for shape in self._icon.shapes.values()), default=-1) + 1 self.scene.set_creation_tool(RectangleCreationTool(self.scene, layer)) diff --git a/src/bedit_gui/views/icon_graphics_scene.py b/src/bedit_gui/views/icon_graphics_scene.py index 481f510..f6d02ba 100644 --- a/src/bedit_gui/views/icon_graphics_scene.py +++ b/src/bedit_gui/views/icon_graphics_scene.py @@ -1,12 +1,14 @@ from __future__ import annotations +import math +from copy import deepcopy from typing import Protocol from PySide6.QtCore import QObject, QPointF, QRectF, Qt, Signal -from PySide6.QtGui import QBrush, QColor, QPainterPath, QPen -from PySide6.QtWidgets import QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneMouseEvent +from PySide6.QtGui import QBrush, QColor, QPainter, QPainterPath, QPen +from PySide6.QtWidgets import QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem, QGraphicsScene, QGraphicsSceneContextMenuEvent, QGraphicsSceneMouseEvent, QMenu, QStyleOptionGraphicsItem, QWidget -from bedit_gui.models import Icon, LineType, Rectangle, Shape +from bedit_gui.models import Icon, LineType, Rectangle, Shape, ShapeID class ShapeCreationTool(Protocol): @@ -24,16 +26,19 @@ class RectangleCreationTool: self.preview: QGraphicsRectItem | None = None def begin(self, position: QPointF) -> None: + position = self._bounded(position) self.start = position self.preview = self.scene.addRect(QRectF(position, position), QPen(Qt.PenStyle.DashLine)) def update(self, position: QPointF) -> None: if self.preview is not None and self.start is not None: + position = self._bounded(position) self.preview.setRect(QRectF(self.start, position).normalized()) def finish(self, position: QPointF) -> Rectangle | None: if self.start is None: return None + position = self._bounded(position) rect = QRectF(self.start, position).normalized() self.cancel() if rect.width() < 1 or rect.height() < 1: @@ -46,21 +51,143 @@ class RectangleCreationTool: self.preview = None self.start = None + def _bounded(self, position: QPointF) -> QPointF: + rect = self.scene.sceneRect() + x = max(rect.left(), min(rect.right(), round(position.x()))) + y = max(rect.top(), min(rect.bottom(), round(position.y()))) + return QPointF(x, y) + + +class ShapeGraphicsItem(QGraphicsPathItem): + handle_size = 6.0 + + def __init__(self, shape_id: ShapeID, shape: Shape, scene: IconGraphicsScene) -> None: + super().__init__() + self.shape_id = shape_id + self.shape_model = deepcopy(shape) + self.icon_scene = scene + self._original_shape: Shape | None = None + self._resizing = False + self.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemIsMovable | QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges) + + def resize_handle_rect(self) -> QRectF: + size = self.handle_size + corner = self.path().boundingRect().bottomRight() + return QRectF(corner.x() - size / 2, corner.y() - size / 2, size, size) + + def boundingRect(self) -> QRectF: + margin = self.handle_size / 2 + return super().boundingRect().adjusted(-margin, -margin, margin, margin) + + def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: + self._original_shape = self.current_shape() + self._resizing = self.isSelected() and self.resize_handle_rect().contains(event.pos()) + if self._resizing: + event.accept() + return + super().mousePressEvent(event) + + def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: + if self._resizing: + self.resize_to(event.scenePos()) + event.accept() + return + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: + if self._resizing: + self.resize_to(event.scenePos()) + self._resizing = False + event.accept() + else: + super().mouseReleaseEvent(event) + current = self.current_shape() + if self._original_shape is not None and current != self._original_shape: + self.icon_scene.shape_changed.emit(self.shape_id, self._original_shape, current) + self._original_shape = None + + def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: + if not self.isSelected(): + self.icon_scene.clearSelection() + self.setSelected(True) + menu = QMenu() + options = menu.addAction("Shape Options") + if menu.exec(event.screenPos()) is options: + self.icon_scene.shape_options_requested.emit(self.shape_id) + event.accept() + + def itemChange(self, change: QGraphicsItem.GraphicsItemChange, value: object) -> object: + if change is QGraphicsItem.GraphicsItemChange.ItemPositionChange and self.scene() is not None: + position = value + if isinstance(position, QPointF): + bounds = self.icon_scene.sceneRect() + size = self.path().boundingRect() + x = max(bounds.left(), min(bounds.right() - size.width(), round(position.x()))) + y = max(bounds.top(), min(bounds.bottom() - size.height(), round(position.y()))) + return QPointF(x, y) + return super().itemChange(change, value) + + def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget | None = None) -> None: + super().paint(painter, option, widget) + if self.isSelected(): + painter.setPen(QPen(QColor("#ffffff"))) + painter.setBrush(QBrush(QColor("#2675bf"))) + painter.drawRect(self.resize_handle_rect()) + + def current_shape(self) -> Shape: + raise NotImplementedError + + def resize_to(self, position: QPointF) -> None: + raise NotImplementedError + + +class RectangleGraphicsItem(ShapeGraphicsItem): + def __init__(self, shape_id: ShapeID, shape: Rectangle, scene: IconGraphicsScene) -> None: + super().__init__(shape_id, shape, scene) + self.rectangle = deepcopy(shape) + self.setPos(shape.pos[0], shape.pos[1]) + self._set_size(shape.width, shape.height) + self.setPen(scene._pen(shape)) + self.setBrush(QBrush(scene._color(shape.fill_color))) + self.setZValue(shape.layer) + + def current_shape(self) -> Rectangle: + shape = deepcopy(self.rectangle) + 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 _set_size(self, width: float, height: float) -> None: + self.prepareGeometryChange() + path = QPainterPath() + path.addRoundedRect(QRectF(0, 0, width, height), self.rectangle.corner_radius, self.rectangle.corner_radius) + self.setPath(path) + class IconGraphicsScene(QGraphicsScene): shape_created = Signal(object) + shape_changed = Signal(object, object, object) + shape_options_requested = Signal(object) tool_active_changed = Signal(bool) def __init__(self, parent: QObject | None = None) -> None: super().__init__(parent) self._tool: ShapeCreationTool | None = None - self.setSceneRect(-100, -100, 200, 200) + self.setSceneRect(-64, -64, 128, 128) def set_icon(self, icon: Icon) -> None: self.cancel_creation_tool() self.clear() - for shape in sorted(icon.shapes.values(), key=lambda item: item.layer): - self._add_shape_item(shape) + for shape_id, shape in sorted(icon.shapes.items(), key=lambda item: item[1].layer): + self._add_shape_item(shape_id, shape) def set_creation_tool(self, tool: ShapeCreationTool) -> None: self.cancel_creation_tool() @@ -77,6 +204,29 @@ class IconGraphicsScene(QGraphicsScene): def has_creation_tool(self) -> bool: return self._tool is not None + def selected_shape_ids(self) -> list[ShapeID]: + return [item.shape_id for item in self.selectedItems() if isinstance(item, ShapeGraphicsItem)] + + def drawBackground(self, painter: QPainter, rect: QRectF) -> None: + super().drawBackground(painter, rect) + rect = rect.intersected(self.sceneRect()) + if rect.isEmpty(): + return + pen = QPen(QColor("#d0d0d0")) + pen.setCosmetic(True) + painter.setPen(pen) + + first_x = math.floor(rect.left() / 8) * 8 + last_x = math.ceil(rect.right() / 8) * 8 + first_y = math.floor(rect.top() / 8) * 8 + last_y = math.ceil(rect.bottom() / 8) * 8 + + for x in range(first_x, last_x + 1, 8): + painter.drawLine(QPointF(x, rect.top()), QPointF(x, rect.bottom())) + + for y in range(first_y, last_y + 1, 8): + painter.drawLine(QPointF(rect.left(), y), QPointF(rect.right(), y)) + def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: if self._tool is not None and event.button() == Qt.MouseButton.LeftButton: self._tool.begin(event.scenePos()) @@ -103,21 +253,21 @@ class IconGraphicsScene(QGraphicsScene): return super().mouseReleaseEvent(event) - def _add_shape_item(self, shape: Shape) -> None: + def _add_shape_item(self, shape_id: ShapeID, shape: Shape) -> None: if not isinstance(shape, Rectangle): return - rect = QRectF(shape.pos[0], shape.pos[1], shape.width, shape.height) - path = QPainterPath() - path.addRoundedRect(rect, shape.corner_radius, shape.corner_radius) - item = QGraphicsPathItem(path) - item.setPen(self._pen(shape)) - item.setBrush(QBrush(QColor(shape.fill_color))) - item.setZValue(shape.layer) - self.addItem(item) + self.addItem(RectangleGraphicsItem(shape_id, shape, self)) @staticmethod def _pen(shape: Rectangle) -> QPen: styles = {LineType.SOLID: Qt.PenStyle.SolidLine, LineType.DASHED: Qt.PenStyle.DashLine, LineType.DOTTED: Qt.PenStyle.DotLine, LineType.DASH_DOT: Qt.PenStyle.DashDotLine} if shape.line_type is LineType.NONE: return QPen(Qt.PenStyle.NoPen) - return QPen(QColor(shape.line_color), shape.line_thickness, styles[shape.line_type]) + return QPen(IconGraphicsScene._color(shape.line_color), shape.line_thickness, styles[shape.line_type]) + + @staticmethod + def _color(value: str) -> QColor: + color = value.removeprefix("#") + if len(color) == 8: + return QColor(int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16), int(color[6:8], 16)) + return QColor(value) diff --git a/src/bedit_gui/views/shape_options_dialog.py b/src/bedit_gui/views/shape_options_dialog.py new file mode 100644 index 0000000..ee547a7 --- /dev/null +++ b/src/bedit_gui/views/shape_options_dialog.py @@ -0,0 +1,81 @@ +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 bedit_gui.models import LineType, Rectangle, Shape +from bedit_gui.views.color_button import ColorButton + + +class ShapeOptionsDialog(QDialog): + def __init__(self, shape: Shape, scene_rect: QRectF, parent: QWidget | None = None) -> None: + super().__init__(parent) + self._shape = deepcopy(shape) + self._scene_rect = scene_rect + self.setWindowTitle("Shape Options") + + self.form = QFormLayout() + self.type_label = QLabel(shape.type or "") + self.layer = QSpinBox() + self.layer.setRange(-1000000, 1000000) + self.layer.setValue(shape.layer) + self.x = QSpinBox() + self.x.setRange(round(scene_rect.left()), round(scene_rect.right() - 1)) + self.x.setValue(shape.pos[0]) + self.y = QSpinBox() + self.y.setRange(round(scene_rect.top()), round(scene_rect.bottom() - 1)) + self.y.setValue(shape.pos[1]) + self.form.addRow("Type", self.type_label) + self.form.addRow("Layer", self.layer) + self.form.addRow("X", self.x) + self.form.addRow("Y", self.y) + + if isinstance(shape, Rectangle): + self._add_rectangle_fields(shape) + + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + buttons.accepted.connect(self.accept) + buttons.rejected.connect(self.reject) + layout = QVBoxLayout(self) + layout.addLayout(self.form) + 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()) + + def _add_rectangle_fields(self, shape: Rectangle) -> 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.line_type = QComboBox() + for line_type in LineType: + self.line_type.addItem(line_type.value.replace("_", " ").title(), line_type) + self.line_type.setCurrentIndex(self.line_type.findData(shape.line_type)) + self.line_thickness = QDoubleSpinBox() + self.line_thickness.setRange(0, 1000) + self.line_thickness.setValue(shape.line_thickness) + self.corner_radius = QDoubleSpinBox() + self.corner_radius.setRange(0, max(self._scene_rect.width(), self._scene_rect.height())) + self.corner_radius.setValue(shape.corner_radius) + self.line_color = ColorButton(shape.line_color) + self.fill_color = ColorButton(shape.fill_color) + self.form.addRow("Width", self.width) + self.form.addRow("Height", self.height) + self.form.addRow("Line type", self.line_type) + self.form.addRow("Line thickness", self.line_thickness) + self.form.addRow("Corner radius", self.corner_radius) + self.form.addRow("Line color", self.line_color) + self.form.addRow("Fill color", self.fill_color) diff --git a/untitled.bedit.json b/untitled.bedit.json index 73fb31c..fda7e86 100644 --- a/untitled.bedit.json +++ b/untitled.bedit.json @@ -402,35 +402,20 @@ "icons": { "43b3aee6-0b38-429b-9c6d-d38fc097297f": { "shapes": { - "1749a693-44fe-4275-ab22-eb82db339df4": { + "7b93f7ba-d38e-4503-a64f-beb9fc915285": { "layer": 0, "type": "rectangle", "pos": [ - -138, - -84 + -40, + -40 ], - "width": 241.0, - "height": 217.0, + "width": 80, + "height": 80, "line_type": "solid", "line_thickness": 1.0, - "corner_radius": 0.0, - "line_color": "#000000", - "fill_color": "#ffffff" - }, - "923075c7-3d34-4d0f-902d-cda24731433a": { - "layer": 1, - "type": "rectangle", - "pos": [ - -63, - -31 - ], - "width": 72.0, - "height": 119.0, - "line_type": "solid", - "line_thickness": 1.0, - "corner_radius": 0.0, - "line_color": "#000000", - "fill_color": "#ffffff" + "corner_radius": 5.0, + "line_color": "#000000ff", + "fill_color": "#c8c8c8ff" } } }