This commit is contained in:
Zacharias-Brohn
2026-03-09 12:47:09 +01:00
parent 720bc2808e
commit 1ee345f946
11 changed files with 762 additions and 115 deletions
+170
View File
@@ -0,0 +1,170 @@
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI
property real currentHue: 0
required property var drawing
property real handleSize: 30
property real lastChromaticHue: 0
property real ringThickness: 12
readonly property int segmentCount: 180
function hueToAngle(hue) {
return arcStartAngle + arcSweep * hue;
}
function normalizeAngle(angle) {
const tau = Math.PI * 2;
let a = angle % tau;
if (a < 0)
a += tau;
return a;
}
function syncFromPenColor() {
if (!drawing)
return;
const c = drawing.penColor;
// QML color exposes HSL channels directly.
// If the current color is chromatic, move the handle to that hue.
// If it is achromatic (black/white/gray), keep the last useful hue.
if (c.hslSaturation > 0) {
currentHue = c.hslHue;
lastChromaticHue = c.hslHue;
} else {
currentHue = lastChromaticHue;
}
canvas.requestPaint();
}
function updateHueFromPoint(x, y) {
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const dx = x - cx;
const dy = y - cy;
const distance = Math.sqrt(dx * dx + dy * dy);
const radius = (Math.min(canvas.width, canvas.height) - handleSize - 8) / 2;
if (distance < radius - 24 || distance > radius + 24)
return;
const angle = normalizeAngle(Math.atan2(dy, dx));
const start = normalizeAngle(arcStartAngle);
let relative = angle - start;
if (relative < 0)
relative += Math.PI * 2;
if (relative > arcSweep) {
const gap = Math.PI * 2 - arcSweep;
relative = relative < arcSweep + gap / 2 ? arcSweep : 0;
}
currentHue = relative / arcSweep;
lastChromaticHue = currentHue;
drawing.penColor = Qt.hsla(currentHue, 1.0, 0.5, 1.0);
}
implicitHeight: 180
implicitWidth: 220
Component.onCompleted: syncFromPenColor()
onCurrentHueChanged: canvas.requestPaint()
onDrawingChanged: syncFromPenColor()
Connections {
function onPenColorChanged() {
root.syncFromPenColor();
}
target: root.drawing
}
Canvas {
id: canvas
anchors.fill: parent
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
Component.onCompleted: requestPaint()
onHeightChanged: requestPaint()
onPaint: {
const ctx = getContext("2d");
ctx.reset();
ctx.clearRect(0, 0, width, height);
const cx = width / 2;
const cy = height / 2;
const radius = (Math.min(width, height) - root.handleSize - 8) / 2;
ctx.beginPath();
ctx.arc(cx, cy, radius, root.arcStartAngle, root.arcStartAngle + root.arcSweep);
ctx.lineWidth = root.ringThickness + 4;
ctx.lineCap = "round";
ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.12);
ctx.stroke();
for (let i = 0; i < root.segmentCount; ++i) {
const t1 = i / root.segmentCount;
const t2 = (i + 1) / root.segmentCount;
const a1 = root.arcStartAngle + root.arcSweep * t1;
const a2 = root.arcStartAngle + root.arcSweep * t2;
ctx.beginPath();
ctx.arc(cx, cy, radius, a1, a2);
ctx.lineWidth = root.ringThickness;
ctx.lineCap = "round";
ctx.strokeStyle = Qt.hsla(t1, 1.0, 0.5, 1.0);
ctx.stroke();
}
const handleAngle = root.hueToAngle(root.currentHue);
const hx = cx + radius * Math.cos(handleAngle);
const hy = cy + radius * Math.sin(handleAngle);
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2, 0, Math.PI * 2);
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.95);
ctx.fill();
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2, 0, Math.PI * 2);
ctx.lineWidth = 1.5;
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.18);
ctx.stroke();
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2 - 6, 0, Math.PI * 2);
ctx.fillStyle = root.drawing.penColor;
ctx.fill();
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2 - 6, 0, Math.PI * 2);
ctx.lineWidth = 1;
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.20);
ctx.stroke();
}
onWidthChanged: requestPaint()
}
MouseArea {
acceptedButtons: Qt.LeftButton
anchors.fill: parent
onPositionChanged: mouse => {
if (mouse.buttons & Qt.LeftButton)
root.updateHueFromPoint(mouse.x, mouse.y);
}
onPressed: mouse => root.updateHueFromPoint(mouse.x, mouse.y)
}
}