fix: merge two strokecanvas into one + better cursorShape and pointer handling
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 8s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 47s
Python / verify (pull_request) Successful in 1m34s
Rust / fmt (pull_request) Successful in 35s
Rust / clippy (pull_request) Successful in 1m0s

This commit is contained in:
2026-10-04 13:53:21 +02:00
parent 506b69165d
commit f1918172c3
4 changed files with 110 additions and 86 deletions
+13 -12
View File
@@ -129,22 +129,22 @@ Item {
Behavior on rsx { Behavior on rsx {
enabled: !selectArea.pressed && root.mode === "select" enabled: !selectArea.pressed && root.mode === "select"
ExAnim {} Anim {}
} }
Behavior on rsy { Behavior on rsy {
enabled: !selectArea.pressed && root.mode === "select" enabled: !selectArea.pressed && root.mode === "select"
ExAnim {} Anim {}
} }
Behavior on sh { Behavior on sh {
enabled: !selectArea.pressed && root.mode === "select" enabled: !selectArea.pressed && root.mode === "select"
ExAnim {} Anim {}
} }
Behavior on sw { Behavior on sw {
enabled: !selectArea.pressed && root.mode === "select" enabled: !selectArea.pressed && root.mode === "select"
ExAnim {} Anim {}
} }
Behavior on opacity { Behavior on opacity {
Anim { Anim {
@@ -236,19 +236,19 @@ Item {
to: 0 to: 0
} }
ExAnim { Anim {
properties: "rsx,rsy" properties: "rsx,rsy"
target: root target: root
to: 0 to: 0
} }
ExAnim { Anim {
property: "sw" property: "sw"
target: root target: root
to: root.screen.width to: root.screen.width
} }
ExAnim { Anim {
property: "sh" property: "sh"
target: root target: root
to: root.screen.height to: root.screen.height
@@ -286,6 +286,7 @@ Item {
anchors.fill: parent anchors.fill: parent
inkColor: root.inkColor inkColor: root.inkColor
layer.enabled: true layer.enabled: true
selection: selectionRect
tool: root.mode === "edit" && root.tool !== "move" ? root.tool : "none" tool: root.mode === "edit" && root.tool !== "move" ? root.tool : "none"
layer.effect: MultiEffect { layer.effect: MultiEffect {
@@ -297,7 +298,7 @@ Item {
} }
} }
Rectangle { CustomRect {
id: overlay id: overlay
anchors.fill: parent anchors.fill: parent
@@ -334,12 +335,11 @@ Item {
} }
} }
Rectangle { CustomRect {
id: border id: border
border.color: Colors.palette.m3primary border.color: Colors.palette.m3primary
border.width: root.realBorderWidth border.width: root.realBorderWidth
color: "transparent"
implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2 implicitHeight: selectionRect.implicitHeight + root.realBorderWidth * 2
implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2 implicitWidth: selectionRect.implicitWidth + root.realBorderWidth * 2
radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0 radius: root.realRounding > 0 ? root.realRounding + root.realBorderWidth : 0
@@ -355,10 +355,13 @@ Item {
SelectionHandles { SelectionHandles {
id: handles id: handles
border: root.realBorderWidth
selH: root.sh selH: root.sh
selW: root.sw selW: root.sw
selX: root.rsx selX: root.rsx
selY: root.rsy selY: root.rsy
selection: selectionRect
tool: root.tool
visible: root.mode === "edit" && root.tool === "move" visible: root.mode === "edit" && root.tool === "move"
onBoundsChanged: (x, y, w, h) => { onBoundsChanged: (x, y, w, h) => {
@@ -396,6 +399,4 @@ Item {
onToolSelected: t => root.tool = t onToolSelected: t => root.tool = t
onUndoRequested: annotations.undo() onUndoRequested: annotations.undo()
} }
component ExAnim: Anim {}
} }
+48 -55
View File
@@ -8,13 +8,13 @@ Item {
property color inkColor: "#ff3b30" property color inkColor: "#ff3b30"
property real inkWidth: 4 property real inkWidth: 4
required property Rectangle selection
property string tool: "none" property string tool: "none"
signal changed signal changed
function clearAll() { function clearAll() {
committedCanvas.clear(); canvas.clear();
previewCanvas.clear();
changed(); changed();
} }
@@ -54,89 +54,82 @@ Item {
} }
function undo() { function undo() {
committedCanvas.undoLastGroup(); canvas.undoLastGroup();
changed(); changed();
} }
anchors.fill: parent anchors.fill: parent
StrokeCanvas { StrokeCanvas {
id: committedCanvas id: canvas
anchors.fill: parent anchors.fill: parent
penColor: root.inkColor penColor: root.inkColor
penWidth: root.inkWidth penWidth: root.inkWidth
} }
StrokeCanvas { MouseArea {
id: previewCanvas
anchors.fill: parent
penColor: root.inkColor
penWidth: root.inkWidth
}
PointHandler {
id: drawHandler id: drawHandler
property real startX property real startX
property real startY property real startY
property bool started: false
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
cursorShape: undefined
enabled: root.tool !== "none" enabled: root.tool !== "none"
hoverEnabled: true
onActiveChanged: { onPositionChanged: e => {
if (active) const x = e.x;
const y = e.y;
canvas.hoverPoint = Qt.point(x, y);
if (!pressed)
return; return;
if (root.tool === "pen") { if (root.tool === "pen") {
committedCanvas.endStroke(); canvas.appendPoint(x, y);
} else if (started) {
const pts = root.sampleShape(root.tool, startX, startY, point.position.x, point.position.y);
if (pts.length > 0) {
committedCanvas.beginStroke(pts[0][0], pts[0][1], true);
for (let i = 1; i < pts.length; i++)
committedCanvas.appendPoint(pts[i][0], pts[i][1]);
committedCanvas.endStroke();
}
previewCanvas.clear();
}
started = false;
root.changed();
}
onPointChanged: {
if (!active)
return; return;
}
if (point.pressedButtons & Qt.RightButton) { const pts = root.sampleShape(root.tool, startX, startY, x, y);
root.clearAll(); if (pts.length > 0) {
return; canvas.beginStroke(pts[0][0], pts[0][1], true);
for (let i = 1; i < pts.length; i++)
canvas.appendPoint(pts[i][0], pts[i][1]);
} }
}
onPressed: e => {
if (e.button & Qt.LeftButton) {
startX = e.x;
startY = e.y;
canvas.beginStroke(e.x, e.y);
} else if (e.button & Qt.RightButton) {
root.clearAll();
}
}
onPressedChanged: if (!pressed)
canvas.endStroke()
}
HoverHandler {
cursorShape: {
if (root.tool === "none")
return undefined;
const x = point.position.x; const x = point.position.x;
const y = point.position.y; const y = point.position.y;
const withinXWidth = x > root.selection.x && x < root.selection.x + root.selection.implicitWidth;
const withinYHeight = y > root.selection.y && y < root.selection.y + root.selection.implicitHeight;
if (root.tool === "pen") { if (withinXWidth && withinYHeight) {
if (!started) { canvas.showHover(x, y);
started = true; return Qt.BlankCursor;
committedCanvas.beginStroke(x, y); } else {
return; canvas.hideHover();
} return undefined;
committedCanvas.appendPoint(x, y);
return;
}
if (!started) {
started = true;
startX = point.pressPosition.x;
startY = point.pressPosition.y;
}
const pts = root.sampleShape(root.tool, startX, startY, x, y);
if (pts.length > 0) {
previewCanvas.beginStroke(pts[0][0], pts[0][1], true);
for (let i = 1; i < pts.length; i++)
previewCanvas.appendPoint(pts[i][0], pts[i][1]);
} }
} }
} }
+28 -12
View File
@@ -5,6 +5,7 @@ import QtQuick
Item { Item {
id: root id: root
required property int border
property color handleBorder: "#3584e4" property color handleBorder: "#3584e4"
property color handleFill: "white" property color handleFill: "white"
property real hs: 12 property real hs: 12
@@ -13,6 +14,8 @@ Item {
required property real selW required property real selW
required property real selX required property real selX
required property real selY required property real selY
required property Rectangle selection
required property string tool
signal boundsChanged(x: real, y: real, w: real, h: real) signal boundsChanged(x: real, y: real, w: real, h: real)
signal dragFinished signal dragFinished
@@ -29,6 +32,7 @@ Item {
property real accumX: 0 property real accumX: 0
property real accumY: 0 property real accumY: 0
cursorShape: undefined
target: null target: null
onActiveChanged: { onActiveChanged: {
@@ -45,51 +49,63 @@ Item {
} }
} }
HoverHandler {
cursorShape: {
if (root.tool !== "move")
return undefined;
if (moveHandler.centroid.pressedButtons & Qt.LeftButton)
return Qt.ClosedHandCursor;
else
return Qt.OpenHandCursor;
}
}
Handle { Handle {
edge: "tl" edge: "tl"
x: -root.hs / 2 x: -(root.hs + root.border) / 2
y: -root.hs / 2 y: -(root.hs + root.border) / 2
} }
Handle { Handle {
edge: "t" edge: "t"
x: root.width / 2 - root.hs / 2 x: root.width / 2 - root.hs / 2
y: -root.hs / 2 y: -(root.hs + root.border) / 2
} }
Handle { Handle {
edge: "tr" edge: "tr"
x: root.width - root.hs / 2 x: root.width - root.hs / 2 + root.border / 2
y: -root.hs / 2 y: -(root.hs + root.border) / 2
} }
Handle { Handle {
edge: "r" edge: "r"
x: root.width - root.hs / 2 x: root.width - root.hs / 2 + root.border / 2
y: root.height / 2 - root.hs / 2 y: root.height / 2 - root.hs / 2
} }
Handle { Handle {
edge: "br" edge: "br"
x: root.width - root.hs / 2 x: root.width - root.hs / 2 + root.border / 2
y: root.height - root.hs / 2 y: root.height - root.hs / 2 + root.border / 2
} }
Handle { Handle {
edge: "b" edge: "b"
x: root.width / 2 - root.hs / 2 x: root.width / 2 - root.hs / 2
y: root.height - root.hs / 2 y: root.height - root.hs / 2 + root.border / 2
} }
Handle { Handle {
edge: "bl" edge: "bl"
x: -root.hs / 2 x: -(root.hs + root.border) / 2
y: root.height - root.hs / 2 y: root.height - root.hs / 2 + root.border / 2
} }
Handle { Handle {
edge: "l" edge: "l"
x: -root.hs / 2 x: -(root.hs + root.border) / 2
y: root.height / 2 - root.hs / 2 y: root.height / 2 - root.hs / 2
} }
@@ -1,6 +1,7 @@
#include "strokecanvasrenderer.hpp" #include "strokecanvasrenderer.hpp"
#include "strokecanvasitem.hpp" #include "strokecanvasitem.hpp"
#include <qcolor.h> #include <qcolor.h>
#include <QSet>
namespace ZShell::internal { namespace ZShell::internal {
@@ -148,14 +149,27 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
m_penColor = canvas->m_penColor; m_penColor = canvas->m_penColor;
m_penWidth = canvas->m_penWidth; m_penWidth = canvas->m_penWidth;
while (m_strokes.size() < canvas->m_strokes.size()) // Detect removed strokes: present in renderer but no longer in canvas
m_strokes.append(canvas->m_strokes[m_strokes.size()]); QSet<int> canvasGroupIds;
for (const auto& stroke : canvas->m_strokes)
canvasGroupIds.insert(stroke.groupId);
if (canvas->m_strokes.isEmpty() && !m_strokes.isEmpty()) { for (int i = m_strokes.size() - 1; i >= 0; --i) {
for (const auto& stroke : m_strokes) if (!canvasGroupIds.contains(m_strokes[i].groupId)) {
if (stroke.groupId >= 0) if (m_strokes[i].groupId >= 0)
m_pendingGroupRemovals.append(stroke.groupId); m_pendingGroupRemovals.append(m_strokes[i].groupId);
m_strokes.clear(); m_strokes.remove(i);
}
}
// Detect added strokes: present in canvas but not yet in renderer
QSet<int> rendererGroupIds;
for (const auto& stroke : m_strokes)
rendererGroupIds.insert(stroke.groupId);
for (const auto& stroke : canvas->m_strokes) {
if (!rendererGroupIds.contains(stroke.groupId))
m_strokes.append(stroke);
} }
m_currentStroke = canvas->m_currentStroke; m_currentStroke = canvas->m_currentStroke;