Qt pinch Zoom in , Zoom out (pinchArea) - qt

enter code here
PinchArea
{
MouseArea
{
id:dragArea
hoverEnabled: true
anchors.fill: parent
drag.target: image
scrollGestureEnabled: false
// drag.maximumX: Screen.width
// drag.maximumY: Screen.height
}
anchors.fill:image
enabled: true
pinch.target: image
pinch.maximumScale: 4.0
pinch.minimumScale: 1.0
pinch.dragAxis: pinch.XAndYAxis
pinch.maximumX: image.width
pinch.maximumY: image.heighr
onPinchStarted: {
console.log("console ","pinchstarted")
}
onPinchUpdated: {
console.log("console ","pinchUpdated")
}
onPinchFinished: {
console.log("console ","pinchfinished")
}
}
}
I want to increase and decrease the scale by 0.1 when I pinch.
And when you take a log, too many onPinchUpdated are generated.
I want to log 1 step at a time when I pinch.

You'll need to remove pinch.target: image and instead manually set the scale of image based on rounding the pinch.scale value accessible in the onPinchStarted and onPinchUpdated handlers.
Something like:
PinchArea
{
MouseArea
{
id:dragArea
hoverEnabled: true
anchors.fill: parent
drag.target: image
scrollGestureEnabled: false
// drag.maximumX: Screen.width
// drag.maximumY: Screen.height
}
anchors.fill:image
enabled: true
// pinch.target: image
pinch.maximumScale: 4.0
pinch.minimumScale: 1.0
pinch.dragAxis: pinch.XAndYAxis
pinch.maximumX: image.width
pinch.maximumY: image.heighr
onPinchStarted: {
console.log("console ","pinchstarted")
image.scale = Math.round(pinch.scale / 10) * 10;
}
onPinchUpdated: {
console.log("console ","pinchUpdated")
image.scale = Math.round(pinch.scale / 10) * 10;
}
onPinchFinished: {
console.log("console ","pinchfinished")
}
}
}

Related

QtQuick Grid, mouse events no propagating over adjacent mouse areas

