How to make transparent part of image unselectable in Qt/QML - qml

I've loaded a PNG image into my QML code and enabled dragging on it.
Image {
source: "image.png"
width: 128
height: 128
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
The problem is that the whole image is selectable, even the transparent parts which I need to disable selecting on these areas. I even tried to make the Canvas from the image, but the problem remains.
Canvas {
width: 128
height: 128
Component.onCompleted: loadImage("image.png")
onImageLoaded: requestPaint()
onPaint: {
var ctx = getContext("2d")
var im = ctx.createImageData("image.png")
im.data[3] = 128
ctx.drawImage(im, 0, 0)
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
Here is the image which the only red part should be selectable:
Any idea?

You can get the image data with this method:
CanvasImageData getImageData(real sx, real sy, real sw, real sh)
Then you can check the pixel color values on the click position and decide whether or not to make a selection.
You can find additional info on how to access individual pixels here. It is for HTML canvas, but the QML canvas should be fully compatible.
...
MouseArea {
anchors.fill: parent
drag.target: parent
onClicked: {
var ctx = parent.getContext("2d")
// get the pixel
var imageData = ctx.getImageData(mouseX, mouseY, 1, 1)
// read the color data and decide whether to select or not
}
}

Related

QML Flickable scrollbar change color fills the whole handle

I have the following QML where I'm trying to change the scrollbar handle color. The color changes but it just fills the whole scrollbar and the handle does not move anymore while scrolling. Removing the color makes the scrollbar look correct again.
How can I change the color while keeping the handle moving
import QtQuick 2.15
import QtQuick.Controls
Flickable {
property bool scrollVisible;
property int columns : 3
id: slidesFrame
anchors.fill: parent
clip:true
flickDeceleration :10000
boundsMovement: Flickable.StopAtBounds
contentHeight: slidesFlowView.childrenRect.height + slidesFlowView.parent.anchors.margins
ScrollBar.vertical: ScrollBar {
id:control
onVisibleChanged: {
slidesFrame.scrollVisible = visible
}
contentItem: Rectangle {
implicitWidth: 10
color: control.pressed ? "#3C73AE" : "#424242"
}
background: Rectangle {
color:"#6D6D6D"
}
}
Rectangle{
color:"#1E1E1E"
anchors.fill: parent
anchors.margins: 12
Flow {
id: slidesFlowView
anchors.fill: parent
spacing: 14
Repeater{
model : GPresenter.slides
SlidePreview{
width: {
const scrollWidth = slidesFrame.scrollVisible ? control.width/slidesFrame.columns : 0
return (parent.width/slidesFrame.columns) - ((parent.spacing/slidesFrame.columns) * (slidesFrame.columns-1)) - scrollWidth
}
}
}
}
}
}
Here it is below without the color change looking correctly

How to rotate only the right or left part of an image

I want to simulate the closure of one page of a book.
Any suggestions on how to do that in qml?
Thanks in advance
Perhaps Flipable, in combination with the fillMode property of Image:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: window
width: 640
height: 480
visible: true
Image {
id: backgroundImage
source: "http://www.thebookdesigner.com/wp-content/uploads/2013/04/pages-vs-spreads.png"
anchors.centerIn: parent
Flipable {
id: flipable
anchors.fill: parent
anchors.leftMargin: parent.width / 2
property bool flipped: false
front: Image {
id: foldImage
source: backgroundImage.source
fillMode: Image.Pad
width: foldImage.implicitWidth / 2
horizontalAlignment: Image.AlignRight
}
back: Image {
source: backgroundImage.source
width: foldImage.implicitWidth / 2
fillMode: Image.Pad
horizontalAlignment: Image.AlignLeft
}
transform: Rotation {
id: rotation
origin.x: 0
origin.y: flipable.height / 2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle
}
states: State {
name: "back"
PropertyChanges {
target: rotation;
angle: -180
}
when: flipable.flipped
}
transitions: Transition {
NumberAnimation {
target: rotation
property: "angle"
duration: 1000
easing.type: Easing.InCubic
}
}
MouseArea {
anchors.fill: parent
onClicked: flipable.flipped = !flipable.flipped
}
}
}
}
Flipable does what its name suggests, and the fillMode property in combination with a width that is too small for the entire image allows you to "reposition" the contents of the image within the bounds of the item.
So, the front item contains the right side of the image, and the back item contains the left side. With an actual book that has many pages, you'd have to use the relevant pages instead of the same one.

(How) can i access relative position of qml element to main window

I have a qml element and want to show a (own) tooltip element as a new window right above this element. for this i need the absolute screen position to place the new window (AFAIK).
i got as far that the regular approach is to use "mapToItem" to get the relative position, but i cannot get to the "main window" - because the element in question is located within a "Loader" (which in this case is again located in another Loader).
So my question is: Is it possible to access the mainWindow from inside the dynamically loaded component, or is there maybe another easier way to anchor a new (tooltip) window right above an element ?
EDIT
mapToGlobal would probably work too, but i have to use qt 5.6.
i finally got it to work by setting the main window as a context property in c++:
this->qmlEngine->rootContext()->setContextProperty("mainWindow", this->root);
and in qml i can then access the main window position (on screen) and add the relative position the item has to the shown window like that:
tooltipWindow.setX(mainWindow.x +item1.mapToItem(item2,0,0).x )
The Window item has contentItem especially for that
[read-only] contentItem : Item
The invisible root item of the scene.
So you can refer to Window.contentItem as if it was Window:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: mainWindow
visible: true
width: 600
height: 300
Component {
id: testElement
Rectangle {
id: rect
width: 100
height: 100
color: "orange"
border { width: 1; color: "#999" }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: tooltip.show(true);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mapToItem(mainWindow.contentItem,mouse.x, mouse.y));
}
}
}
Item {
x: 40
y: 50
Item {
x: 80
y: 60
Loader {
sourceComponent: testElement
}
}
}
Rectangle {
id: tooltip
visible: false
width: 100
height: 20
color: "lightgreen"
border { width: 1; color: "#999" }
Text {
anchors.centerIn: parent
text: "I'm here"
}
function show(isShow) {
tooltip.visible = isShow;
}
function setPosition(point) {
tooltip.x = point.x - tooltip.width / 2;
tooltip.y = point.y - tooltip.height;
}
}
}
As for me I would reparent tooltip Item to hovered item itself at MouseArea.onEntered and so you can avoid position recalculation etc.:
onEntered: tooltip.show(true, rect);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mouse.x, mouse.y);
...
function show(isShow, obj) {
obj = (typeof obj !== 'undefined' ? obj : null);
if(obj !== null) {
tooltip.parent = obj;
}
tooltip.visible = isShow;
}
function setPosition(x, y) {
tooltip.x = x - tooltip.width / 2;
tooltip.y = y - tooltip.height;
}

