Merge settings window to main #23
@@ -0,0 +1,165 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property color color: DynamicColors.palette.m3secondary
|
||||||
|
required property string icon
|
||||||
|
property bool initialized: false
|
||||||
|
readonly property bool isHorizontal: orientation === Qt.Horizontal
|
||||||
|
readonly property bool isVertical: orientation === Qt.Vertical
|
||||||
|
property real multiplier: 100
|
||||||
|
property real oldValue
|
||||||
|
|
||||||
|
// Wrapper components can inject their own track visuals here.
|
||||||
|
property Component trackContent
|
||||||
|
|
||||||
|
// Keep current behavior for existing usages.
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
|
background: CustomRect {
|
||||||
|
id: groove
|
||||||
|
|
||||||
|
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
|
||||||
|
height: root.availableHeight
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
width: root.availableWidth
|
||||||
|
x: root.leftPadding
|
||||||
|
y: root.topPadding
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: trackLoader
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceComponent: root.trackContent
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
if (!item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.rootSlider = root;
|
||||||
|
item.groove = groove;
|
||||||
|
item.handleItem = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle: Item {
|
||||||
|
id: handle
|
||||||
|
|
||||||
|
property alias moving: icon.moving
|
||||||
|
|
||||||
|
implicitHeight: Math.min(root.width, root.height)
|
||||||
|
implicitWidth: Math.min(root.width, root.height)
|
||||||
|
x: root.isHorizontal ? root.leftPadding + root.visualPosition * (root.availableWidth - width) : root.leftPadding + (root.availableWidth - width) / 2
|
||||||
|
y: root.isVertical ? root.topPadding + root.visualPosition * (root.availableHeight - height) : root.topPadding + (root.availableHeight - height) / 2
|
||||||
|
|
||||||
|
Elevation {
|
||||||
|
anchors.fill: parent
|
||||||
|
level: handleInteraction.containsMouse ? 2 : 1
|
||||||
|
radius: rect.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: rect
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
color: DynamicColors.palette.m3inverseSurface
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: handleInteraction
|
||||||
|
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
id: icon
|
||||||
|
|
||||||
|
property bool moving
|
||||||
|
|
||||||
|
function update(): void {
|
||||||
|
animate = !moving;
|
||||||
|
binding.when = moving;
|
||||||
|
font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger;
|
||||||
|
font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: DynamicColors.palette.m3inverseOnSurface
|
||||||
|
text: root.icon
|
||||||
|
|
||||||
|
onMovingChanged: anim.restart()
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
id: binding
|
||||||
|
|
||||||
|
property: "text"
|
||||||
|
target: icon
|
||||||
|
value: Math.round(root.value * root.multiplier)
|
||||||
|
when: false
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: anim
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.normal / 2
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||||
|
property: "scale"
|
||||||
|
target: icon
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptAction {
|
||||||
|
script: icon.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.normal / 2
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||||
|
property: "scale"
|
||||||
|
target: icon
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on value {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressedChanged: handle.moving = pressed
|
||||||
|
onValueChanged: {
|
||||||
|
if (!initialized) {
|
||||||
|
initialized = true;
|
||||||
|
oldValue = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(value - oldValue) < 0.01)
|
||||||
|
return;
|
||||||
|
|
||||||
|
oldValue = value;
|
||||||
|
handle.moving = true;
|
||||||
|
stateChangeDelay.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: stateChangeDelay
|
||||||
|
|
||||||
|
interval: 500
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
if (!root.pressed)
|
||||||
|
handle.moving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -8,11 +9,18 @@ Item {
|
|||||||
readonly property real arcStartAngle: 0.75 * Math.PI
|
readonly property real arcStartAngle: 0.75 * Math.PI
|
||||||
readonly property real arcSweep: 1.5 * Math.PI
|
readonly property real arcSweep: 1.5 * Math.PI
|
||||||
property real currentHue: 0
|
property real currentHue: 0
|
||||||
|
property bool dragActive: false
|
||||||
required property var drawing
|
required property var drawing
|
||||||
property real handleSize: 30
|
readonly property real handleAngle: hueToAngle(currentHue)
|
||||||
|
readonly property real handleCenterX: width / 2 + radius * Math.cos(handleAngle)
|
||||||
|
readonly property real handleCenterY: height / 2 + radius * Math.sin(handleAngle)
|
||||||
|
property real handleSize: 32
|
||||||
property real lastChromaticHue: 0
|
property real lastChromaticHue: 0
|
||||||
property real ringThickness: 12
|
readonly property real radius: (Math.min(width, height) - handleSize) / 2
|
||||||
readonly property int segmentCount: 180
|
readonly property int segmentCount: 240
|
||||||
|
readonly property color thumbColor: DynamicColors.palette.m3inverseSurface
|
||||||
|
readonly property color thumbContentColor: DynamicColors.palette.m3inverseOnSurface
|
||||||
|
readonly property color trackColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
|
||||||
|
|
||||||
function hueToAngle(hue) {
|
function hueToAngle(hue) {
|
||||||
return arcStartAngle + arcSweep * hue;
|
return arcStartAngle + arcSweep * hue;
|
||||||
@@ -26,18 +34,25 @@ Item {
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pointIsOnTrack(x, y) {
|
||||||
|
const cx = width / 2;
|
||||||
|
const cy = height / 2;
|
||||||
|
const dx = x - cx;
|
||||||
|
const dy = y - cy;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
return distance >= radius - handleSize / 2 && distance <= radius + handleSize / 2;
|
||||||
|
}
|
||||||
|
|
||||||
function syncFromPenColor() {
|
function syncFromPenColor() {
|
||||||
if (!drawing)
|
if (!drawing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const c = drawing.penColor;
|
const c = drawing.penColor;
|
||||||
|
|
||||||
// QML color exposes HSL channels directly.
|
if (c.hsvSaturation > 0) {
|
||||||
// If the current color is chromatic, move the handle to that hue.
|
currentHue = c.hsvHue;
|
||||||
// If it is achromatic (black/white/gray), keep the last useful hue.
|
lastChromaticHue = c.hsvHue;
|
||||||
if (c.hslSaturation > 0) {
|
|
||||||
currentHue = c.hslHue;
|
|
||||||
lastChromaticHue = c.hslHue;
|
|
||||||
} else {
|
} else {
|
||||||
currentHue = lastChromaticHue;
|
currentHue = lastChromaticHue;
|
||||||
}
|
}
|
||||||
@@ -45,16 +60,15 @@ Item {
|
|||||||
canvas.requestPaint();
|
canvas.requestPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHueFromPoint(x, y) {
|
function updateHueFromPoint(x, y, force = false) {
|
||||||
const cx = canvas.width / 2;
|
const cx = width / 2;
|
||||||
const cy = canvas.height / 2;
|
const cy = height / 2;
|
||||||
const dx = x - cx;
|
const dx = x - cx;
|
||||||
const dy = y - cy;
|
const dy = y - cy;
|
||||||
|
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
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)
|
if (!force && (distance < radius - handleSize / 2 || distance > radius + handleSize / 2))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const angle = normalizeAngle(Math.atan2(dy, dx));
|
const angle = normalizeAngle(Math.atan2(dy, dx));
|
||||||
@@ -71,7 +85,7 @@ Item {
|
|||||||
|
|
||||||
currentHue = relative / arcSweep;
|
currentHue = relative / arcSweep;
|
||||||
lastChromaticHue = currentHue;
|
lastChromaticHue = currentHue;
|
||||||
drawing.penColor = Qt.hsla(currentHue, 1.0, 0.5, 1.0);
|
drawing.penColor = Qt.hsva(currentHue, drawing.penColor.hsvSaturation, drawing.penColor.hsvValue, drawing.penColor.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: 180
|
implicitHeight: 180
|
||||||
@@ -80,6 +94,9 @@ Item {
|
|||||||
Component.onCompleted: syncFromPenColor()
|
Component.onCompleted: syncFromPenColor()
|
||||||
onCurrentHueChanged: canvas.requestPaint()
|
onCurrentHueChanged: canvas.requestPaint()
|
||||||
onDrawingChanged: syncFromPenColor()
|
onDrawingChanged: syncFromPenColor()
|
||||||
|
onHandleSizeChanged: canvas.requestPaint()
|
||||||
|
onHeightChanged: canvas.requestPaint()
|
||||||
|
onWidthChanged: canvas.requestPaint()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onPenColorChanged() {
|
function onPenColorChanged() {
|
||||||
@@ -97,7 +114,6 @@ Item {
|
|||||||
renderTarget: Canvas.Image
|
renderTarget: Canvas.Image
|
||||||
|
|
||||||
Component.onCompleted: requestPaint()
|
Component.onCompleted: requestPaint()
|
||||||
onHeightChanged: requestPaint()
|
|
||||||
onPaint: {
|
onPaint: {
|
||||||
const ctx = getContext("2d");
|
const ctx = getContext("2d");
|
||||||
ctx.reset();
|
ctx.reset();
|
||||||
@@ -105,15 +121,10 @@ Item {
|
|||||||
|
|
||||||
const cx = width / 2;
|
const cx = width / 2;
|
||||||
const cy = height / 2;
|
const cy = height / 2;
|
||||||
const radius = (Math.min(width, height) - root.handleSize - 8) / 2;
|
const radius = root.radius;
|
||||||
|
const trackWidth = root.handleSize;
|
||||||
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();
|
|
||||||
|
|
||||||
|
// Background track: always show the full hue spectrum
|
||||||
for (let i = 0; i < root.segmentCount; ++i) {
|
for (let i = 0; i < root.segmentCount; ++i) {
|
||||||
const t1 = i / root.segmentCount;
|
const t1 = i / root.segmentCount;
|
||||||
const t2 = (i + 1) / root.segmentCount;
|
const t2 = (i + 1) / root.segmentCount;
|
||||||
@@ -122,49 +133,76 @@ Item {
|
|||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, radius, a1, a2);
|
ctx.arc(cx, cy, radius, a1, a2);
|
||||||
ctx.lineWidth = root.ringThickness;
|
ctx.lineWidth = trackWidth;
|
||||||
ctx.lineCap = "round";
|
ctx.lineCap = "round";
|
||||||
ctx.strokeStyle = Qt.hsla(t1, 1.0, 0.5, 1.0);
|
ctx.strokeStyle = Qt.hsla(t1, 1.0, 0.5, 1.0);
|
||||||
ctx.stroke();
|
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()
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: handle
|
||||||
|
|
||||||
|
height: root.handleSize
|
||||||
|
width: root.handleSize
|
||||||
|
x: root.handleCenterX - width / 2
|
||||||
|
y: root.handleCenterY - height / 2
|
||||||
|
z: 1
|
||||||
|
|
||||||
|
Elevation {
|
||||||
|
anchors.fill: parent
|
||||||
|
level: handleHover.containsMouse ? 2 : 1
|
||||||
|
radius: rect.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: rect
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
color: root.thumbColor
|
||||||
|
radius: width / 2
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: handleHover
|
||||||
|
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
hoverEnabled: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
color: root.drawing ? root.drawing.penColor : Qt.hsla(root.currentHue, 1.0, 0.5, 1.0)
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
width: parent.width - 12
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
onCanceled: {
|
||||||
|
root.dragActive = false;
|
||||||
|
}
|
||||||
onPositionChanged: mouse => {
|
onPositionChanged: mouse => {
|
||||||
if (mouse.buttons & Qt.LeftButton)
|
if ((mouse.buttons & Qt.LeftButton) && root.dragActive)
|
||||||
|
root.updateHueFromPoint(mouse.x, mouse.y, true);
|
||||||
|
}
|
||||||
|
onPressed: mouse => {
|
||||||
|
root.dragActive = root.pointIsOnTrack(mouse.x, mouse.y);
|
||||||
|
if (root.dragActive)
|
||||||
root.updateHueFromPoint(mouse.x, mouse.y);
|
root.updateHueFromPoint(mouse.x, mouse.y);
|
||||||
}
|
}
|
||||||
onPressed: mouse => root.updateHueFromPoint(mouse.x, mouse.y)
|
onReleased: {
|
||||||
|
root.dragActive = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-128
@@ -1,141 +1,29 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Templates
|
|
||||||
import qs.Config
|
import qs.Config
|
||||||
|
|
||||||
Slider {
|
BaseStyledSlider {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property color color: DynamicColors.palette.m3secondary
|
trackContent: Component {
|
||||||
required property string icon
|
Item {
|
||||||
property bool initialized
|
property var groove
|
||||||
property real oldValue
|
readonly property real handleHeight: handleItem ? handleItem.height : 0
|
||||||
|
property var handleItem
|
||||||
|
readonly property real handleWidth: handleItem ? handleItem.width : 0
|
||||||
|
|
||||||
orientation: Qt.Vertical
|
// Set by BaseStyledSlider's Loader
|
||||||
|
property var rootSlider
|
||||||
|
|
||||||
background: CustomRect {
|
anchors.fill: parent
|
||||||
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
anchors.left: parent.left
|
color: rootSlider?.color
|
||||||
anchors.right: parent.right
|
height: rootSlider?.isVertical ? handleHeight + (1 - rootSlider?.visualPosition) * (groove?.height - handleHeight) : groove?.height
|
||||||
color: root.color
|
radius: groove?.radius
|
||||||
implicitHeight: parent.height - y
|
width: rootSlider?.isHorizontal ? handleWidth + rootSlider?.visualPosition * (groove?.width - handleWidth) : groove?.width
|
||||||
radius: parent.radius
|
x: rootSlider?.isHorizontal ? (rootSlider?.mirrored ? groove?.width - width : 0) : 0
|
||||||
y: root.handle.y
|
y: rootSlider?.isVertical ? groove?.height - height : 0
|
||||||
}
|
|
||||||
}
|
|
||||||
handle: Item {
|
|
||||||
id: handle
|
|
||||||
|
|
||||||
property alias moving: icon.moving
|
|
||||||
|
|
||||||
implicitHeight: root.width
|
|
||||||
implicitWidth: root.width
|
|
||||||
y: root.visualPosition * (root.availableHeight - height)
|
|
||||||
|
|
||||||
Elevation {
|
|
||||||
anchors.fill: parent
|
|
||||||
level: handleInteraction.containsMouse ? 2 : 1
|
|
||||||
radius: rect.radius
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomRect {
|
|
||||||
id: rect
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
color: DynamicColors.palette.m3inverseSurface
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: handleInteraction
|
|
||||||
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
hoverEnabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
id: icon
|
|
||||||
|
|
||||||
property bool moving
|
|
||||||
|
|
||||||
function update(): void {
|
|
||||||
animate = !moving;
|
|
||||||
binding.when = moving;
|
|
||||||
font.pointSize = moving ? Appearance.font.size.small : Appearance.font.size.larger;
|
|
||||||
font.family = moving ? Appearance.font.family.sans : Appearance.font.family.material;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: DynamicColors.palette.m3inverseOnSurface
|
|
||||||
text: root.icon
|
|
||||||
|
|
||||||
onMovingChanged: anim.restart()
|
|
||||||
|
|
||||||
Binding {
|
|
||||||
id: binding
|
|
||||||
|
|
||||||
property: "text"
|
|
||||||
target: icon
|
|
||||||
value: Math.round(root.value * 100)
|
|
||||||
when: false
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
id: anim
|
|
||||||
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.normal / 2
|
|
||||||
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
|
||||||
property: "scale"
|
|
||||||
target: icon
|
|
||||||
to: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptAction {
|
|
||||||
script: icon.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.normal / 2
|
|
||||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
|
||||||
property: "scale"
|
|
||||||
target: icon
|
|
||||||
to: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Behavior on value {
|
|
||||||
Anim {
|
|
||||||
duration: Appearance.anim.durations.large
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressedChanged: handle.moving = pressed
|
|
||||||
onValueChanged: {
|
|
||||||
if (!initialized) {
|
|
||||||
initialized = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Math.abs(value - oldValue) < 0.01)
|
|
||||||
return;
|
|
||||||
oldValue = value;
|
|
||||||
handle.moving = true;
|
|
||||||
stateChangeDelay.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: stateChangeDelay
|
|
||||||
|
|
||||||
interval: 500
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.pressed)
|
|
||||||
handle.moving = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
BaseStyledSlider {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real alpha: 1.0
|
||||||
|
property real brightness: 1.0
|
||||||
|
property string channel: "saturation"
|
||||||
|
readonly property color currentColor: Qt.hsva(hue, channel === "saturation" ? value : saturation, channel === "brightness" ? value : brightness, alpha)
|
||||||
|
property real hue: 0.0
|
||||||
|
property real saturation: 1.0
|
||||||
|
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
|
||||||
|
trackContent: Component {
|
||||||
|
Item {
|
||||||
|
property var groove
|
||||||
|
property var handleItem
|
||||||
|
property var rootSlider
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
antialiasing: true
|
||||||
|
color: "transparent"
|
||||||
|
radius: groove?.radius ?? 0
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: rootSlider?.isHorizontal ? Gradient.Horizontal : Gradient.Vertical
|
||||||
|
|
||||||
|
GradientStop {
|
||||||
|
color: root.channel === "saturation" ? Qt.hsva(root.hue, 0.0, root.brightness, root.alpha) : Qt.hsva(root.hue, root.saturation, 0.0, root.alpha)
|
||||||
|
position: 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientStop {
|
||||||
|
color: root.channel === "saturation" ? Qt.hsva(root.hue, 1.0, root.brightness, root.alpha) : Qt.hsva(root.hue, root.saturation, 1.0, root.alpha)
|
||||||
|
position: 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+92
-17
@@ -12,10 +12,40 @@ Item {
|
|||||||
required property Canvas drawing
|
required property Canvas drawing
|
||||||
required property var visibilities
|
required property var visibilities
|
||||||
|
|
||||||
implicitHeight: huePicker.implicitHeight + 12 + palette.implicitHeight + Appearance.padding.normal * 2
|
function syncFromPenColor() {
|
||||||
|
if (!drawing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!saturationSlider.pressed)
|
||||||
|
saturationSlider.value = drawing.penColor.hsvSaturation;
|
||||||
|
|
||||||
|
if (!brightnessSlider.pressed)
|
||||||
|
brightnessSlider.value = drawing.penColor.hsvValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePenColorFromHsv() {
|
||||||
|
if (!drawing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
drawing.penColor = Qt.hsva(huePicker.currentHue, saturationSlider.value, brightnessSlider.value, drawing.penColor.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: column.height + Appearance.padding.larger * 2
|
||||||
implicitWidth: huePicker.implicitWidth + Appearance.padding.normal * 2
|
implicitWidth: huePicker.implicitWidth + Appearance.padding.normal * 2
|
||||||
|
|
||||||
|
Component.onCompleted: syncFromPenColor()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
function onPenColorChanged() {
|
||||||
|
root.syncFromPenColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
target: root.drawing
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: column
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
@@ -25,6 +55,38 @@ Item {
|
|||||||
drawing: root.drawing
|
drawing: root.drawing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GradientSlider {
|
||||||
|
id: saturationSlider
|
||||||
|
|
||||||
|
brightness: brightnessSlider.value
|
||||||
|
channel: "saturation"
|
||||||
|
from: 0
|
||||||
|
hue: huePicker.currentHue
|
||||||
|
icon: "\ue40a"
|
||||||
|
implicitHeight: 30
|
||||||
|
implicitWidth: palette.width
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
to: 1
|
||||||
|
|
||||||
|
onMoved: root.updatePenColorFromHsv()
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientSlider {
|
||||||
|
id: brightnessSlider
|
||||||
|
|
||||||
|
channel: "brightness"
|
||||||
|
from: 0
|
||||||
|
hue: huePicker.currentHue
|
||||||
|
icon: "\ue1ac"
|
||||||
|
implicitHeight: 30
|
||||||
|
implicitWidth: palette.width
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
saturation: saturationSlider.value
|
||||||
|
to: 1
|
||||||
|
|
||||||
|
onMoved: root.updatePenColorFromHsv()
|
||||||
|
}
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id: palette
|
id: palette
|
||||||
|
|
||||||
@@ -38,12 +100,24 @@ Item {
|
|||||||
model: root.colors
|
model: root.colors
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
|
id: colorCircle
|
||||||
|
|
||||||
required property color modelData
|
required property color modelData
|
||||||
readonly property bool selected: Qt.colorEqual(root.drawing.penColor, modelData)
|
readonly property bool selected: Qt.colorEqual(root.drawing.penColor, modelData)
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: 28
|
height: 28
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
border.color: Qt.rgba(0, 0, 0, 0.25)
|
||||||
|
border.width: Qt.colorEqual(modelData, "#ffffff") ? 1 : 0
|
||||||
|
color: colorCircle.modelData
|
||||||
|
height: 20
|
||||||
|
radius: width / 2
|
||||||
|
width: 20
|
||||||
|
}
|
||||||
|
|
||||||
CustomRect {
|
CustomRect {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
border.color: selected ? "#ffffff" : Qt.rgba(1, 1, 1, 0.28)
|
border.color: selected ? "#ffffff" : Qt.rgba(1, 1, 1, 0.28)
|
||||||
@@ -52,25 +126,26 @@ Item {
|
|||||||
height: parent.height
|
height: parent.height
|
||||||
radius: width / 2
|
radius: width / 2
|
||||||
width: parent.height
|
width: parent.height
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
onClicked: root.drawing.penColor = colorCircle.modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomRect {
|
FilledSlider {
|
||||||
anchors.centerIn: parent
|
from: 1
|
||||||
border.color: Qt.rgba(0, 0, 0, 0.25)
|
icon: "border_color"
|
||||||
border.width: Qt.colorEqual(modelData, "#ffffff") ? 1 : 0
|
implicitHeight: 30
|
||||||
color: modelData
|
implicitWidth: palette.width
|
||||||
height: 20
|
multiplier: 1
|
||||||
radius: width / 2
|
orientation: Qt.Horizontal
|
||||||
width: 20
|
to: 45
|
||||||
}
|
value: root.drawing.penWidth
|
||||||
|
|
||||||
MouseArea {
|
onMoved: root.drawing.penWidth = value
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onClicked: root.drawing.penColor = parent.modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user