Zooming/Scaling an Image at a specific point within a Flickable - qt

By moving a Slider, I want to be able to scale (zoom in) on an Image and once scaled (zoomed in), be able to move around, ( flick ) I have this example working here below, but my problem is the following.
If I zoom in, scale in, and then flick around to a spot on the image, then I want to zoom (scale) in further on that specific point in the center of the window, when I scale in using the Slider, the zoom (scale) occurs at the center of the image because by default the 'transformOrigin' is Item.Center
Try running the code to understand what I am talking about: ( you will need to change the image source)
import QtQuick 2.12
import QtQuick.Controls 2.3
Item {
id: mainId
width: 1280
height: 720
property int scaleMultiplier: 3
Flickable {
id: flickArea
anchors.fill: parent
focus: true
contentWidth: Math.max(inner.width * slider.value * scaleMultiplier, width)
contentHeight: Math.max(inner.height * slider.value * scaleMultiplier, height)
anchors.centerIn: parent
boundsBehavior: Flickable.StopAtBounds
contentX: contentWidth === width ? 0 : inner.width * slider.value * scaleMultiplier / 2 - flickArea.width / 2
contentY: contentHeight === height ? 0 : inner.height * slider.value * scaleMultiplier / 2 - flickArea.height / 2
Image {
id: inner
scale: slider.value * scaleMultiplier
anchors.centerIn: parent
source: "test_images/1.png"
}
}
Slider {
id: slider
value: .01
orientation: Qt.Vertical
anchors {
bottom: parent.bottom
right: parent.right
top: parent.top
margins: 50
}
from: 0.01
}
}
I tried setting an arbitrary transform origin point like so:
Image {
id: inner
scale: slider.value * scaleMultiplier
anchors.centerIn: parent
source: "test_images/1.png"
transform: Scale {
id: scaleID ;
origin.x: flickArea.contentX + flickArea.width * flickArea.visibleArea.widthRatio / 2
origin.y: flickArea.contentY + flickArea.height * flickArea.visibleArea.heightRatio / 2
}
}
But it seems to have no effect. Any help is appreciated. What am I missing?
Again what I want is when I flick to a specific point on the Image, and scale in, I want the point of origin to scale into be the area in the center of the viewable flickarea. For example let's say I am zoomed in the upper right corner of the image, then I wish to zoom in further, when I zoom in further, the Image is brought back to the center of the image.
Thanks.

Found the answer with help from #sierdzio here:
https://forum.qt.io/topic/105233/zooming-scaling-an-image-at-a-specific-point-within-a-flickable/2
I needed to use resizeContents() method from Flickable
property real zoom: 1;
onZoomChanged: {
var zoomPoint = Qt.point(flickArea.width/2 + flickArea.contentX,
flickArea.height/2 + flickArea.contentY);
flickArea.resizeContent((inner.width * zoom), (inner.height * zoom), zoomPoint);
flickArea.returnToBounds();
}
and
// REMOVE THESE LINES:
contentWidth: Math.max(inner.width * slider.value * scaleMultiplier, width)
contentHeight: Math.max(inner.height * slider.value * scaleMultiplier, height)
contentX: contentWidth === width ? 0 : inner.width * slider.value * scaleMultiplier / 2 - flickArea.width / 2
contentY: contentHeight === height ? 0 : inner.height * slider.value * scaleMultiplier / 2 - flickArea.height / 2
// Instead, do:
contentWidth: inner.width
contentHeight: inner.height

Related

QML SplitView - manually resize childs