I've written this simple qml app that allows to paint pixels over a grid:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Grid {
id: grid
anchors.fill: parent
rows: 32
columns: 64
Repeater {
model: grid.columns * grid.rows;
delegate: delegateGridImage
}
}
Component {
id: delegateGridImage
Item {
id: gridItem
property int currentColumn: index % grid.columns
property int currentRow: Math.floor(index / grid.rows);
// Resize to screen size
width: grid.width / grid.columns
height: grid.height / grid.rows
Rectangle {
id: pixel
anchors.fill: parent
property bool pixel_state: true
color: if (pixel_state == true ) { "white" } else { "black" }
MouseArea {
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: console.log(index)
onPressed: pixel.pixel_state ^= true
}
}
}
}
}
This works fine:
I would like to be able to paint multiple pixels with a single mouse click pressed.
I've tried the onEntered event, but it only listens to the active mouse area until the click button is released. Is there a way to not block the events from the other mouse areas?
You can use a global MouseArea and deduct the current item below the cursor via childAt(...) of the Grid.
Window {
... // remove the MouseArea of pixel
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
property bool pixel_activate: true
onPressed: {
var child = grid.childAt(mouse.x, mouse.y)
child.pixel_state ^= true
pixel_activate = child.pixel_state
}
onPositionChanged: {
if (!pressed) return;
var child = grid.childAt(mouse.x, mouse.y)
child.pixel_state = pixel_activate
}
}
}
You just have to decide what action you want to perform once you hold the button pressed (currently it performs first action activate/deactivate and all following). Also take a look at the MouseEvent passed by the signals pressed and positionChanged so you can differentiate what key was pressed.
Lasall's solution works great. This is the final result:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Grid {
id: grid
anchors.fill: parent
rows: 32
columns: 64
Repeater {
model: grid.columns * grid.rows;
delegate: delegateGridImage
}
}
Component {
id: delegateGridImage
Item {
id: gridItem
property int currentColumn: index % grid.columns
property int currentRow: Math.floor(index / grid.rows);
property bool pixel_state: false
// Resize to screen size
width: grid.width / grid.columns
height: grid.height / grid.rows
Rectangle {
id: pixel
anchors.fill: parent
color: if (gridItem.pixel_state == true ) { "white" } else { "black" }
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
property bool pixel_activate: true
onPressed: {
var child = grid.childAt(mouse.x, mouse.y)
child.pixel_state ^= true
pixel_activate = child.pixel_state
}
onPositionChanged: {
if (!pressed) return;
var child = grid.childAt(mouse.x, mouse.y)
child.pixel_state = pixel_activate
}
}
}

How can I propagate hover or mousearea update events to lower elements in QML?

I have some sibling Rectangle elements with a radius property so they appear as circles. Each has a child Item that has a child MouseArea, the purpose of the Item being to implement a "round mouse area" effect (original SO answer). The Item and MouseArea are instrumented such that clicks and drags will only take effect within the visible circular shape of the Rectangle, not within the bounding box that is the real footprint of the Rectangle.
Unfortunately there is a glitch illustrated below. The desired outcome when dragging at the dot is for circle 1 to move, and this happens in most circumstances. However, it does not happen when you create create circle 1 then circle 2 then move your mouse cursor to the dot. If you do that and attempt to drag or click, your interaction will fall through to the background full-window MouseArea and create a new circle.
The cause of this problem is that when the mouse cursor moves to the dot from circle #2, the mouseX and mouseY for circle #1's MouseArea do not get updated. When circle #2 allows the click to propagate downward, it hits the Rectangle of circle #1 but then circle #1's Item claims containsMouse is false and it propagates downward again.
As soon as the mouse cursor leaves the footprint of circle #2's bounding rectangle, such as by moving a bit up or left from the dot, circle #1's MouseArea gets updated and its containsMouse becomes true and it starts capturing clicks and drags again.
I have tried a handful of potential solutions and not gotten much farther than the code below.
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow {
visible: true
width: 640
height: 480
property real spotlightRadius: 100
MouseArea {
visible: true
anchors.fill: parent
onClicked: {
spotlightComponent.createObject(parent, {
"x": x + mouseX - spotlightRadius,
"y": y + mouseY - spotlightRadius,
"width": spotlightRadius * 2,
"height": spotlightRadius * 2
})
}
}
Component {
id: spotlightComponent
Rectangle {
id: spotlightCircle
visible: true
x: parent.x
y: parent.y
width: parent.width
height: parent.height
radius: Math.max(parent.width, parent.height) / 2
color: Qt.rgba(Math.random()*0.5+0.5,Math.random()*0.5+0.5,Math.random()*0.5+0.5,0.5);
Item {
anchors.fill: parent
drag.target: parent
onDoubleclicked: parent.destroy()
onWheel: { parent.z += wheel.pixelDelta.y; currentSpotlight = parent }
property alias drag: mouseArea.drag
//FIXME when moving the mouse out of a higher element's containsMouse circle
// but still inside its mouseArea.containsMouse square, lower elements'
// mouseArea do not update, so their containsMouse doesn't update, so clicks
// fall through when they should not.
property bool containsMouse: {
var x1 = width / 2;
var y1 = height / 2;
var x2 = mouseArea.mouseX;
var y2 = mouseArea.mouseY;
var deltax = x1 - x2;
var deltay = y1 - y2;
var distance2 = deltax * deltax + deltay * deltay;
var radius2 = Math.pow(Math.min(width, height) / 2, 2);
return distance2 < radius2;
}
signal clicked(var mouse)
signal doubleclicked(var mouse)
signal wheel(var wheel)
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
//FIXME without acceptedButtons, propagated un-accepted clicks end up with the wrong coordinates
acceptedButtons: parent.containsMouse ? Qt.LeftButton : Qt.NoButton
propagateComposedEvents: true
onClicked: { if (parent.containsMouse) { parent.clicked(mouse) } else { mouse.accepted = false } }
onDoubleClicked: { if (parent.containsMouse) { parent.doubleclicked(mouse) } }
onWheel: { if (parent.containsMouse) { parent.wheel(wheel) } }
drag.filterChildren: true
}
}
}
}
}
This is not the exact solution for your problem, but this is how I overcame the root of the issue.
In my application there is a MouseArea that overlaps a large chunk of the scene which is a QQuickFrameBufferObject. This is where I draw the 3D scene. Since you cannot propagate a QHoverEvent in QML, you will have to catch the position changed signal using the onPositionChanged handler and invoke a method in C++ which will send a QHoverEvent to the required items.
QML:
MouseArea {
onPositionChanged: {
model.sendHoverEvent(Qt.point(mouse.x, mouse.y))
}
}
C++:
class TreeViewModel : public QAbstractListModel
{
// ...
void TreeViewModel::sendHoverEvent(QPointF p) {
QHoverEvent hoverEvent(QEvent::HoverMove, p, p);
QApplication::sendEvent(mApplication.graphicsLayer(), &hoverEvent);
}
};
I think that using HoverHandler instead of MouseArea can give the desired result in this case because they stack as you'd expect i.e. they are full "transparent" for mouse movement, nor are they blocked by any MouseArea that happens to be on top.
You need to reject the pressed event of your underlying MouseArea. It should be enough to solve your problems. If the pressed event is rejected, the click will automatically be forwarded to the underlying sibling items. propagateComposedEvents and filterChildren are useless in your case.
Note that if the wheel event causes the z coordinate of your spotlightCircle to become less than 0, it will no longer accept mouse event since they will be caught by the "Creation" MouseArea
import QtQuick 2.10
import QtQuick.Controls 2.3
ApplicationWindow {
visible: true
width: 640
height: 480
property real spotlightRadius: 100
MouseArea {
visible: true
anchors.fill: parent
onClicked: {
spotlightComponent.createObject(parent, {
"x": x + mouseX - spotlightRadius,
"y": y + mouseY - spotlightRadius,
"width": spotlightRadius * 2,
"height": spotlightRadius * 2
})
}
}
Component {
id: spotlightComponent
Rectangle {
id: spotlightCircle
visible: true
x: parent.x
y: parent.y
width: parent.width
height: parent.height
radius: Math.max(parent.width, parent.height) / 2
color: Qt.rgba(Math.random()*0.5+0.5,Math.random()*0.5+0.5,Math.random()*0.5+0.5,0.5);
Item {
anchors.fill: parent
onDoubleClicked: parent.destroy()
onWheel: { parent.z += wheel.pixelDelta.y; currentSpotlight = parent }
signal clicked(var mouse)
signal pressed(var mouse)
signal doubleClicked(var mouse)
signal wheel(var wheel)
property alias drag: mouseArea.drag
property bool containsMouse: {
var x1 = width / 2;
var y1 = height / 2;
var x2 = mouseArea.mouseX;
var y2 = mouseArea.mouseY;
var deltax = x1 - x2;
var deltay = y1 - y2;
var distance2 = deltax * deltax + deltay * deltay;
var radius2 = Math.pow(Math.min(width, height) / 2, 2);
return distance2 < radius2;
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
drag.target: spotlightCircle
onPressed: { if (parent.containsMouse) { parent.pressed(mouse) } else { mouse.accepted = false } }
onClicked: { if (parent.containsMouse) { parent.clicked(mouse) } else { mouse.accepted = false } }
onDoubleClicked: { if (containsMouse2) { parent.doubleClicked(mouse) } }
onWheel: { if (parent.containsMouse) { parent.wheel(wheel) } }
}
}
}
}
}

