171 lines
4.1 KiB
QML
171 lines
4.1 KiB
QML
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)
|
|
}
|
|
}
|