I have a QtQuick Controls 1.3 SplitView(as I am on QT 5.11), which contains 3 rectangles in vertical orientation. It displays fine, and I can drag-resize the childs as intended.
Now I want to add a button which allows the user to completely hide the bottom most rectangle, effectively collapsing it. However, nothing I am trying to resize the rect works:
import QtQuick.Controls 1.3 as C1
[...]
C1.SplitView {
id: splitView
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width - flightSelector.width - separator.width
orientation: Qt.Vertical
LateralChart {
id: lateralChart
width: parent.width
height: mainPage.height * 0.75
Layout.minimumHeight: 30
Layout.maximumHeight: mainPage.height - 30 - 30
}
UtilityBar {
id: utilityBar
Layout.minimumHeight: 30
Layout.maximumHeight: 30
onCollapse: {
console.log("minimize")
flightList.height = 0 // no effect
flightList.preferredHeight = 0 // no effect
flightlist.Layout.maximumHeight = 0 // no effect
flightList.shouldCollapse = true // no effect
}
}
FlightListTable {
id: flightList
width: parent.width
Layout.minimumHeight: 0
Layout.maximumHeight: mainPage.height - 60 - 30
model: flightListFilterModel
property bool shouldCollapse: false
C1.SplitView.preferredHeight: shouldCollapse ? 0 : 300
}
}
[...]
If I just go flightList.visible = false, then the rect will be hidden, but the position of the middle rect remains, so it is positioned wrong (it should move to the bottom).
How can I resize SplitView child contents dynamically via JS code?
According to the docs, there must always be one (and only one) child object that has Layout.fillheight set to true. By default, it will choose the last visible child in the SplitView. In your case, it sounds like you want that to actually be the first child. So adding Layout.fillHeight: true to your LateralChart should give you the desired output.

TestCase mouseDrag only clicks item inside Flickable but does not drag

In a QML TestCase, I'm trying to setup automatic scrolling of a ListView that is contained inside a Flickable (to add a custom footer that can be flicked into view, which wouldn't happen with just ListView { footer: Component {} })
However, the mouseDrag only seems to click the correct coordinate, but not drag it to any direction. Here is a simplified version that is as close to the real one as possible:
Implementation.qml
import QtQuick 2.5
FocusScope {
width: 1920
height: 1080
Flickable {
objectName: 'flickableList'
boundsBehavior: Flickable.StopAtBounds
clip: true
width: parent.width
height: 240
contentHeight: 500
ListView {
interactive: false
height: parent.height
width: parent.width
model: ['example1', 'example2', 'example3', 'example4', 'example5']
delegate: Item {
width: 300
height: 100
Text {
text: modelData
}
}
}
}
Item {
id: footer
height: 100
width: parent.width
}
}
TheTest.qml
// The relevant part
var theList = findChild(getView(), 'flickableList')
var startY = 220
var endY = 20
mouseDrag(theList, 100, startY, 100, endY, Qt.LeftButton, Qt.NoModifier, 100)
So, when I'm viewing the UI testrunner, I can see it clearly click on the correct delegate (it has a focus highlight in the actual implementation), ie. the third item "example3", which starts at Y 200 and ends at Y 300). But the drag event never happens. Nothing moves on the screen, and compare(theList.contentY, 200) says it is still at position 0. I would expect it to be at 200, since the mouse is supposed to be mouseDragging from position 220 to 20, ie. scrolling the list down by 200. And 220 is also within the visible height (240).
Just to be sure, I also reversed the Y values, but also no movement:
var theList = findChild(getView(), 'flickableList')
var startY = 20
var endY = 220
mouseDrag(theList, 100, startY, 100, endY, Qt.LeftButton, Qt.NoModifier, 100)
Also, as the 3rd item clearly is clicked on (it gets highlighted), the passed item theList (= the Flickable), should be valid.
Edit:
Oh, and this does scroll the list, but it goes all the way to the bottom of the list (388 px down in the actual implementation, even when the delta is just 30 pixels):
mousePress(theList, startX, startY)
mouseMove(theList, endX, endY)
mouseRelease(theList, endX, endY)
So the question is:
Does mouseDrag only work for specific types of components (ie. does not work on Flickable?), or is there something missing? How can I get it to scroll the list down? Thanks!
Your tag says you're using Qt 5.5 - I would recommend trying Qt 5.14 if possible, as there was a fix that might help:
mouseDrag(): ensure that intermediate moves are done for all drags
[...]
In practice, this means that mouseDrag() never did intermediate moves
(i.e. what happens during a drag in real life) for drags that go from
right to left or upwards.
https://codereview.qt-project.org/c/qt/qtdeclarative/+/281903
If that doesn't help, or upgrading is not an option, I would recommend looking at Qt's own tests (although they are written in C++):
https://code.qt.io/cgit/qt/qtdeclarative.git/tree/tests/auto/quick/qquickflickable/tst_qquickflickable.cpp#n1150
I think mouseDrag only works for mouse area. You could wrap every object with that.
But in the end, you need to use a mouse area inside you delegate and Drag and Drop it.
https://doc.qt.io/qt-5/qml-qtquick-drag.html
import QtQuick 2.5
FocusScope {
width: 1920
height: 1080
Flickable {
objectName: 'flickableList'
boundsBehavior: Flickable.StopAtBounds
clip: true
width: parent.width
height: 240
contentHeight: 500
ListView {
interactive: false
height: parent.height
width: parent.width
model: ['example1', 'example2', 'example3', 'example4', 'example5']
delegate: DelegateList{
textAreaText = modelData
}
}
}
Item {
id: footer
height: 100
width: parent.width
}
}
And the DelegateList.qml
Item {
id: root
property alias textAreaText: textArea.text
width: 300
height: 100
Text {
id: textArea
}
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}

