harbour-allradio/qml/items/ValuePicker.qml
2025-05-28 20:47:14 +02:00

176 lines
5.4 KiB
QML

import QtQuick 2.0
import Sailfish.Silica 1.0
// TODO: Investigate using one quarter of the image mirrored in two directions
// TODO: Investigate using a scaled down version of the image
Item {
id: valuePicker
property int value
property int max
property int min
property real _scaleRatio: valueCircle.width / 408
width: valueCircle.width
height: valueCircle.height
onValueChanged: {
value = (value < 1 ? 1 : (value > max ? max : value))
if (mouse.changingProperty == 0) {
var delta = (value - valueIndicator.value)
valueIndicator.value += (delta % (max - 1))
}
}
function _xTranslation(value, bound) {
// Use sine to map range of 0-bound to the X translation of a circular locus (-1 to 1)
return Math.sin((value % bound) / bound * Math.PI * 2)
}
function _yTranslation(value, bound) {
// Use cosine to map range of 0-bound to the Y translation of a circular locus (-1 to 1)
return Math.cos((value % bound) / bound * Math.PI * 2)
}
Image {
id: valueCircle
width: page.width * 0.75
height: width
source: "../images/timepicker.png"
opacity: 0.2
}
GlassItem {
id: valueIndicator
falloffRadius: 0.22
radius: 0.25
anchors.centerIn: valueCircle
color: mouse.changingProperty == 2 ? Theme.highlightColor : Theme.primaryColor
property real value
transform: Translate {
// The ss band is 72px wide, ending at 204px from the center
x: _scaleRatio*168 * _xTranslation(valueIndicator.value, max)
y: -_scaleRatio*168 * _yTranslation(valueIndicator.value, max)
}
Behavior on value {
id: valueAnimation
SmoothedAnimation { velocity: 80 }
enabled: !mouse.isMoving || mouse.isLagging
}
}
MouseArea {
id: mouse
property int changingProperty
property bool isMoving
property bool isLagging
anchors.fill: parent
preventStealing: true
function radiusForCoord(x, y) {
// Return the distance from the mouse position to the center
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
}
function angleForCoord(x, y) {
// Return the angular position in degrees, rising anticlockwise from the positive X-axis
var result = Math.atan(y / x) / (Math.PI * 2) * 360
// Adjust for various quadrants
if (x < 0) {
result += 180
} else if (y < 0) {
result += 360
}
return result
}
function remapAngle(value, bound) {
// Return the angle in degrees mapped to the adjusted range 0 - (bound-1) and
// translated to the clockwise from positive Y-axis orientation
return Math.round(bound - (((value - 90) / 360) * bound)) % bound
}
function remapMouse(mouseX, mouseY) {
// Return the mouse coordinates in cartesian coords relative to the circle center
return { x: mouseX - (width / 2), y: 0 - (mouseY - (height / 2)) }
}
function propertyForRadius(radius) {
// Return the property associated with clicking at radius distance from the center
if (radius < mouse.width / 2) {
return 2 // Minutes
}
return 0
}
function updateForAngle(angle) {
// Update the selected property for the specified angular position
// Minutes
// Map angular position to 0-59
var m = remapAngle(angle, max)
// Round single touch to the nearest 5 min mark
if (!isMoving) m = (Math.round(m/5) * 5) % (max - 1)
var delta = (m - valueIndicator.value) % (max - 1)
// It is not possible to make jumps of more than 30 minutes - reverse the direction
if (delta > 60) {
delta -= max
} else if (delta < -60) {
delta += max
}
if (isMoving && isLagging) {
if (Math.abs(delta) < 2) {
isLagging = false
}
}
valueIndicator.value += delta
valuePicker.value = m
}
onPressed: {
console.log("PRESSED")
var coords = remapMouse(mouseX, mouseY)
var radius = radiusForCoord(coords.x, coords.y)
changingProperty = propertyForRadius(radius)
if (changingProperty != 0) {
preventStealing = true
var angle = angleForCoord(coords.x, coords.y)
isLagging = true
updateForAngle(angle)
} else {
// Outside the minutes band - allow pass through to underlying component
preventStealing = false
}
}
onPositionChanged: {
if (changingProperty > 0) {
var coords = remapMouse(mouseX, mouseY)
var angle = angleForCoord(coords.x, coords.y)
isMoving = true
updateForAngle(angle)
}
}
onReleased: {
changingProperty = 0
isMoving = false
isLagging = false
}
}
}