QML: in loading images dynamically

I am trying to load different images dynamically based on a button press.
Rectangle {
id: rect
width: 800
height: 600
color: "black"
x: 300
y: 10
MediaPlayer {
id: mediaplayer
source: "images/video10.avi"
autoPlay: true
onError: videoPage1.sendToPost(mediaplayer.errorString)
}
VideoOutput {
id: videoOutput
anchors.fill: parent
source: mediaplayer
fillMode: VideoOutput.Stretch
}
MouseArea {
id: playArea
anchors.fill: parent
onPressed: {
if(!mediaplayer.hasVideo){
console.log("No media");
}
pressX = mouse.x
pressY = mouse.y
}
onReleased: {
releaseX = mouse.x
releaseY = mouse.y
doSomething();
roi_flag=0;
}
}
}
Component {
id: highlightComponent;
Rectangle {
color: "transparent";
border.color: "black"
border.width: 2
property alias text:t1.text;
property alias ss: image.source;
Text {
id: t1
text: "click"
anchors.centerIn: parent
width: 5
}
Image {
id:image
// width: 130; height: 100
// source: (direction_flag ? "images/top.png" : "images/right.png")
source : ss
anchors.centerIn: parent
}
}
}
function doSomething(){
console.log(rOI);
count++;
var obj = 'highlightItem' + count;
var dir;
if(direction_flag == 0){
if((pressX - releaseX) > 0){
dir= "images/left.png";
}else if((pressX - releaseX) < 0){
dir="images/right.png";
}
}else{
if((pressY - releaseY) > 0){
dir="images/top.png";
}else if((pressY - releaseY) < 0){
dir="images/down.png";
}
}
highlightComponent.incubateObject (rect, {
"x" : pressX,"y":pressY,width:releaseX - pressX,height: releaseY - pressY,text:count,ss:dir});
}
The method works when changing the text but its not working when changing the image, I am getting the error "QML Image: Protocol "" is unknown".
Any suggestions will be helpful, thanks.

