Rectangles in incons

This commit is contained in:
2026-07-27 14:03:06 +02:00
parent 09a9e3f12f
commit 035cbdf53f
8 changed files with 403 additions and 47 deletions

View File

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