Make time picker animations better

This commit is contained in:
Stefano Moretti 2023-05-01 11:32:30 +02:00
parent 11606b8f39
commit 051e97c636
2 changed files with 83 additions and 24 deletions

View File

@ -10,7 +10,7 @@ Item {
readonly property string timeString: _zeroPad(hours) + ":" + _zeroPad(minutes) readonly property string timeString: _zeroPad(hours) + ":" + _zeroPad(minutes)
readonly property bool isPM: hours >= 12 readonly property bool isPM: hours >= 12
property bool pickMinutes: false readonly property alias pickMinutes: clock.pickMinutes
property bool time24h: false property bool time24h: false
property color clockColor: "gray" property color clockColor: "gray"
@ -25,6 +25,11 @@ Item {
property int labelsSize: 20 property int labelsSize: 20
property int clockHandCircleSize: 2 * labelsSize property int clockHandCircleSize: 2 * labelsSize
function setPickMinutes(pick) {
if (!animation.running)
clock.pickMinutes = pick
}
function update() { function update() {
circle.pos = circle.mapToItem(root.screen, 0, circle.height) circle.pos = circle.mapToItem(root.screen, 0, circle.height)
circle.pos.y = Window.height - circle.pos.y circle.pos.y = Window.height - circle.pos.y
@ -45,20 +50,45 @@ Item {
implicitHeight: implicitWidth implicitHeight: implicitWidth
onPickMinutesChanged: { onPickMinutesChanged: {
handAnimation.enabled = circleAnimation.enabled = true var minAngle = 360 / 60 * root.minutes
disableAnimationTimer.start() var hourAngle = 360 / 12 * (root.hours - (root.hours >= 12 ? 12 : 0))
var diff = minAngle - hourAngle
if (Math.abs(diff) <= 180) {
handToAnimation.to = handFromAnimation.from = (minAngle + hourAngle) / 2
} else {
handToAnimation.to = root.pickMinutes ? (diff >= 0 ? 0 : 360) : (diff >= 0 ? 360 : 0)
handFromAnimation.from = root.pickMinutes ? (diff >= 0 ? 360 : 0) : (diff >= 0 ? 0 : 360)
}
circleToAnimation.to = handToAnimation.to / 180 * Math.PI
circleFromAnimation.from = handFromAnimation.from / 180 * Math.PI
var toMiddleTime
var fromMiddleTime
if (root.pickMinutes) {
toMiddleTime = Math.abs(hourAngle - handToAnimation.to) / 180 * 400
fromMiddleTime = Math.abs(minAngle - handFromAnimation.from) / 180 * 400
} else {
toMiddleTime = Math.abs(minAngle - handToAnimation.to) / 180 * 400
fromMiddleTime = Math.abs(hourAngle - handFromAnimation.from) / 180 * 400
}
handToAnimation.duration = circleToAnimation.duration = toMiddleTime
handFromAnimation.duration = circleFromAnimation.duration = fromMiddleTime
animation.interval = Math.max(toMiddleTime + fromMiddleTime, 200)
animation.start()
} }
Timer { Timer {
id: disableAnimationTimer id: animation
interval: 400
repeat: false
onTriggered: handAnimation.enabled = circleAnimation.enabled = false
} }
Rectangle { Rectangle {
id: clock id: clock
property bool pickMinutes: false
width: Math.min(root.width, root.height) width: Math.min(root.width, root.height)
height: width height: width
radius: width / 2 radius: width / 2
@ -98,13 +128,14 @@ Item {
} }
} }
enabled: !animation.running
anchors.fill: parent anchors.fill: parent
pressAndHoldInterval: 100 pressAndHoldInterval: 100
onClicked: (mouse) => { selectTime(mouse, true); root.pickMinutes = true } onClicked: (mouse) => { selectTime(mouse, true); clock.pickMinutes = true }
onPositionChanged: (mouse) => { if (isHold) selectTime(mouse) } onPositionChanged: (mouse) => { if (isHold) selectTime(mouse) }
onPressAndHold: (mouse) => { isHold = true; selectTime(mouse) } onPressAndHold: (mouse) => { isHold = true; selectTime(mouse) }
onReleased: { if (isHold) { isHold = false; root.pickMinutes = true } } onReleased: { if (isHold) { isHold = false; clock.pickMinutes = true } }
} }
// clock hand // clock hand
@ -125,9 +156,15 @@ Item {
color: root.clockHandColor color: root.clockHandColor
antialiasing: true antialiasing: true
Behavior on rotation { Behavior on rotation {
id: handAnimation enabled: animation.running
enabled: false SequentialAnimation {
NumberAnimation { duration: 400 } NumberAnimation { id: handToAnimation }
NumberAnimation { id: handFromAnimation }
}
}
Behavior on height {
enabled: animation.running
NumberAnimation { duration: animation.interval }
} }
} }
@ -159,9 +196,11 @@ Item {
} }
Behavior on angle { Behavior on angle {
id: circleAnimation enabled: animation.running
enabled: false SequentialAnimation {
NumberAnimation { duration: 400 } NumberAnimation { id: circleToAnimation }
NumberAnimation { id: circleFromAnimation }
}
} }
} }
@ -188,9 +227,9 @@ Item {
font.pixelSize: root.labelsSize font.pixelSize: root.labelsSize
visible: root.time24h visible: root.time24h
opacity: root.pickMinutes ? 0 : 1 opacity: root.pickMinutes ? 0 : 1
color: root.labelsColor color: layer.enabled || modelData != root.hours ? root.labelsColor : root.labelsSelectedColor
text: modelData text: modelData
layer.enabled: true layer.enabled: root.labelsSelectedColor != root.labelsColor && animation.running
layer.samplerName: "maskSource" layer.samplerName: "maskSource"
layer.effect: shaderEffect layer.effect: shaderEffect
Behavior on opacity { NumberAnimation { duration: 200 } } Behavior on opacity { NumberAnimation { duration: 200 } }
@ -210,9 +249,24 @@ Item {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.pixelSize: root.labelsSize font.pixelSize: root.labelsSize
opacity: root.pickMinutes ? 0 : 1 opacity: root.pickMinutes ? 0 : 1
color: root.labelsColor color: {
if (!layer.enabled) {
if (root.time24h) {
if (modelData == root.hours)
return root.labelsSelectedColor
} else if (root.isPM) {
if (modelData == root.hours - 12
|| (modelData == 12 && root.hours == 12))
return root.labelsSelectedColor
} else if (modelData == root.hours
|| (modelData == 12 && root.hours == 0)) {
return root.labelsSelectedColor
}
}
return root.labelsColor
}
text: modelData text: modelData
layer.enabled: true layer.enabled: root.labelsSelectedColor != root.labelsColor && animation.running
layer.samplerName: "maskSource" layer.samplerName: "maskSource"
layer.effect: shaderEffect layer.effect: shaderEffect
Behavior on opacity { NumberAnimation { duration: 200 } } Behavior on opacity { NumberAnimation { duration: 200 } }
@ -233,9 +287,14 @@ Item {
font.pixelSize: root.labelsSize font.pixelSize: root.labelsSize
visible: modelData % 5 == 0 visible: modelData % 5 == 0
opacity: root.pickMinutes ? 1 : 0 opacity: root.pickMinutes ? 1 : 0
color: root.labelsColor color: animation.running || modelData != root.minutes
? root.labelsColor : root.labelsSelectedColor
text: _zeroPad(modelData) text: _zeroPad(modelData)
layer.enabled: true layer.enabled: animation.running
|| (root.labelsSelectedColor != root.labelsColor
&& root.minutes != modelData
&& (Math.abs(root.minutes - modelData) < 5
|| (modelData == 0 && root.minutes > 55)))
layer.samplerName: "maskSource" layer.samplerName: "maskSource"
layer.effect: shaderEffect layer.effect: shaderEffect
Behavior on opacity { NumberAnimation { duration: 200 } } Behavior on opacity { NumberAnimation { duration: 200 } }

View File

@ -32,7 +32,7 @@ Dialog {
focus: true focus: true
onOpened: timePicker.update() onOpened: timePicker.update()
onClosed: timePicker.pickMinutes = false onClosed: timePicker.setPickMinutes(false)
on_IsLandscapeChanged: updateTimer.restart() on_IsLandscapeChanged: updateTimer.restart()
@ -91,7 +91,7 @@ Dialog {
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: timePicker.pickMinutes = false onClicked: timePicker.setPickMinutes(false)
} }
} }
@ -111,7 +111,7 @@ Dialog {
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: timePicker.pickMinutes = true onClicked: timePicker.setPickMinutes(true)
} }
} }
} }