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 } } }