QML synchronize keyboard navigation with multiple list views

I need something like a Matrix view with infinite navigation capability just like ListView. By navigation I mean keyboard based scrolling, platform does not have mouse inputs.
Width of items with in matrix is not uniform, it's based on model item property.
I did try GirdView but I see two problems
Width of GirdView is limited to width of screen
If GirdView width is increased beyond screen width the activeFocusedItem wont be visible on scree upon navigating
Width of grid cell is max of width of grid items in column
TableView also have similar problem, haven't tried it out though.
I am considering ListView of horizontal ListViews
import QtQuick 2.0
ListView {
id: mat
model: 10
height: 120
width: parent.width
anchors.centerIn: parent
focus: true
highlightMoveDuration: 100
highlightMoveVelocity: -1
spacing: 0
delegate: ListView {
property string matIndex: index
id: eList
height: 60
width: parent.width
orientation: Qt.Horizontal
highlightMoveDuration: 100
highlightMoveVelocity: -1
model: 100
delegate: Rectangle {
height: 50
width: (Math.random() * 50) + 50
color: index % 2 == 0 ? "lightblue": "lightgreen";
radius: 4
border.width: activeFocus ? 3 : 0
border.color: "green"
Text {
anchors.centerIn: parent
text: "(" + eList.matIndex + "," + index + ")"
}
}
}
}
This way I am able to manage uneven items width.
However I am stuck with navigation, I can navigate only one list at a time. In above snapshot I have navigated on third row & activeFocus is on item with index 50. How can I synchronize multiple ListView navigation so that I see scrolling effect on all horizontal ListViews.
I would try to use the active ListView's "onContentXChanged" handler to scroll all of the non-active (but visible) ListViews' content to the same X position.
Update:
Here is a rudimentary implementation, based on the op's code:
import QtQuick 2.0
ListView {
id: mat
model: 10
height: 120
width: 600
//anchors.centerIn: parent
focus: true
highlightMoveDuration: 100
highlightMoveVelocity: -1
spacing: 0
property var updateItemsScroll: function (pos)
{
console.log("Update position to" , pos);
for( var i = 0; i < mat.count; i++)
{
if (currentIndex != i &&
mat.contentItem.children[i])
{
mat.contentItem.children[i].contentX = pos;
}
}
}
delegate: ListView {
property string matIndex: index
id: eList
height: 60
width: parent.width
orientation: Qt.Horizontal
highlightMoveDuration: 100
highlightMoveVelocity: -1
onContentXChanged: updateItemsScroll(contentX);
model: 100
delegate: Rectangle {
height: 50
width: (Math.random() * 50) + 50
color: index % 2 == 0 ? "lightblue": "lightgreen";
radius: 4
border.width: activeFocus ? 3 : 0
border.color: "green"
Text {
anchors.centerIn: parent
text: "(" + eList.matIndex + "," + index + ")"
}
}
}
}

