Index: leahi.qrc =================================================================== diff -u -rac81e21684bfb1c72b8ef86bfeda04e174e5066e -rd66feb12d37023f6f67672ff4acdb5abebdbff15 --- leahi.qrc (.../leahi.qrc) (revision ac81e21684bfb1c72b8ef86bfeda04e174e5066e) +++ leahi.qrc (.../leahi.qrc) (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -210,6 +210,7 @@ sources/gui/qml/compounds/LabelUnitValueAdjuster.qml sources/gui/qml/compounds/SettingsSlider.qml sources/gui/qml/compounds/AutoStepController.qml + sources/gui/qml/compounds/InteractiveImage.qml qtquickcontrols2.conf Index: sources/gui/qml/compounds/AutoStepController.qml =================================================================== diff -u -ra8a42037ec8a5b2a5057c8dbb0f6cc079672b7fe -rd66feb12d37023f6f67672ff4acdb5abebdbff15 --- sources/gui/qml/compounds/AutoStepController.qml (.../AutoStepController.qml) (revision a8a42037ec8a5b2a5057c8dbb0f6cc079672b7fe) +++ sources/gui/qml/compounds/AutoStepController.qml (.../AutoStepController.qml) (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -14,9 +14,11 @@ signal triggered() function refresh() { - _stepTimer.running = false - _stepTimer.running = true // restart timer - _progressAnim.restart() + if ( isPlaying ) { + _stepTimer.stop() + _stepTimer.start() // restart timer + _progressAnim.restart() + } } Timer { id: _stepTimer @@ -34,7 +36,7 @@ anchors.verticalCenter : _root.verticalCenter iconImageSource : _root.isPlaying ? "qrc:/images/iPause" : "qrc:/images/iPlay" isDefault : true - borderColor: Colors.transparent + borderColor : Colors.transparent onClicked : _root.isPlaying = ! _root.isPlaying ProgressCircle { id: _progress Index: sources/gui/qml/compounds/InstructionView.qml =================================================================== diff -u -ra8a42037ec8a5b2a5057c8dbb0f6cc079672b7fe -rd66feb12d37023f6f67672ff4acdb5abebdbff15 --- sources/gui/qml/compounds/InstructionView.qml (.../InstructionView.qml) (revision a8a42037ec8a5b2a5057c8dbb0f6cc079672b7fe) +++ sources/gui/qml/compounds/InstructionView.qml (.../InstructionView.qml) (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -20,6 +20,7 @@ // Qml imports import "qrc:/globals" import "qrc:/components" +import "qrc:/compounds" Rectangle { id: _root property string title : "" @@ -128,9 +129,15 @@ } } - Image { id: _image - anchors.right : parent.right - source : _root.stepImages.length && _root.stepImages[_root.currentIndex] ? _root.stepImages[_root.currentIndex] : "" + InteractiveImage { id: _image + anchors { + bottom : parent.bottom + right : parent.right + } + width : parent.width / 2 + height : _root.height + + source : _root.stepImages.length && _root.stepImages[_root.currentIndex] ? _root.stepImages[_root.currentIndex] : "" } } } Index: sources/gui/qml/compounds/InteractiveImage.qml =================================================================== diff -u --- sources/gui/qml/compounds/InteractiveImage.qml (revision 0) +++ sources/gui/qml/compounds/InteractiveImage.qml (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -0,0 +1,118 @@ +import QtQuick 2.0 + +Item { id: _root + property alias source:_image.source + + property real minScale : 1 + property real maxScale : 4 + property real startScale: 1 + property bool pinching : false + + onSourceChanged: reset() + + function snapBack() { + _flickable.contentX = Math.max( 0, Math.min(_flickable.contentWidth - _flickable.width, _flickable.contentX) ) + _flickable.contentY = Math.max( 0, Math.min(_flickable.contentHeight - _flickable.height, _flickable.contentY) ) + } + + function rubberClamp() { + let minX = 0 + let maxX = Math.max(0, _flickable.contentWidth - _flickable.width) + let minY = 0 + let maxY = Math.max(0, _flickable.contentHeight - _flickable.height) + let resistance = 0.35 + + _flickable.contentX = (_flickable.contentX < minX) ? minX + (_flickable.contentX - minX) * resistance + : (_flickable.contentX > maxX) ? maxX + (_flickable.contentX - maxX) * resistance + : _flickable.contentX + + _flickable.contentY = (_flickable.contentY < minY) ? minY + (_flickable.contentY - minY) * resistance + : (_flickable.contentY > maxY) ? maxY + (_flickable.contentY - maxY) * resistance + : _flickable.contentY + } + + function reset() { + _scaleTransform.xScale = 1 + _scaleTransform.yScale = 1 + _flickable.contentX = 0 + _flickable.contentY = 0 + } + + Flickable { id: _flickable + anchors.fill : parent + contentWidth : _image.width * _scaleTransform.xScale + contentHeight : _image.height * _scaleTransform.yScale + clip : true + interactive : ! pinching + + PinchArea { id: _pinchArea + anchors.fill : parent + pinch.target: _image + + Image { id: _image + fillMode : Image.PreserveAspectFit + + transform: Scale { id: _scaleTransform + origin.x: 0 + origin.y: 0 + xScale : 1 + yScale : 1 + + // smooth zoom feel + Behavior on xScale { NumberAnimation { duration: 80; easing.type: Easing.OutQuad } } + Behavior on yScale { NumberAnimation { duration: 80; easing.type: Easing.OutQuad } } + } + + // ✅ bottom-right when not zoomed + x: (_flickable.contentWidth <= _flickable.width) ? (_flickable.width - width * _scaleTransform.xScale) : -_flickable.contentX + y: (_flickable.contentHeight <= _flickable.height) ? (_flickable.height - height * _scaleTransform.yScale) : -_flickable.contentY + + // smooth snap + Behavior on x { NumberAnimation { duration: 80; easing.type: Easing.OutCubic } } + Behavior on y { NumberAnimation { duration: 80; easing.type: Easing.OutCubic } } + } + + onPinchStarted: (pinch) => { + pinching = true + startScale = _scaleTransform.xScale + } + + onPinchUpdated: (pinch) => { + if (Math.abs(pinch.scale - 1) < 0.01) return + + // smooth scale + let newScale = startScale * pinch.scale + newScale = Math.max(minScale, Math.min(maxScale, newScale)) + + // 🔑 ratio between scales + let scaleRatio = newScale / _flickable.xScale + + // 🔑 adjust content so zoom follows fingers + let cx = pinch.center.x + let cy = pinch.center.y + + _flickable.contentX = (_flickable.contentX + cx) * scaleRatio - cx + _flickable.contentY = (_flickable.contentY + cy) * scaleRatio - cy + + // apply scale AFTER adjusting content + _scaleTransform.xScale = newScale + _scaleTransform.yScale = newScale + + // 🔑 now apply drag (correct direction) + let dx = pinch.center.x - pinch.previousCenter.x + let dy = pinch.center.y - pinch.previousCenter.y + + _flickable.contentX -= dx * 0.8 + _flickable.contentY -= dy * 0.8 + + rubberClamp() + } + + onPinchFinished: { + reset() + snapBack() + Qt.callLater(() => pinching = false) + } + } + } +} Index: sources/gui/qml/dialogs/NotificationDialog.qml =================================================================== diff -u -ra8a42037ec8a5b2a5057c8dbb0f6cc079672b7fe -rd66feb12d37023f6f67672ff4acdb5abebdbff15 --- sources/gui/qml/dialogs/NotificationDialog.qml (.../NotificationDialog.qml) (revision a8a42037ec8a5b2a5057c8dbb0f6cc079672b7fe) +++ sources/gui/qml/dialogs/NotificationDialog.qml (.../NotificationDialog.qml) (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -152,7 +152,7 @@ onCurrentIndexChanged: { var item = model.get(_listView.currentIndex) - _root.figureImageSource = item.image + _root.figureImageSource = item.image ?? "" _autoStepController.refresh() } @@ -213,9 +213,16 @@ } } - Image { id: _figureImage - anchors.right : parent.right - anchors.verticalCenter: parent.verticalCenter + InteractiveImage { id: _figureImage + anchors { + right : parent.right + bottom : parent.bottom + bottomMargin : Variables.defaultMargin * 3 + + } + width : parent.width / 2 + height : _listView.height + } } Index: sources/gui/qml/pages/treatment/TreatmentSectionHeader.qml =================================================================== diff -u -rac81e21684bfb1c72b8ef86bfeda04e174e5066e -rd66feb12d37023f6f67672ff4acdb5abebdbff15 --- sources/gui/qml/pages/treatment/TreatmentSectionHeader.qml (.../TreatmentSectionHeader.qml) (revision ac81e21684bfb1c72b8ef86bfeda04e174e5066e) +++ sources/gui/qml/pages/treatment/TreatmentSectionHeader.qml (.../TreatmentSectionHeader.qml) (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -77,8 +77,6 @@ IconButton { id : _pauseResume visible : showPauseResume - iconSize : 25 - extraSpace : 30 iconImageSource : _root.isPaused ? "qrc:/images/iPlay" : "qrc:/images/iPause" isDefault : true onClicked : _root.pauseResumeClicked() Index: sources/gui/qml/pages/treatment/TreatmentStack.qml =================================================================== diff -u -r0bc5c7e5752e76707f92b9ef1ecfe54b0c5ead29 -rd66feb12d37023f6f67672ff4acdb5abebdbff15 --- sources/gui/qml/pages/treatment/TreatmentStack.qml (.../TreatmentStack.qml) (revision 0bc5c7e5752e76707f92b9ef1ecfe54b0c5ead29) +++ sources/gui/qml/pages/treatment/TreatmentStack.qml (.../TreatmentStack.qml) (revision d66feb12d37023f6f67672ff4acdb5abebdbff15) @@ -177,6 +177,7 @@ ) } } + Connections { target: _treatmentAdjustmentPressuresLimits function onConfirmClicked ( vValue ) { vTreatmentAdjustmentPressuresLimits.doAdjustment( @@ -187,6 +188,7 @@ ) } } + Connections { target: _treatmentAdjustmentBolusVolume function onConfirmClicked ( vValue ) { vTreatmentAdjustmentBolusVolume.doAdjustment(_treatmentAdjustmentBolusVolume.fluidBolusVolume)