How works ChartView::zoomIn(rect rectangle)

I want to zoom a selected with rectang area in ChartView:
import QtQuick 2.7
import QtCharts 2.1
ChartView{
id: chart
width: 400
height: 400
ScatterSeries{
markerSize: 10
XYPoint{x: 1; y: 1}
XYPoint{x: 2; y: 2}
XYPoint{x: 5; y: 5}
}
Rectangle{
id: rectang
color: "black"
opacity: 0.6
visible: false
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.AllButtons
onPressed: {rectang.x = mouseX; rectang.y = mouseY; rectangle.visible = true}
onMouseXChanged: {rectang.width = mouseX - rectang.x}
onMouseYChanged: {rectang.height = mouseY - rectang.y}
onReleased: {
chart.zoomIn(rectang); // something wrong with that line, it doesn't work
rectang.visible = false
}
}
}
Can you tell how to use ChartView::zoomIn(rect rectangle) properly? I want zoom to work like in Zoom Line Example. Simple ChartView::zoomIn() just zoom a center by factor of 2.
That helpped:
onReleased: {
chart.zoomIn(Qt.rect(rectang.x, rectang.y, rectang.width, rectang.height))
rectang.visible = false
}
I mistakenly thought that rect and Rectangle are the same types.

QML-Move item with arrow keys