Can We have a SwipeView by using PathView?

In QML Swipe View is not bidirectional.So I need a swipe view
A code sample will be very beneficial for me.
I need to keep only 3 items in my view & at a time only item should be visible & on swiping the view in either way left or right element should be on center.
This code solves half problem That is why I posted as answer
import QtQuick 2.0
Item {
property alias model: view.model
property alias delegate: view.delegate
property alias currentIndex: view.currentIndex
property real itemHeight: 60
clip: true
PathView {
id: view
anchors.fill: parent
snapMode: PathView.NoSnap
pathItemCount: height/itemHeight
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
dragMargin: view.width/5
path: Path {
startY: view.width/4; startX:-itemHeight/2 -50
PathLine { y: view.width/4; x: (view.pathItemCount*itemHeight + 3*itemHeight) }
}
}
}
And this is My Item :
Item{
id:widgetMain
width :480
height : 240
property int delegateHeight: widgetMain.height
property int delegateWidth : widgetMain.width
Spinner {
id: spinner
width: parent.width;
height: parent.height;
focus: true
model: ["qrc:/Tile1.qml",
"qrc:/Tile2.qml"
,"qrc:/Tile3.qml"]
itemHeight: 150
delegate: Loader {
width: delegateWidth
height: delegateHeight
source: modelData
}
}
}
Now If I swipe towards any direction, It shows only 1 tile in the view. & When my drag reaches to half way, then the tile removes & shifts to last.
Here I want to display that one tile is swiping & 2nd tile is coming from behind(Just like a Swipe view).
Now can you help me please?

How to create QML slider with a custom shape?

I have to create a slider with a custom shape like the one shown below (blue is the slider handle):
The red color one is groove and the blue color one is handle.
For the groove, you can use any Item to draw this shape, e.g. a Image
For the handle, you just use a Rectangle and place it according to the Slider.position, e.g. like so:
Slider {
id: slider
width: 400
height: 150
y: 200
handle: Rectangle {
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
y: Math.max(slider.topPadding, slider.availableHeight - height + slider.topPadding - ((slider.availableHeight - height) * (slider.position * 2)))
width: 15
height: 30
radius: 5
color: 'blue'
}
}
If you have a stranger shape, just change the function for y.
You can use any function that maps a value position (range [0, 1]) to an y-value. You can use every property of the slider, to calculate an appropriate position.
Here another example, where the slider draws the sine function:
Slider {
width: 400
height: 150
id: slider1
y: 200
handle: Rectangle {
x: slider1.leftPadding + slider1.visualPosition * (slider1.availableWidth - width)
y: slider1.topPadding + (slider1.availableHeight - height) / 2 + (slider1.availableHeight - height) / 2 * Math.sin(slider1.position * 10)
width: 15
height: 30
radius: 5
color: 'blue'
}
}
And here for the fun of it: A random function. But I don't think you can draw a fitting groove to it
Slider {
id: slider
width: 400
height: 150
y: 200
onPositionChanged: {
var a = Math.round(Math.random() * 5) - Math.round(Math.random() * 5)
console.log(a)
handle.handleY = handle.y + (a)
console.log(handle.handleY)
}
handle: Rectangle {
property int handleY: slider.topPadding + slider.availableHeight / 2 - height / 2
x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width)
y: Math.max(slider.topPadding, Math.min(handleY, slider.availableHeight - height + slider.topPadding))
width: 15
height: 30
radius: 5
color: 'blue'
}
}
You can do this directly in QML using Canvas or in Qt C++ (example with a circular slider) and then expose the element to QML. While not a direct copy-and-paste solution check this to see a custom circular progress bar that uses arc() to draw the element. You can use line() if you want straight lines. The behaviour of the slider is also something you will have to do on your own.
You can try to inherit from Slider to try and get some (or even most) of the functionality that your element requires.

Resources