screenshot tools baked-in: drawing uses optimized qcanvaspainter, direct to clipboard, eliminate forking to swappy
C++ / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 21s
JS/TS / fmt (pull_request) Successful in 20s
Python / lint (pull_request) Successful in 31s
Python / fmt (pull_request) Successful in 33s
Python / typecheck (pull_request) Failing after 1m4s
Python / test (pull_request) Successful in 1m7s
C++ / build (pull_request) Successful in 1m48s
Rust / fmt (pull_request) Successful in 37s
Rust / build (pull_request) Successful in 1m42s
Python / buildcheck (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m32s
C++ / clang-tidy (pull_request) Successful in 3m35s
C++ / fmt (pull_request) Successful in 7s
JS/TS / lint (pull_request) Successful in 21s
JS/TS / fmt (pull_request) Successful in 20s
Python / lint (pull_request) Successful in 31s
Python / fmt (pull_request) Successful in 33s
Python / typecheck (pull_request) Failing after 1m4s
Python / test (pull_request) Successful in 1m7s
C++ / build (pull_request) Successful in 1m48s
Rust / fmt (pull_request) Successful in 37s
Rust / build (pull_request) Successful in 1m42s
Python / buildcheck (pull_request) Successful in 2m33s
Rust / clippy (pull_request) Successful in 1m32s
C++ / clang-tidy (pull_request) Successful in 3m35s
This commit is contained in:
Executable
+143
@@ -0,0 +1,143 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import ZShell.Internal
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color inkColor: "#ff3b30"
|
||||
property real inkWidth: 4
|
||||
property string tool: "none"
|
||||
|
||||
signal changed
|
||||
|
||||
function clearAll() {
|
||||
committedCanvas.clear();
|
||||
previewCanvas.clear();
|
||||
changed();
|
||||
}
|
||||
|
||||
function sampleShape(tool, x1, y1, x2, y2) {
|
||||
const pts = [];
|
||||
switch (tool) {
|
||||
case "line":
|
||||
pts.push([x1, y1], [x2, y2]);
|
||||
break;
|
||||
case "arrow":
|
||||
{
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
const headLen = 14 + root.inkWidth * 2;
|
||||
pts.push([x1, y1], [x2, y2]);
|
||||
pts.push([x2 - headLen * Math.cos(angle - Math.PI / 6), y2 - headLen * Math.sin(angle - Math.PI / 6)]);
|
||||
pts.push([x2, y2]);
|
||||
pts.push([x2 - headLen * Math.cos(angle + Math.PI / 6), y2 - headLen * Math.sin(angle + Math.PI / 6)]);
|
||||
break;
|
||||
}
|
||||
case "rect":
|
||||
pts.push([x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]);
|
||||
break;
|
||||
case "ellipse":
|
||||
{
|
||||
const cx = (x1 + x2) / 2, cy = (y1 + y2) / 2;
|
||||
const rx = Math.abs(x2 - x1) / 2, ry = Math.abs(y2 - y1) / 2;
|
||||
const perimeter = Math.PI * (3 * (rx + ry) - Math.sqrt((3 * rx + ry) * (rx + 3 * ry)));
|
||||
const steps = Math.max(48, Math.min(256, Math.ceil(perimeter / 4)));
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const a = (i / steps) * Math.PI * 2;
|
||||
pts.push([cx + rx * Math.cos(a), cy + ry * Math.sin(a)]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
function undo() {
|
||||
committedCanvas.undoLastGroup();
|
||||
changed();
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
StrokeCanvas {
|
||||
id: committedCanvas
|
||||
|
||||
anchors.fill: parent
|
||||
penColor: root.inkColor
|
||||
penWidth: root.inkWidth
|
||||
}
|
||||
|
||||
StrokeCanvas {
|
||||
id: previewCanvas
|
||||
|
||||
anchors.fill: parent
|
||||
penColor: root.inkColor
|
||||
penWidth: root.inkWidth
|
||||
}
|
||||
|
||||
PointHandler {
|
||||
id: drawHandler
|
||||
|
||||
property real startX
|
||||
property real startY
|
||||
property bool started: false
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
enabled: root.tool !== "none"
|
||||
|
||||
onActiveChanged: {
|
||||
if (active)
|
||||
return;
|
||||
|
||||
if (root.tool === "pen") {
|
||||
committedCanvas.endStroke();
|
||||
} 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;
|
||||
|
||||
if (point.pressedButtons & Qt.RightButton) {
|
||||
root.clearAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const x = point.position.x;
|
||||
const y = point.position.y;
|
||||
|
||||
if (root.tool === "pen") {
|
||||
if (!started) {
|
||||
started = true;
|
||||
committedCanvas.beginStroke(x, y);
|
||||
return;
|
||||
}
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import ZShell.Components
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
readonly property var colorPalette: ["#ff3b30", "#ff9500", "#ffcc00", "#34c759", "#0a84ff", "#af52de", "#ffffff", "#000000"]
|
||||
property color inkColor: "#ff3b30"
|
||||
property string tool: "move"
|
||||
readonly property var toolList: [
|
||||
{
|
||||
id: "move",
|
||||
glyph: "arrows_output"
|
||||
},
|
||||
{
|
||||
id: "pen",
|
||||
glyph: "draw"
|
||||
},
|
||||
{
|
||||
id: "line",
|
||||
glyph: "diagonal_line"
|
||||
},
|
||||
{
|
||||
id: "arrow",
|
||||
glyph: "arrow_outward"
|
||||
},
|
||||
{
|
||||
id: "rect",
|
||||
glyph: "rectangle"
|
||||
},
|
||||
{
|
||||
id: "ellipse",
|
||||
glyph: "circle"
|
||||
}
|
||||
]
|
||||
|
||||
signal cancelRequested
|
||||
signal clearRequested
|
||||
signal colorSelected(color c)
|
||||
signal copyRequested
|
||||
signal saveRequested
|
||||
signal toolSelected(string tool)
|
||||
signal undoRequested
|
||||
|
||||
color: DynamicColors.palette.m3surface
|
||||
height: row.implicitHeight + Appearance.padding.normal * 2
|
||||
radius: Appearance.rounding.full
|
||||
width: row.implicitWidth + Appearance.padding.normal * 2
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: 6
|
||||
|
||||
ButtonRow {
|
||||
id: toolRow
|
||||
|
||||
spacing: Appearance.spacing.extraSmall
|
||||
|
||||
Repeater {
|
||||
model: root.toolList
|
||||
|
||||
delegate: IconButton {
|
||||
id: tool
|
||||
|
||||
required property var modelData
|
||||
|
||||
color: root.tool === modelData.id ? "#3584e4" : "transparent"
|
||||
icon: tool.modelData.glyph
|
||||
inactiveColor: root.tool === modelData.id ? DynamicColors.palette.m3primary : "transparent"
|
||||
inactiveOnColor: root.tool === modelData.id ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: shapeMorph && pressed ? 12 : 0
|
||||
|
||||
onClicked: root.toolSelected(tool.modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 1
|
||||
color: DynamicColors.palette.m3outlineVariant
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
spacing: Appearance.spacing.extraSmall
|
||||
|
||||
Repeater {
|
||||
model: root.colorPalette
|
||||
|
||||
delegate: IconButton {
|
||||
readonly property real buttonSize: toolRow.implicitHeight - Appearance.padding.normal
|
||||
required property color modelData
|
||||
|
||||
fillWidth: false
|
||||
font.pointSize: 0
|
||||
icon: ""
|
||||
implicitHeight: buttonSize
|
||||
implicitWidth: buttonSize
|
||||
inactiveColor: modelData
|
||||
inactiveOnColor: DynamicColors.on(modelData)
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.colorSelected(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 1
|
||||
color: DynamicColors.palette.m3outlineVariant
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
spacing: Appearance.spacing.extraSmall
|
||||
|
||||
IconButton {
|
||||
icon: "undo"
|
||||
inactiveColor: "transparent"
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.undoRequested()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "delete_history"
|
||||
inactiveColor: "transparent"
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.clearRequested()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "content_copy"
|
||||
inactiveColor: "transparent"
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.copyRequested()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "delete_forever"
|
||||
inactiveColor: "transparent"
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.cancelRequested()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "check"
|
||||
inactiveColor: "transparent"
|
||||
inactiveOnColor: DynamicColors.palette.m3onSurface
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
|
||||
onClicked: root.saveRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+183
@@ -0,0 +1,183 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color handleBorder: "#3584e4"
|
||||
property color handleFill: "white"
|
||||
property real hs: 12
|
||||
property real minSize: 24
|
||||
required property real selH
|
||||
required property real selW
|
||||
required property real selX
|
||||
required property real selY
|
||||
|
||||
signal boundsChanged(x: real, y: real, w: real, h: real)
|
||||
signal dragFinished
|
||||
signal moveBy(dx: real, dy: real)
|
||||
|
||||
height: selH
|
||||
width: selW
|
||||
x: selX
|
||||
y: selY
|
||||
|
||||
DragHandler {
|
||||
id: moveHandler
|
||||
|
||||
property real accumX: 0
|
||||
property real accumY: 0
|
||||
|
||||
target: null
|
||||
|
||||
onActiveChanged: {
|
||||
accumX = 0;
|
||||
accumY = 0;
|
||||
if (!active)
|
||||
root.dragFinished();
|
||||
}
|
||||
onTranslationChanged: {
|
||||
const t = moveHandler.translation;
|
||||
root.moveBy(t.x - accumX, t.y - accumY);
|
||||
accumX = t.x;
|
||||
accumY = t.y;
|
||||
}
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "tl"
|
||||
x: -root.hs / 2
|
||||
y: -root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "t"
|
||||
x: root.width / 2 - root.hs / 2
|
||||
y: -root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "tr"
|
||||
x: root.width - root.hs / 2
|
||||
y: -root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "r"
|
||||
x: root.width - root.hs / 2
|
||||
y: root.height / 2 - root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "br"
|
||||
x: root.width - root.hs / 2
|
||||
y: root.height - root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "b"
|
||||
x: root.width / 2 - root.hs / 2
|
||||
y: root.height - root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "bl"
|
||||
x: -root.hs / 2
|
||||
y: root.height - root.hs / 2
|
||||
}
|
||||
|
||||
Handle {
|
||||
edge: "l"
|
||||
x: -root.hs / 2
|
||||
y: root.height / 2 - root.hs / 2
|
||||
}
|
||||
|
||||
component Handle: Rectangle {
|
||||
id: handle
|
||||
|
||||
required property string edge
|
||||
|
||||
border.color: root.handleBorder
|
||||
border.width: 2
|
||||
color: root.handleFill
|
||||
height: root.hs
|
||||
radius: root.hs / 2
|
||||
width: root.hs
|
||||
z: 10
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: {
|
||||
switch (handle.edge) {
|
||||
case "tl":
|
||||
case "br":
|
||||
return Qt.SizeFDiagCursor;
|
||||
case "tr":
|
||||
case "bl":
|
||||
return Qt.SizeBDiagCursor;
|
||||
case "t":
|
||||
case "b":
|
||||
return Qt.SizeVerCursor;
|
||||
default:
|
||||
return Qt.SizeHorCursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: dh
|
||||
|
||||
property real startH
|
||||
property real startW
|
||||
property real startX
|
||||
property real startY
|
||||
|
||||
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.TakeOverForbidden
|
||||
target: null
|
||||
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
startX = root.x;
|
||||
startY = root.y;
|
||||
startW = root.width;
|
||||
startH = root.height;
|
||||
} else {
|
||||
root.dragFinished();
|
||||
}
|
||||
}
|
||||
onTranslationChanged: {
|
||||
const delta = dh.translation;
|
||||
const e = handle.edge;
|
||||
let nx = startX, ny = startY, nw = startW, nh = startH;
|
||||
|
||||
if (e.includes("l")) {
|
||||
nx = startX + delta.x;
|
||||
nw = startW - delta.x;
|
||||
}
|
||||
if (e.includes("r")) {
|
||||
nw = startW + delta.x;
|
||||
}
|
||||
if (e.includes("t")) {
|
||||
ny = startY + delta.y;
|
||||
nh = startH - delta.y;
|
||||
}
|
||||
if (e.includes("b")) {
|
||||
nh = startH + delta.y;
|
||||
}
|
||||
|
||||
if (nw < root.minSize) {
|
||||
if (e.includes("l"))
|
||||
nx = startX + startW - root.minSize;
|
||||
nw = root.minSize;
|
||||
}
|
||||
if (nh < root.minSize) {
|
||||
if (e.includes("t"))
|
||||
ny = startY + startH - root.minSize;
|
||||
nh = root.minSize;
|
||||
}
|
||||
|
||||
root.boundsChanged(nx, ny, nw, nh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user