drawsvg-ui 0.5.5__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- canvas_view.py +297 -105
- constants.py +22 -15
- {drawsvg_ui-0.5.5.dist-info → drawsvg_ui-0.6.0.dist-info}/METADATA +1 -1
- {drawsvg_ui-0.5.5.dist-info → drawsvg_ui-0.6.0.dist-info}/RECORD +10 -10
- {drawsvg_ui-0.5.5.dist-info → drawsvg_ui-0.6.0.dist-info}/WHEEL +1 -1
- export_drawsvg.py +21 -10
- import_drawsvg.py +14 -4
- {drawsvg_ui-0.5.5.dist-info → drawsvg_ui-0.6.0.dist-info}/entry_points.txt +0 -0
- {drawsvg_ui-0.5.5.dist-info → drawsvg_ui-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {drawsvg_ui-0.5.5.dist-info → drawsvg_ui-0.6.0.dist-info}/top_level.txt +0 -0
canvas_view.py
CHANGED
|
@@ -14,7 +14,14 @@ from typing import Any, Callable
|
|
|
14
14
|
from PySide6 import QtCore, QtGui, QtWidgets
|
|
15
15
|
from PySide6.QtGui import QTransform
|
|
16
16
|
|
|
17
|
-
from constants import
|
|
17
|
+
from constants import (
|
|
18
|
+
DEFAULTS,
|
|
19
|
+
HOVER_PREVIEW_COLOR,
|
|
20
|
+
HOVER_PREVIEW_STRENGTH,
|
|
21
|
+
HOVER_PREVIEW_X_COUNT,
|
|
22
|
+
PALETTE_MIME,
|
|
23
|
+
SHAPES,
|
|
24
|
+
)
|
|
18
25
|
from items import (
|
|
19
26
|
BlockArrowItem,
|
|
20
27
|
CurvyBracketItem,
|
|
@@ -224,15 +231,26 @@ class SceneHistory(QtCore.QObject):
|
|
|
224
231
|
return
|
|
225
232
|
self._timer.start()
|
|
226
233
|
|
|
227
|
-
def capture_now(self) -> None:
|
|
228
|
-
self._capture_snapshot()
|
|
229
|
-
|
|
230
|
-
def
|
|
231
|
-
if not self.
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
self.
|
|
234
|
+
def capture_now(self) -> None:
|
|
235
|
+
self._capture_snapshot()
|
|
236
|
+
|
|
237
|
+
def _flush_pending_snapshot(self) -> None:
|
|
238
|
+
if self._ignore_changes or not self._timer.isActive():
|
|
239
|
+
return
|
|
240
|
+
# Only flush when we're on the latest state; otherwise we'd
|
|
241
|
+
# truncate redo history while navigating older states.
|
|
242
|
+
if self._index != len(self._states) - 1:
|
|
243
|
+
return
|
|
244
|
+
self._timer.stop()
|
|
245
|
+
self._capture_snapshot()
|
|
246
|
+
|
|
247
|
+
def undo(self) -> None:
|
|
248
|
+
self._flush_pending_snapshot()
|
|
249
|
+
if not self.can_undo():
|
|
250
|
+
return
|
|
251
|
+
self._index -= 1
|
|
252
|
+
self._apply_current_state()
|
|
253
|
+
self._notify()
|
|
236
254
|
|
|
237
255
|
def redo(self) -> None:
|
|
238
256
|
if not self.can_redo():
|
|
@@ -690,11 +708,12 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
690
708
|
QtCore.QTimer.singleShot(0, self._fit_view_to_page)
|
|
691
709
|
self._update_scene_rect()
|
|
692
710
|
|
|
693
|
-
self._panning = False
|
|
694
|
-
self._pan_start = QtCore.QPointF()
|
|
695
|
-
self._prev_drag_mode = self.dragMode()
|
|
696
|
-
self._right_button_pressed = False
|
|
697
|
-
self._suppress_context_menu = False
|
|
711
|
+
self._panning = False
|
|
712
|
+
self._pan_start = QtCore.QPointF()
|
|
713
|
+
self._prev_drag_mode = self.dragMode()
|
|
714
|
+
self._right_button_pressed = False
|
|
715
|
+
self._suppress_context_menu = False
|
|
716
|
+
self._hover_preview_item: QtWidgets.QGraphicsItem | None = None
|
|
698
717
|
|
|
699
718
|
self._history = SceneHistory(self)
|
|
700
719
|
self._history.capture_initial_state()
|
|
@@ -723,37 +742,61 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
723
742
|
return False
|
|
724
743
|
return True
|
|
725
744
|
|
|
726
|
-
def
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
745
|
+
def _stacking_indices(self) -> dict[int, int]:
|
|
746
|
+
scene = self.scene()
|
|
747
|
+
if scene is None:
|
|
748
|
+
return {}
|
|
749
|
+
try:
|
|
750
|
+
ordered = scene.items(QtCore.Qt.SortOrder.AscendingOrder)
|
|
751
|
+
except TypeError:
|
|
752
|
+
# Older bindings can miss the overload with explicit sort order.
|
|
753
|
+
ordered = list(reversed(scene.items()))
|
|
754
|
+
return {id(item): index for index, item in enumerate(ordered)}
|
|
755
|
+
|
|
756
|
+
def _item_sort_key(
|
|
757
|
+
self,
|
|
758
|
+
item: QtWidgets.QGraphicsItem,
|
|
759
|
+
stacking_indices: Mapping[int, int] | None = None,
|
|
760
|
+
) -> tuple[float, int, str, float, float]:
|
|
761
|
+
shape = str(item.data(0)) if item.data(0) else item.__class__.__name__
|
|
762
|
+
pos = item.pos()
|
|
763
|
+
stack_index = -1
|
|
764
|
+
if stacking_indices is not None:
|
|
765
|
+
stack_index = int(stacking_indices.get(id(item), -1))
|
|
766
|
+
return (
|
|
767
|
+
round(float(item.zValue()), 6),
|
|
768
|
+
stack_index,
|
|
769
|
+
shape,
|
|
770
|
+
round(float(pos.x()), 6),
|
|
771
|
+
round(float(pos.y()), 6),
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
def _serialize_scene_state(self) -> dict[str, Any]:
|
|
775
|
+
scene = self.scene()
|
|
776
|
+
if scene is None:
|
|
777
|
+
return {"items": [], "grid_visible": bool(self._show_grid)}
|
|
778
|
+
stacking_indices = self._stacking_indices()
|
|
779
|
+
items = [
|
|
780
|
+
item
|
|
781
|
+
for item in scene.items()
|
|
782
|
+
if self._is_serializable_item(item) and item.parentItem() is None
|
|
783
|
+
]
|
|
784
|
+
items.sort(key=lambda item: self._item_sort_key(item, stacking_indices))
|
|
785
|
+
return {
|
|
786
|
+
"items": [self._serialize_item(item, stacking_indices) for item in items],
|
|
787
|
+
"grid_visible": bool(self._show_grid),
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
def _serialize_item(
|
|
791
|
+
self,
|
|
792
|
+
item: QtWidgets.QGraphicsItem,
|
|
793
|
+
stacking_indices: Mapping[int, int] | None = None,
|
|
794
|
+
) -> dict[str, Any]:
|
|
795
|
+
shape_value = item.data(0)
|
|
796
|
+
shape = str(shape_value) if shape_value else item.__class__.__name__
|
|
797
|
+
base: dict[str, Any] = {
|
|
798
|
+
"shape": shape,
|
|
799
|
+
"class": item.__class__.__name__,
|
|
757
800
|
"pos": [float(item.pos().x()), float(item.pos().y())],
|
|
758
801
|
"rotation": float(item.rotation()),
|
|
759
802
|
"scale": float(item.scale()),
|
|
@@ -844,14 +887,18 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
844
887
|
base["direction"] = item.text_direction()
|
|
845
888
|
elif isinstance(item, FolderTreeItem):
|
|
846
889
|
base["structure"] = item.structure()
|
|
847
|
-
elif isinstance(item, GroupItem):
|
|
848
|
-
children = [
|
|
849
|
-
child
|
|
850
|
-
for child in item.childItems()
|
|
851
|
-
if self._is_serializable_item(child)
|
|
852
|
-
]
|
|
853
|
-
children.sort(
|
|
854
|
-
|
|
890
|
+
elif isinstance(item, GroupItem):
|
|
891
|
+
children = [
|
|
892
|
+
child
|
|
893
|
+
for child in item.childItems()
|
|
894
|
+
if self._is_serializable_item(child)
|
|
895
|
+
]
|
|
896
|
+
children.sort(
|
|
897
|
+
key=lambda child: self._item_sort_key(child, stacking_indices)
|
|
898
|
+
)
|
|
899
|
+
base["children"] = [
|
|
900
|
+
self._serialize_item(child, stacking_indices) for child in children
|
|
901
|
+
]
|
|
855
902
|
else:
|
|
856
903
|
width, height = self._item_dimensions(item)
|
|
857
904
|
base["size"] = [width, height]
|
|
@@ -978,10 +1025,45 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
978
1025
|
return float(width_attr), float(height_attr)
|
|
979
1026
|
bounds = item.boundingRect()
|
|
980
1027
|
return float(bounds.width()), float(bounds.height())
|
|
981
|
-
|
|
982
|
-
def
|
|
983
|
-
|
|
984
|
-
|
|
1028
|
+
|
|
1029
|
+
def _hover_preview_target_at(
|
|
1030
|
+
self, view_pos: QtCore.QPoint
|
|
1031
|
+
) -> QtWidgets.QGraphicsItem | None:
|
|
1032
|
+
candidate = self.itemAt(view_pos)
|
|
1033
|
+
selectable = QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
|
1034
|
+
while candidate is not None and not (candidate.flags() & selectable):
|
|
1035
|
+
candidate = candidate.parentItem()
|
|
1036
|
+
if candidate is None:
|
|
1037
|
+
return None
|
|
1038
|
+
if candidate.isSelected():
|
|
1039
|
+
return None
|
|
1040
|
+
if isinstance(candidate, LineItem):
|
|
1041
|
+
return None
|
|
1042
|
+
if not self._is_serializable_item(candidate):
|
|
1043
|
+
return None
|
|
1044
|
+
return candidate
|
|
1045
|
+
|
|
1046
|
+
def _set_hover_preview_item(self, item: QtWidgets.QGraphicsItem | None) -> None:
|
|
1047
|
+
if item is self._hover_preview_item:
|
|
1048
|
+
return
|
|
1049
|
+
self._hover_preview_item = item
|
|
1050
|
+
self.viewport().update()
|
|
1051
|
+
|
|
1052
|
+
def _clear_hover_preview(self) -> None:
|
|
1053
|
+
self._set_hover_preview_item(None)
|
|
1054
|
+
|
|
1055
|
+
def _notify_selection_snapshot(self) -> None:
|
|
1056
|
+
hover_item = self._hover_preview_item
|
|
1057
|
+
if hover_item is not None:
|
|
1058
|
+
try:
|
|
1059
|
+
if hover_item.isSelected():
|
|
1060
|
+
self._hover_preview_item = None
|
|
1061
|
+
self.viewport().update()
|
|
1062
|
+
except RuntimeError:
|
|
1063
|
+
self._hover_preview_item = None
|
|
1064
|
+
self.viewport().update()
|
|
1065
|
+
payload = self._build_selection_snapshot()
|
|
1066
|
+
self.selectionSnapshotChanged.emit(payload)
|
|
985
1067
|
|
|
986
1068
|
def _on_scene_contents_changed(self, _changes) -> None:
|
|
987
1069
|
scene = self.scene()
|
|
@@ -1414,12 +1496,13 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
1414
1496
|
self.fitInView(padded, QtCore.Qt.AspectRatioMode.KeepAspectRatio)
|
|
1415
1497
|
self.centerOn(self._page_item)
|
|
1416
1498
|
|
|
1417
|
-
def clear_canvas(self):
|
|
1418
|
-
"""Remove all items from the scene."""
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1499
|
+
def clear_canvas(self):
|
|
1500
|
+
"""Remove all items from the scene."""
|
|
1501
|
+
self._clear_hover_preview()
|
|
1502
|
+
scene = self.scene()
|
|
1503
|
+
for item in list(scene.items()):
|
|
1504
|
+
if isinstance(item, A4PageItem):
|
|
1505
|
+
continue
|
|
1423
1506
|
if item.__class__.__name__.endswith("Handle"):
|
|
1424
1507
|
continue
|
|
1425
1508
|
if item.parentItem() is not None:
|
|
@@ -1435,13 +1518,109 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
1435
1518
|
return
|
|
1436
1519
|
self._ensure_pages_for_items(scene.items())
|
|
1437
1520
|
|
|
1438
|
-
def drawBackground(self, painter: QtGui.QPainter, rect: QtCore.QRectF):
|
|
1439
|
-
super().drawBackground(painter, rect)
|
|
1440
|
-
|
|
1441
|
-
def
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1521
|
+
def drawBackground(self, painter: QtGui.QPainter, rect: QtCore.QRectF):
|
|
1522
|
+
super().drawBackground(painter, rect)
|
|
1523
|
+
|
|
1524
|
+
def drawForeground(self, painter: QtGui.QPainter, rect: QtCore.QRectF) -> None:
|
|
1525
|
+
super().drawForeground(painter, rect)
|
|
1526
|
+
|
|
1527
|
+
del rect
|
|
1528
|
+
|
|
1529
|
+
strength = max(0.0, float(HOVER_PREVIEW_STRENGTH))
|
|
1530
|
+
marker_count = max(0, int(HOVER_PREVIEW_X_COUNT))
|
|
1531
|
+
if strength <= 0.0 or marker_count <= 0:
|
|
1532
|
+
return
|
|
1533
|
+
|
|
1534
|
+
item = self._hover_preview_item
|
|
1535
|
+
scene = self.scene()
|
|
1536
|
+
if item is None or scene is None:
|
|
1537
|
+
return
|
|
1538
|
+
|
|
1539
|
+
try:
|
|
1540
|
+
if item.scene() is not scene:
|
|
1541
|
+
self._hover_preview_item = None
|
|
1542
|
+
return
|
|
1543
|
+
except RuntimeError:
|
|
1544
|
+
self._hover_preview_item = None
|
|
1545
|
+
return
|
|
1546
|
+
|
|
1547
|
+
corners_poly = item.mapToScene(item.boundingRect())
|
|
1548
|
+
if len(corners_poly) < 4:
|
|
1549
|
+
return
|
|
1550
|
+
c0 = QtCore.QPointF(corners_poly[0])
|
|
1551
|
+
c1 = QtCore.QPointF(corners_poly[1])
|
|
1552
|
+
c2 = QtCore.QPointF(corners_poly[2])
|
|
1553
|
+
c3 = QtCore.QPointF(corners_poly[3])
|
|
1554
|
+
|
|
1555
|
+
def lerp(start: QtCore.QPointF, end: QtCore.QPointF, t: float) -> QtCore.QPointF:
|
|
1556
|
+
return QtCore.QPointF(
|
|
1557
|
+
start.x() + (end.x() - start.x()) * t,
|
|
1558
|
+
start.y() + (end.y() - start.y()) * t,
|
|
1559
|
+
)
|
|
1560
|
+
|
|
1561
|
+
transform = painter.worldTransform()
|
|
1562
|
+
sx = math.hypot(transform.m11(), transform.m21())
|
|
1563
|
+
sy = math.hypot(transform.m12(), transform.m22())
|
|
1564
|
+
lod = max((sx + sy) * 0.5, 1e-6)
|
|
1565
|
+
strength_for_size = max(0.5, strength)
|
|
1566
|
+
marker_half = 3.0 * strength_for_size / lod
|
|
1567
|
+
|
|
1568
|
+
base_color = QtGui.QColor(HOVER_PREVIEW_COLOR)
|
|
1569
|
+
if not base_color.isValid():
|
|
1570
|
+
base_color = QtGui.QColor("#14b5ff")
|
|
1571
|
+
outline_color = QtGui.QColor(base_color)
|
|
1572
|
+
outline_color.setAlpha(max(0, min(255, int(round(125 * strength)))))
|
|
1573
|
+
marker_color = QtGui.QColor(base_color)
|
|
1574
|
+
marker_color.setAlpha(max(0, min(255, int(round(220 * strength)))))
|
|
1575
|
+
|
|
1576
|
+
painter.save()
|
|
1577
|
+
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True)
|
|
1578
|
+
|
|
1579
|
+
outline_pen = QtGui.QPen(outline_color, max(0.6, 1.0 * strength_for_size))
|
|
1580
|
+
outline_pen.setCosmetic(True)
|
|
1581
|
+
painter.setPen(outline_pen)
|
|
1582
|
+
painter.setBrush(QtCore.Qt.BrushStyle.NoBrush)
|
|
1583
|
+
painter.drawPolygon(QtGui.QPolygonF([c0, c1, c2, c3]))
|
|
1584
|
+
|
|
1585
|
+
marker_pen = QtGui.QPen(marker_color, max(0.7, 1.3 * strength_for_size))
|
|
1586
|
+
marker_pen.setCosmetic(True)
|
|
1587
|
+
painter.setPen(marker_pen)
|
|
1588
|
+
|
|
1589
|
+
edges = ((c0, c1), (c1, c2), (c2, c3), (c3, c0))
|
|
1590
|
+
edge_lengths = [QtCore.QLineF(start, end).length() for start, end in edges]
|
|
1591
|
+
perimeter = sum(edge_lengths)
|
|
1592
|
+
if perimeter <= 1e-6:
|
|
1593
|
+
painter.restore()
|
|
1594
|
+
return
|
|
1595
|
+
|
|
1596
|
+
points: list[QtCore.QPointF] = []
|
|
1597
|
+
for idx in range(marker_count):
|
|
1598
|
+
distance = perimeter * (idx / marker_count)
|
|
1599
|
+
remaining = distance
|
|
1600
|
+
for edge_index, edge_length in enumerate(edge_lengths):
|
|
1601
|
+
if remaining <= edge_length or edge_index == len(edge_lengths) - 1:
|
|
1602
|
+
start, end = edges[edge_index]
|
|
1603
|
+
t = 0.0 if edge_length <= 1e-6 else remaining / edge_length
|
|
1604
|
+
points.append(lerp(start, end, t))
|
|
1605
|
+
break
|
|
1606
|
+
remaining -= edge_length
|
|
1607
|
+
|
|
1608
|
+
for point in points:
|
|
1609
|
+
painter.drawLine(
|
|
1610
|
+
QtCore.QPointF(point.x() - marker_half, point.y() - marker_half),
|
|
1611
|
+
QtCore.QPointF(point.x() + marker_half, point.y() + marker_half),
|
|
1612
|
+
)
|
|
1613
|
+
painter.drawLine(
|
|
1614
|
+
QtCore.QPointF(point.x() - marker_half, point.y() + marker_half),
|
|
1615
|
+
QtCore.QPointF(point.x() + marker_half, point.y() - marker_half),
|
|
1616
|
+
)
|
|
1617
|
+
|
|
1618
|
+
painter.restore()
|
|
1619
|
+
|
|
1620
|
+
def set_grid_visible(self, visible: bool):
|
|
1621
|
+
self._show_grid = visible
|
|
1622
|
+
for page in self._pages.values():
|
|
1623
|
+
page.set_grid_visible(visible)
|
|
1445
1624
|
self.viewport().update()
|
|
1446
1625
|
if hasattr(self, "_history"):
|
|
1447
1626
|
self._history.mark_dirty()
|
|
@@ -1531,12 +1710,16 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
1531
1710
|
else:
|
|
1532
1711
|
return None
|
|
1533
1712
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1713
|
+
scene = self.scene()
|
|
1714
|
+
if scene is None:
|
|
1715
|
+
return None
|
|
1716
|
+
item.setData(0, normalized)
|
|
1717
|
+
scene.addItem(item)
|
|
1718
|
+
scene.clearSelection()
|
|
1719
|
+
item.setSelected(True)
|
|
1720
|
+
self._ensure_page_for_item(item, drop_reference)
|
|
1721
|
+
self._update_scene_rect()
|
|
1722
|
+
return item
|
|
1540
1723
|
|
|
1541
1724
|
def add_shape_at_view_center(self, shape: str) -> QtWidgets.QGraphicsItem | None:
|
|
1542
1725
|
normalized = shape.strip()
|
|
@@ -1635,13 +1818,15 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
1635
1818
|
return
|
|
1636
1819
|
super().mousePressEvent(event)
|
|
1637
1820
|
|
|
1638
|
-
def mouseMoveEvent(self, event: QtGui.QMouseEvent):
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
self.viewport().setCursor(QtCore.Qt.CursorShape.
|
|
1821
|
+
def mouseMoveEvent(self, event: QtGui.QMouseEvent):
|
|
1822
|
+
hover_target = self._hover_preview_target_at(event.position().toPoint())
|
|
1823
|
+
self._set_hover_preview_item(hover_target)
|
|
1824
|
+
|
|
1825
|
+
item = self.itemAt(event.position().toPoint())
|
|
1826
|
+
if item and item.flags() & QtWidgets.QGraphicsItem.GraphicsItemFlag.ItemIsMovable:
|
|
1827
|
+
self.viewport().setCursor(QtCore.Qt.CursorShape.SizeAllCursor)
|
|
1828
|
+
else:
|
|
1829
|
+
self.viewport().setCursor(QtCore.Qt.CursorShape.ArrowCursor)
|
|
1645
1830
|
|
|
1646
1831
|
if self._panning or self._right_button_pressed:
|
|
1647
1832
|
delta = event.position() - self._pan_start
|
|
@@ -1655,20 +1840,22 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
1655
1840
|
self.viewport().setCursor(
|
|
1656
1841
|
QtCore.Qt.CursorShape.ClosedHandCursor
|
|
1657
1842
|
)
|
|
1658
|
-
if self._panning:
|
|
1659
|
-
self.
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1843
|
+
if self._panning:
|
|
1844
|
+
self._clear_hover_preview()
|
|
1845
|
+
self._pan_start = event.position()
|
|
1846
|
+
hbar = self.horizontalScrollBar()
|
|
1847
|
+
vbar = self.verticalScrollBar()
|
|
1848
|
+
hbar.setValue(hbar.value() - int(delta.x()))
|
|
1849
|
+
vbar.setValue(vbar.value() - int(delta.y()))
|
|
1664
1850
|
event.accept()
|
|
1665
1851
|
return
|
|
1666
|
-
if getattr(self, "_dup_source", None):
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1852
|
+
if getattr(self, "_dup_source", None):
|
|
1853
|
+
self._clear_hover_preview()
|
|
1854
|
+
pos = self.mapToScene(event.position().toPoint())
|
|
1855
|
+
delta = pos - self._dup_start
|
|
1856
|
+
if self._dup_items is None:
|
|
1857
|
+
# Only create clones after surpassing the threshold.
|
|
1858
|
+
if delta.manhattanLength() < DUPLICATE_DRAG_THRESHOLD:
|
|
1672
1859
|
event.accept()
|
|
1673
1860
|
return
|
|
1674
1861
|
self._dup_items = []
|
|
@@ -1684,9 +1871,13 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
1684
1871
|
it.setSelected(True)
|
|
1685
1872
|
for it, start in zip(self._dup_items, self._dup_orig):
|
|
1686
1873
|
it.setPos(start + delta)
|
|
1687
|
-
event.accept()
|
|
1688
|
-
return
|
|
1689
|
-
super().mouseMoveEvent(event)
|
|
1874
|
+
event.accept()
|
|
1875
|
+
return
|
|
1876
|
+
super().mouseMoveEvent(event)
|
|
1877
|
+
|
|
1878
|
+
def leaveEvent(self, event: QtCore.QEvent) -> None:
|
|
1879
|
+
self._clear_hover_preview()
|
|
1880
|
+
super().leaveEvent(event)
|
|
1690
1881
|
|
|
1691
1882
|
def mouseReleaseEvent(self, event: QtGui.QMouseEvent):
|
|
1692
1883
|
if event.button() == QtCore.Qt.MouseButton.RightButton:
|
|
@@ -2506,9 +2697,10 @@ class CanvasView(QtWidgets.QGraphicsView):
|
|
|
2506
2697
|
items[idx + 1], items[idx] = items[idx], items[idx + 1]
|
|
2507
2698
|
elif action == back_act:
|
|
2508
2699
|
items.insert(0, items.pop(idx))
|
|
2509
|
-
elif action == front_act:
|
|
2510
|
-
items.append(items.pop(idx))
|
|
2511
|
-
for z, it in enumerate(items):
|
|
2512
|
-
it.setZValue(z)
|
|
2513
|
-
|
|
2514
|
-
|
|
2700
|
+
elif action == front_act:
|
|
2701
|
+
items.append(items.pop(idx))
|
|
2702
|
+
for z, it in enumerate(items):
|
|
2703
|
+
it.setZValue(z)
|
|
2704
|
+
self._history.mark_dirty()
|
|
2705
|
+
else:
|
|
2706
|
+
super().contextMenuEvent(event)
|
constants.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# drawsvg-ui
|
|
2
|
-
# Copyright (C) 2025 Andreas Wambold
|
|
3
|
-
#
|
|
4
|
-
# This program is free software: you can redistribute it and/or modify
|
|
5
|
-
# it under the terms of the GNU General Public License as published by
|
|
6
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
# (at your option) any later version.
|
|
8
|
-
|
|
9
|
-
from PySide6 import QtCore, QtGui
|
|
10
|
-
|
|
11
|
-
PALETTE_MIME = "application/x-drawsvg-shape"
|
|
12
|
-
SHAPES = (
|
|
13
|
-
"Rectangle",
|
|
1
|
+
# drawsvg-ui
|
|
2
|
+
# Copyright (C) 2025 Andreas Wambold
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
from PySide6 import QtCore, QtGui
|
|
10
|
+
|
|
11
|
+
PALETTE_MIME = "application/x-drawsvg-shape"
|
|
12
|
+
SHAPES = (
|
|
13
|
+
"Rectangle",
|
|
14
14
|
"Rounded Rectangle",
|
|
15
15
|
"Split Rounded Rectangle",
|
|
16
16
|
"Ellipse",
|
|
@@ -46,8 +46,15 @@ SELECTED_COLOR = QtGui.QColor("#16CCFA")
|
|
|
46
46
|
PEN_SELECTED = QtGui.QPen(SELECTED_COLOR, 1, QtCore.Qt.PenStyle.DashLine)
|
|
47
47
|
PEN_SELECTED.setCosmetic(True)
|
|
48
48
|
DEFAULT_FILL = QtGui.QBrush(QtCore.Qt.white)
|
|
49
|
-
DEFAULT_TEXT_COLOR = QtGui.QColor("#000")
|
|
50
|
-
DEFAULT_FONT_FAMILY = "Arial"
|
|
49
|
+
DEFAULT_TEXT_COLOR = QtGui.QColor("#000")
|
|
50
|
+
DEFAULT_FONT_FAMILY = "Arial"
|
|
51
|
+
|
|
52
|
+
# Hover preview for selectable items in the canvas.
|
|
53
|
+
# Increase/decrease strength for a stronger/weaker effect.
|
|
54
|
+
HOVER_PREVIEW_STRENGTH = 0.8
|
|
55
|
+
# Total number of X markers drawn around the hovered item's border.
|
|
56
|
+
HOVER_PREVIEW_X_COUNT = 16
|
|
57
|
+
HOVER_PREVIEW_COLOR = QtGui.QColor("#14b5ff")
|
|
51
58
|
|
|
52
59
|
# Default dash patterns used when exporting/importing common pen styles.
|
|
53
60
|
PEN_STYLE_DASH_ARRAYS = {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
app_info.py,sha256=4tXMbEUlznyx0D-e6yj8kHOduMIq0zXQJpCGwpy3kQw,2059
|
|
2
|
-
canvas_view.py,sha256=
|
|
3
|
-
constants.py,sha256=
|
|
4
|
-
export_drawsvg.py,sha256=
|
|
5
|
-
import_drawsvg.py,sha256
|
|
2
|
+
canvas_view.py,sha256=ZmMyn1vikqZGG1oVTGST-uqHm-5PWZwbUnOsUUYeB1A,107421
|
|
3
|
+
constants.py,sha256=L_NKMRuFqbCvg0GpgnJDWPL1dCXbYLdyS2mJbKb2EyU,2181
|
|
4
|
+
export_drawsvg.py,sha256=EzZYg56cEDu19fjr2-PKMBnQsZzD0BraRpz3073GaYI,41282
|
|
5
|
+
import_drawsvg.py,sha256=Sa-x6Kcio9PP0ny2XteXr6V6Qv_zYASGurdyAW7aiJY,38069
|
|
6
6
|
main.py,sha256=qTDUCGJIXqO_sztIhAMU958i5RiPyiRgsFN09IevKxE,860
|
|
7
7
|
main_window.py,sha256=Wq0MehrW00Z-xor0BDftSfluA2YncRME2jzVeNmYi00,9948
|
|
8
8
|
palette.py,sha256=H9b8ZE3ufNupusfwOr0wP3razDEA29HLJYDfmadLLKs,21942
|
|
9
9
|
properties_panel.py,sha256=j_uyn6mqFwR7_nR1seJwnPjYWl_Axj8IUF0DcfPrObg,53661
|
|
10
|
-
drawsvg_ui-0.
|
|
10
|
+
drawsvg_ui-0.6.0.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
|
11
11
|
items/__init__.py,sha256=2xvoyRx2GpE7scPvD0zFKZZ43zumfR0rsD7WyFrkMg0,1770
|
|
12
12
|
items/base.py,sha256=RPkfjRdbRtlN98ZtLG1U8HYI7Eb6embSct_kG8d7FWs,23164
|
|
13
13
|
items/labels.py,sha256=N_qR0mq-eDFdxsnhNbkgsijnogceFOqSbhuRJwB4XTU,10102
|
|
@@ -22,8 +22,8 @@ items/widgets/folder_tree.py,sha256=E3XiIAIa6rpBlr6dmgcluMQcrT8BmxXTidilVGYqhUs,
|
|
|
22
22
|
ui/__init__.py,sha256=dfQXeMdwX6Jn8VwHpWY0sqwhl3bcOsYq13nAWhMCPFg,357
|
|
23
23
|
ui/main_window.ui,sha256=OmnMIUq4T1YjP1iVMGTUd-L8VKsCgLbCHIjx_PgbBXw,4346
|
|
24
24
|
ui/properties_panel.ui,sha256=v0zNct6PZBuFzhmYTPyx-toLAtJP2eFzRAUAVTsYHSA,35679
|
|
25
|
-
drawsvg_ui-0.
|
|
26
|
-
drawsvg_ui-0.
|
|
27
|
-
drawsvg_ui-0.
|
|
28
|
-
drawsvg_ui-0.
|
|
29
|
-
drawsvg_ui-0.
|
|
25
|
+
drawsvg_ui-0.6.0.dist-info/METADATA,sha256=S6_QvYtfOR0E1lv79f0Z3WGfhhA2bweWlVOfbs930Os,5756
|
|
26
|
+
drawsvg_ui-0.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
27
|
+
drawsvg_ui-0.6.0.dist-info/entry_points.txt,sha256=SUUEwp3n44haFICb66Jv9gO8_jabgTXOUqC-FUHnpk0,37
|
|
28
|
+
drawsvg_ui-0.6.0.dist-info/top_level.txt,sha256=gGj1MC4gg01_Ye4LlevrdAMIIfPJgOUi7PD1TCVE0sk,112
|
|
29
|
+
drawsvg_ui-0.6.0.dist-info/RECORD,,
|
export_drawsvg.py
CHANGED
|
@@ -1463,16 +1463,27 @@ def export_drawsvg_py(scene: QtWidgets.QGraphicsScene, parent: QtWidgets.QWidget
|
|
|
1463
1463
|
except Exception:
|
|
1464
1464
|
text_dir = None
|
|
1465
1465
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1466
|
+
text_left = x_top + doc_margin * s
|
|
1467
|
+
text_right = x_top + br.width() * s - doc_margin * s
|
|
1468
|
+
text_center = (text_left + text_right) / 2.0
|
|
1469
|
+
if h_align == "right":
|
|
1470
|
+
text_anchor = "end"
|
|
1471
|
+
text_x = text_right
|
|
1472
|
+
elif h_align == "center":
|
|
1473
|
+
text_anchor = "middle"
|
|
1474
|
+
text_x = text_center
|
|
1475
|
+
else:
|
|
1476
|
+
text_anchor = "start"
|
|
1477
|
+
text_x = text_left
|
|
1478
|
+
text_y = y_top + doc_margin * s
|
|
1479
|
+
|
|
1480
|
+
base_attrs = [
|
|
1481
|
+
f"fill='{color.name()}'",
|
|
1482
|
+
f"font_family='{font.family()}'",
|
|
1483
|
+
f"text_anchor='{text_anchor}'",
|
|
1484
|
+
"dominant_baseline='text-before-edge'",
|
|
1485
|
+
"alignment_baseline='text-before-edge'",
|
|
1486
|
+
f"line_height={line_ratio:.6f}",
|
|
1476
1487
|
"xml__space='preserve'",
|
|
1477
1488
|
f"data_doc_margin={doc_margin:.4f}",
|
|
1478
1489
|
f"data_font_px={pixel_size:.4f}",
|
import_drawsvg.py
CHANGED
|
@@ -786,10 +786,20 @@ def import_drawsvg_py(scene: QtWidgets.QGraphicsScene, parent: QtWidgets.QWidget
|
|
|
786
786
|
except Exception:
|
|
787
787
|
pass
|
|
788
788
|
|
|
789
|
-
doc_margin_scene = doc_margin * scale_factor
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
789
|
+
doc_margin_scene = doc_margin * scale_factor
|
|
790
|
+
box_w_scene = (
|
|
791
|
+
float(box_w) * scale_factor
|
|
792
|
+
if box_w is not None
|
|
793
|
+
else item.boundingRect().width() * scale_factor
|
|
794
|
+
)
|
|
795
|
+
if data_text_h == "right":
|
|
796
|
+
x_pos = text_x - (box_w_scene - doc_margin_scene)
|
|
797
|
+
elif data_text_h == "center":
|
|
798
|
+
x_pos = text_x - box_w_scene / 2.0
|
|
799
|
+
else:
|
|
800
|
+
x_pos = text_x - doc_margin_scene
|
|
801
|
+
y_pos = text_y - doc_margin_scene
|
|
802
|
+
item.setPos(x_pos, y_pos)
|
|
793
803
|
br = item.boundingRect()
|
|
794
804
|
item.setTransformOriginPoint(br.width() / 2.0, br.height() / 2.0)
|
|
795
805
|
if "transform" in kwargs:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|