I'm using Qt version 5.6.0 from windows 8.1. I draw a shape that has 4 sides and the ability to drag its apexes.I want to move apexes of shape with arrow keys.I use this code but it does not work.
Point.qml:
Item {
id: root
signal dragged()
property alias color :point.color
Rectangle {
id:point
anchors.centerIn: parent
width: 20
height: 20
opacity: 0.2
MouseArea {
anchors.fill: parent
drag.target: root
onPositionChanged: {
if(drag.active) {
dragged()
}
}
onClicked: point.focus=true;
}
// Keys.onPressed: {
// console.log("move");
// if (event.key === Qt.Key_Left) {
// console.log("move left");
// event.accepted = true;
// point.x-=1;
// }
// }
// Keys.onRightPressed: {
// console.log("move");
// point.x+=1;
// }
// Keys.onLeftPressed: {
// console.log("move");
// point.x-=1;
// }
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Left: point.x-=1;
break;
case Qt.Key_Right: point.x+=1;
break;
case Qt.Key_Up: point.y-=1;
break;
case Qt.Key_Down: point.y+=1;
break;
}
}
focus: true;
}}
main.qml:
Point {
id: pointA
x: 50
y: 50
}
Point {
id: pointB
x: 250
y: 50
}
Point {
id: pointC
x: 250
y: 250
}
Point {
id: pointD
x: 50
y: 250
}
Item {
anchors.fill: parent
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = canvas.getContext('2d');
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.lineTo(pointD.x, pointD.y);
ctx.lineTo(pointA.x, pointA.y);
ctx.stroke();
}
Component.onCompleted: {
pointA.dragged.connect(repaint)
pointB.dragged.connect(repaint)
pointC.dragged.connect(repaint)
pointD.dragged.connect(repaint)
}
function repaint() {
var ctx = getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
requestPaint()
}
}
}
Update:
main.qml:
PhotoPreview {
id : photoPreview
anchors.fill : parent
//focus:true //visible
visible: capture
}
photopreview.qml:
Item {
id:mywin
property real defaultSize:mywin.width
property var currentFrame: undefined
property real surfaceViewportRatio: 1.5
focus: true
ScrollView {
anchors.fill: parent
flickableItem.interactive: true
frameVisible: true
highlightOnFocus: true
Flickable {
id: flick
anchors.fill: parent
contentWidth: parent.width
contentHeight: parent.height
property alias source :image.source
signal closed
Rectangle {
id: photoFrame
width: parent.width
height: parent.height
color:"transparent"
scale:defaultSize / parent.width
Behavior on scale { NumberAnimation { duration: 200 } }
Behavior on x { NumberAnimation { duration: 200 } }
Behavior on y { NumberAnimation { duration: 200 } }
smooth: true
antialiasing: true
Image {
id:image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
smooth: true
}
PinchArea {
anchors.fill: parent
pinch.target: photoFrame
pinch.minimumRotation: -360
pinch.maximumRotation: 360
pinch.minimumScale: 0.1
pinch.maximumScale: 10
pinch.dragAxis: Pinch.XAndYAxis
property real zRestore: 0
onSmartZoom: {
if (pinch.scale > 0) {
photoFrame.rotation = 0;
photoFrame.scale = Math.min(mywin.width, mywin.height) / Math.max(image.sourceSize.width, image.sourceSize.height) * 0.85
photoFrame.x = flick.contentX + (flick.width - photoFrame.width) / 2
photoFrame.y = flick.contentY + (flick.height - photoFrame.height) / 2
zRestore = photoFrame.z
photoFrame.z = ++mywin.highestZ;
} else {
photoFrame.rotation = pinch.previousAngle
photoFrame.scale = pinch.previousScale
photoFrame.x = pinch.previousCenter.x - photoFrame.width / 2
photoFrame.y = pinch.previousCenter.y - photoFrame.height / 2
photoFrame.z = zRestore
--mywin.highestZ
}
}
MouseArea {
id: dragArea
hoverEnabled: true
anchors.fill: parent
drag.target: photoFrame
scrollGestureEnabled: false // 2-finger-flick gesture should pass through to the Flickable
onPressed: {
photoFrame.z = ++mywin.highestZ;
}
onWheel: {
if (wheel.modifiers & Qt.ControlModifier) {
photoFrame.rotation += wheel.angleDelta.y / 120 * 5;
if (Math.abs(photoFrame.rotation) < 4)
photoFrame.rotation = 0;
} else {
photoFrame.rotation += wheel.angleDelta.x / 120;
if (Math.abs(photoFrame.rotation) < 0.6)
photoFrame.rotation = 0;
var scaleBefore = photoFrame.scale;
photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10;
}
}
}
}
Point {
id: pointA
x: image.width/4
y: image.height/4
color: "blue"
}
Point {
id: pointB
x: image.width/2
y: image.height/2
color: "blue"
}
Point {
id: pointD
x: image.width/4
y: image.height/2
color: "red"
}
Point {
id: pointC
x: image.width/2
y: image.height/4
color: "red"
}
Item {
anchors.fill: parent
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
//...
}
Component.onCompleted: {
//...
}
function repaint() {
//..
}
}
}
}
}
}}
You are using ScrollView that inherit FocusScope, which needs to have focus:true to forward it.
You instead are setting focus to PhotoPreview, which is plain Item that are not supposed to be focused.
So you need to simple remove focus:visible and set it to ScrollView.
The better fix is to remove that redundant Item, and leave ScrollView as PhotoPreview root item, with all its properties and signals.
In Point.qml the key event handler tries to change the x, y of point, however point is a rectangle that anchors.centerIn: parent. The key event handler should change root x, y instead:
//in Point.qml
case Qt.Key_Left:
root.x-=1;
break;
Point now changes it's position when keyboard event is triggered. Next, Point needs to notify the Canvas in main.qml to repaint. Point can emit dragged signal after changing it's x, y:
//in Point.qml
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Left:
root.x-=1;
dragged();
break;
case Qt.Key_Right:
root.x+=1;
dragged();
break;
case Qt.Key_Up:
root.y-=1;
dragged();
break;
case Qt.Key_Down:
root.y+=1;
dragged();
break;
}
}

Resources