Dynamically creating elements with text from a global property in QML

I am creating some Rectangles dynamically with a Text element inside like so:
Rectangle {
id: root
width: 640; height: 480
property var items: []
property int count
function push() {
var temp = Qt.createQmlObject("import QtQuick 2.3; Rectangle {width: 100; height: 30;color: 'yellow'; Text {text: count; anchors.centerIn: parent}}", root, "")
temp.x = Math.floor(Math.random()*200 + 1)
temp.y = Math.floor(Math.random()*200 + 1)
items[count] = temp
count++
}
MouseArea {
anchors.fill: parent
onClicked: push()
}
}
Now, whenever I call the push function by clicking, it creates a new rectangle with present value of count. But the problem is all rectangles created so far change their text to present value of count. I need to create rectangles with present value of count and they should not change their text when count changes afterwords. How can I do this? Thanks!
The Rectangles you are creating have this code :
Text {
text: count
anchors.centerIn: parent
}
Here, there is a binding between the text property and count. So whenever the countchanges, the text will reflect that change.
You need to escape the count in your string definition to actually concatenate the current value of count so that the code for the first item is :
Text {
text: '0'
anchors.centerIn: parent
}
You code shoud then be :
Rectangle {
id: root
width: 640; height: 480
property var items: []
property int count
function push() {
var temp = Qt.createQmlObject("import QtQuick 2.3; Rectangle {width: 100; height: 30;color: 'yellow'; Text {text: '"+count+"'; anchors.centerIn: parent}}", root, "")
temp.x = Math.floor(Math.random()*200 + 1)
temp.y = Math.floor(Math.random()*200 + 1)
items[count] = temp
count++
}
MouseArea {
anchors.fill: parent
onClicked: push()
}
}

Bounds of Flickable.contentY in QML

What is the correct way of asking the bounds of Flickable.contentY? I need that for a scrollbar.
Experimentally I have discovered that
offsetY <= contentY <= offsetY + contentHeight - height
where offsetY can be calculated as
var offsetY = contentY-Math.round(visibleArea.yPosition*contentHeight)
offsetY is zero at the start of the application and seems to be constant unless Flickable is resized.
This formula works in general, but there probably should be a dedicated function for it.
I did a scrollbar easily without offset :
// ScrollBar.qml
import QtQuick 2.0
Rectangle {
id: scrollbar;
color: "#3C3C3C";
visible: (flicker.visibleArea.heightRatio < 1.0);
property Flickable flicker : null;
width: 20;
anchors {
top: flicker.top;
right: flicker.right;
bottom: flicker.bottom;
}
Rectangle {
id: handle;
height: (scrollbar.height * flicker.visibleArea.heightRatio);
color: "#5E5E5E";
border {
width: 1;
color: "white";
}
anchors {
left: parent.left;
right: parent.right;
}
Binding { // Calculate handle's x/y position based on the content position of the Flickable
target: handle;
property: "y";
value: (flicker.visibleArea.yPosition * scrollbar.height);
when: (!dragger.drag.active);
}
Binding { // Calculate Flickable content position based on the handle x/y position
target: flicker;
property: "contentY";
value: (handle.y / scrollbar.height * flicker.contentHeight);
when: (dragger.drag.active);
}
MouseArea {
id: dragger;
anchors.fill: parent;
drag {
target: handle;
minimumX: handle.x;
maximumX: handle.x;
minimumY: 0;
maximumY: (scrollbar.height - handle.height);
axis: Drag.YAxis;
}
}
}
}
Just use it like this :
Flickable {
id: myFlick;
}
ScrollBar {
flicker: myFlick;
}
It can be moved with mouse, and moves automatically when Flickable is scrolled.