Added delay button
This commit is contained in:
+152
-1
@@ -8,6 +8,10 @@ Item {
|
||||
property bool showLabel: true
|
||||
property bool darkMode: settingsContainer.darkMode
|
||||
|
||||
// Per-button trigger config (from the model). Default: hold-to-activate.
|
||||
readonly property string triggerModeResolved: (typeof triggerMode !== "undefined" && triggerMode) ? triggerMode : "hold"
|
||||
readonly property int holdMsResolved: (typeof holdMs !== "undefined" && holdMs > 0) ? holdMs : 800
|
||||
|
||||
// Resolved feedback colour/width for the current status (ok / error).
|
||||
readonly property color okColor: settingsContainer.feedbackOkColor
|
||||
readonly property color errorColor: settingsContainer.feedbackErrorColor
|
||||
@@ -69,6 +73,14 @@ Item {
|
||||
Behavior on border.width { NumberAnimation { duration: 200 } }
|
||||
Behavior on border.color { ColorAnimation { duration: 200 } }
|
||||
|
||||
// Hold-to-activate state (0..1). Only used in "hold" trigger mode.
|
||||
property real holdProgress: 0.0
|
||||
property bool holding: false
|
||||
|
||||
// Squash slightly while held for tactile feedback.
|
||||
scale: holding ? 0.93 : 1.0
|
||||
Behavior on scale { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } }
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width * 0.8
|
||||
@@ -79,9 +91,148 @@ Item {
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
// Circular progress arc that fills up while the button is held.
|
||||
Canvas {
|
||||
id: holdRing
|
||||
anchors.fill: parent
|
||||
anchors.margins: -Math.max(3, width * 0.04)
|
||||
visible: circle.holdProgress > 0.001
|
||||
antialiasing: true
|
||||
renderStrategy: Canvas.Threaded
|
||||
// Ring colour derived from the button colour for a cohesive look.
|
||||
property color baseColor: Qt.lighter(btnColor || "#7b007b", 1.9)
|
||||
property color headColor: "#ffffff"
|
||||
function mix(c1, c2, t) {
|
||||
return Qt.rgba(c1.r + (c2.r - c1.r) * t,
|
||||
c1.g + (c2.g - c1.g) * t,
|
||||
c1.b + (c2.b - c1.b) * t,
|
||||
c1.a + (c2.a - c1.a) * t)
|
||||
}
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
var p = circle.holdProgress
|
||||
if (p <= 0) return
|
||||
var cx = width / 2
|
||||
var cy = height / 2
|
||||
var lw = Math.max(5, width * 0.085)
|
||||
var r = width / 2 - lw / 2 - 1
|
||||
var start = -Math.PI / 2
|
||||
var sweep = p * 2 * Math.PI
|
||||
var end = start + sweep
|
||||
|
||||
// Recessed track underneath for a sense of depth.
|
||||
ctx.beginPath()
|
||||
ctx.lineWidth = lw
|
||||
ctx.lineCap = "round"
|
||||
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.30)
|
||||
ctx.arc(cx, cy, r, 0, 2 * Math.PI, false)
|
||||
ctx.stroke()
|
||||
|
||||
// Progress arc drawn as overlapping segments with a manual
|
||||
// colour fade (dim tail -> bright head). Segment boundaries are
|
||||
// fixed in ANGLE space (not derived from progress) so they never
|
||||
// shift between frames -> no jitter. Opaque colours + overlap
|
||||
// hide the joints. Avoids the seam a conical gradient produces.
|
||||
var tail = Qt.darker(baseColor, 1.5) // opaque dim tail
|
||||
var N = 120
|
||||
var segAng = 2 * Math.PI / N
|
||||
var eps = Math.min(segAng * 0.5, 1.5 / r) // ~1px opaque overlap
|
||||
ctx.lineCap = "butt"
|
||||
ctx.lineWidth = lw
|
||||
for (var i = 0; i < N; i++) {
|
||||
var a0 = i * segAng
|
||||
if (a0 >= sweep) break
|
||||
var a1 = Math.min((i + 1) * segAng, sweep)
|
||||
var t = sweep > 0 ? (a0 / sweep) : 0 // 0..1 along progress
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = mix(tail, headColor, t)
|
||||
ctx.arc(cx, cy, r, start + a0 - eps, start + a1 + eps, false)
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Rounded tail cap.
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = tail
|
||||
ctx.arc(cx + r * Math.cos(start), cy + r * Math.sin(start),
|
||||
lw / 2, 0, 2 * Math.PI, false)
|
||||
ctx.fill()
|
||||
|
||||
// Bright rounded head for a glossy, raised feel.
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = headColor
|
||||
ctx.arc(cx + r * Math.cos(end), cy + r * Math.sin(end),
|
||||
lw * 0.55, 0, 2 * Math.PI, false)
|
||||
ctx.fill()
|
||||
}
|
||||
// Soft bloom around the ring for volume.
|
||||
layer.enabled: true
|
||||
layer.effect: Glow {
|
||||
radius: 14
|
||||
samples: 25
|
||||
spread: 0.2
|
||||
color: Qt.lighter(btnColor || "#7b007b", 1.7)
|
||||
transparentBorder: true
|
||||
}
|
||||
Connections {
|
||||
target: circle
|
||||
function onHoldProgressChanged() { holdRing.requestPaint() }
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: holdAnim
|
||||
target: circle
|
||||
property: "holdProgress"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: delegateRoot.holdMsResolved
|
||||
easing.type: Easing.Linear
|
||||
onFinished: {
|
||||
if (circle.holding) {
|
||||
circle.holding = false
|
||||
btnController.invokeButton(btnId)
|
||||
resetAnim.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberAnimation {
|
||||
id: resetAnim
|
||||
target: circle
|
||||
property: "holdProgress"
|
||||
to: 0.0
|
||||
duration: 220
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: btnController.invokeButton(btnId)
|
||||
onPressed: {
|
||||
if (delegateRoot.triggerModeResolved !== "click") {
|
||||
resetAnim.stop()
|
||||
circle.holding = true
|
||||
holdAnim.restart()
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
if (delegateRoot.triggerModeResolved !== "click" && circle.holding) {
|
||||
// Released too early — cancel and rewind.
|
||||
circle.holding = false
|
||||
holdAnim.stop()
|
||||
resetAnim.restart()
|
||||
}
|
||||
}
|
||||
onCanceled: {
|
||||
if (delegateRoot.triggerModeResolved !== "click") {
|
||||
circle.holding = false
|
||||
holdAnim.stop()
|
||||
resetAnim.restart()
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
if (delegateRoot.triggerModeResolved === "click")
|
||||
btnController.invokeButton(btnId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user