QML: Move the rectangle outside the window - qt

Is it possible to move the rectangle outside the window? The only thing I came up with is to write custom logic that will resize the top window when moving the rectangle outside the window.
Current behavior (imgur .gif):
Current behavior
Desired behavior (imgur .png):
Desired behavior
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: root
width: 300
height: 500
visible: true
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
color: "#00000000"
Rectangle {
id: draggable
color: "blue"
x: 100
y: 100
width: 100
height: 100
MouseArea {
anchors.fill: parent
property real lastMouseX: 0
property real lastMouseY: 0
onPressed: {
lastMouseX = mouseX
lastMouseY = mouseY
}
onMouseXChanged: {
draggable.x += (mouseX - lastMouseX)
}
onMouseYChanged: {
draggable.y += (mouseY - lastMouseY)
}
}
}
Rectangle {
color: "blue"
x: 100
y: 300
width: 100
height: 100
// ...
}
}

Windows can be children of other Windows. The Window behavior is still subject to certain platform-dependent behavior, but on a Desktop environment child Windows should still be able to move outside the parent Window. So simply changing your Rectangle to be a Window will give you the desired effect.
Window {
id: root
Window {
id: draggable
...
}
}

Related

qml components disappearing after enabeling layers

I have a Component for an sddm theme. At the moment I use the theme dark sugar as the base theme. The component looks like the following:
Item {
id: hexagon
property color color:"yellow"
property int radius: 30
//layer.enabled: true
//layer.samples: 8
Shape {
//... Here some Positioning and other Stuff
ShapePath {
//... Here some Options and Pathlines
}
}
}
This works fine, but as soon as I uncomment both layer settings the component disappears. Does this happen, because I load the component like this:
Pane {
...
Item {
...
MyComponent {
z: 1
}
}
}
Nor the Pane or the Item use layer but most Components in the Item use the z: 1 property.
As iam_peter says, the default width and height properties of any Item are 0, and layer.enabled sets the size of the offscreen texture to the item size. By default, the scene graph doesn't do any clipping: a child item can populate scene graph nodes outside its parent's bounds. But when you confine the children's rendering to a specific offscreen texture, anything that doesn't fit is clipped. Here's a more interactive example to play with this:
import QtQuick
import QtQuick.Controls
Rectangle {
width: 640
height: 480
Column {
CheckBox {
id: cbLE
text: "layer enabled"
}
Row {
spacing: 6
TextField {
id: widthField
text: layerItem.width
onEditingFinished: layerItem.width = text
}
Label {
text: "x"
anchors.verticalCenter: parent.verticalCenter
}
TextField {
id: heightField
text: layerItem.height
onEditingFinished: layerItem.height = text
}
}
}
Rectangle {
id: layerItem
x: 100; y: 100
border.color: "black"; border.width: 2
layer.enabled: cbLE.checked
Rectangle {
width: 100
height: 100
color: "tomato"
opacity: 0.5
}
Text {
text: "this text will get clipped even when layer size is defined"
}
}
}
You can use renderdoc to see how the rendering is done; for example you can see the texture that is created by enabling the layer.
This is a small reproducible example:
import QtQuick
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
//width: 200
//height: 200
//layer.enabled: true
Rectangle {
width: 100
height: 100
color: "red"
}
}
}
I suspect that if you don't set a size on the Item on which you want to enable the layer (layer.enabled: true), it will have a size of 0. Hence the offscreen buffer has a size of 0.
As a side note, this works without layer, because the clip property of an Item by default is set to false. So it won't clip to the bounds of its parent.

How to make an Item draggable without flicker when parent's position changes during drag

I'm trying to make an item that can be resized by its edges.
For showing a minimal testcase of the problem it is enough to have its left edge draggable, so here it is:
Rectangle {
id: root
border.width: 1
border.color: 'black'
color: 'red'
// save original position and size at drag start
property real origX: 0
property real origWidth: 0
// drag this item:
Item {
id: dragDummy
x: 0
onXChanged: {
root.x = root.origX + x
root.width = root.origWidth - x
}
}
MouseArea {
anchors.fill: root
drag.target: dragDummy
drag.axis: Drag.XAxis
drag.onActiveChanged: {
// onDragStarted -> Cannot assign to non-existent property "onDragStarted" ???
if(!active) return
root.origX = root.x
root.origWidth = root.width
}
}
}
the problem seems to be that if drag causes parent position to change, that triggers another drag event, causing this flicker:
I'm guessing MouseArea can't help here? Then low level mouse events should be used like in "old-school" apps (i.e. capturing events at root Item, manually compute offset with respect to initial mouse down position, etc...)?
(or I have to move the MouseArea to an ancestor that won't move during drag, which is almost the same...)
There is a nice QML Item type called DragHandler which people often overlook, but I find that it works very well.
This solution is a little more idiomatic than other suggestions in that it uses a declarative style rather than imperative:
import QtQuick 2.15
Item {
id: root
width: 500
height: 100
Item {
height: 100
width: handle.x + handle.width / 2
}
Rectangle {
x: handle.x + handle.width / 2
width: root.width - (handle.x - handle.width/2)
height: 100
border{
width: 1
color: 'black'
}
color: 'red'
}
Item {
id: handle
x: -width / 2
width: 50
height: 100
DragHandler {
yAxis.enabled: false
xAxis{
minimum: -handle.width
maximum: root.width
}
}
}
}
The solution I come up with consists of having two MouseAreas:
a MouseArea moves with the item to drag, that is used only for hit-testing, so its onPressed handler is something like this:
onPressed: (mouse) => {
mouse.accepted = false
root.container.myDragTarget = root
}
onReleased: root.container.myDragTarget = null
another MouseArea, stacked below the others and not moving, handles the mouse position change and the dragging:
onPressed: _start = Qt.point(mouseX, mouseY)
onPositionChanged: {
if(myDragTarget) {
var delta = Qt.point(mouseX - _start.x, mouseY - _start.y)
// do any rounding/snapping of delta here...
_start.x += delta.x
_start.y += delta.y
myDragTarget.x += delta.x
myDragTarget.y += delta.y
}
}
This is able to drag the item reliably.
This is also what I wanted to avoid, because it reinvents mouse drag, but in absence of a better solution it is what I am going to use.
I won't accept this answer as I'm curious to see other ways to approach this problem.
You can workaround the movement and new positioning of the dragged Item by mapping the coordinates with the mapToItem functions.
In my solution, I've not used the drag functionality of the MouseArea as it needs a drag.target. I've used the pressed and position changed signals to implement drag behavior. The only downside is the background Item which is needed for the mapToItem function as it doesn't accept the Window due to it not being an Item.
import QtQuick
import QtQuick.Window
import QtQuick.Shapes
Window {
id: root
visible: true
width: 400
height: 400
Item {
id: background
anchors.fill: parent
Rectangle {
id: rectangle
property int rightX
x: 50
y: 50
width: 200
height: 80
border.width: 1
border.color: "black"
color: "red"
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 10
color: mouseArea.containsMouse || mouseArea.pressed ? "#ff808080" : "#aa808080"
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onPressed: rectangle.rightX = rectangle.x + rectangle.width
onPositionChanged: function(mouse) {
if (mouseArea.pressed) {
var tmp = mouseArea.mapToItem(background, mouse.x, 0)
if (tmp.x <= rectangle.rightX)
rectangle.x = tmp.x
else
rectangle.x = rectangle.rightX
rectangle.width = rectangle.rightX - rectangle.x
}
}
}
}
}
}
}

How to keep the top-right position of QML item when its size is changing?

I have a toolbar that can be moved (by drag). Depending on the context the content of this toolbar will change, and its size will change accordingly.
My problem is, when the size is changing, the top-left position remains the same and the right border is moving (default and normal behaviour). But I want the top-right position to remain the same and the left border to move instead.
From screen 1 to 2 the toolbar gets smaller, and is shown like the blue rectangle. I want it to be placed like the red rectangle.
How can I achieve this ? Without anchoring on the right of the screen, because the toolbar is movable.
The first thing that comes to mind would be to wrap the toolbar in an Item, and anchor the toolbar to the top right of the item.
import QtQuick 2.8
import QtQuick.Controls 2.3
ApplicationWindow {
id: window
width: 800
height: 800
visible: true
Slider {
id: slider
value: 200
to: 400
}
Item {
x: 600
ToolBar {
id: toolBar
anchors.top: parent.top
anchors.right: parent.right
implicitWidth: slider.value
MouseArea {
anchors.fill: parent
drag.target: toolBar.parent
}
}
}
}
The Item doesn't render anything itself, and has a "zero" size so that the ToolBar is anchored correctly.
Edit: thanks to #GrecKo for coming up with the MouseArea idea. :) This allows you to drag the ToolBar.
A simple solution is to readjust the position of the item when the width changes:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
Window {
visible: true
width: 640
height: 480
Slider {
id: slider
value: 200
to: 400
}
Rectangle {
id: block
color: "red"
width: parseInt(slider.value)
height:50
x: 100
y: 50
readonly property int previousWidth: width
onWidthChanged: {
block.x += previousWidth - width
}
MouseArea {
anchors.fill: parent
drag.target: block
}
}
}
Since onWidthChanged is called before the previousWidth property change, you can easily adjust the x position from previous and new width values.
(Edit: improved my example using #Mitch Slider)
You can do that with Behavior and PropertyAction.
This relies on the feature that you can specify the point in a Behavior when its linked property actually change. You can then add some logic before and after this effective change:
import QtQuick 2.8
import QtQuick.Controls 2.3
ApplicationWindow {
id: window
width: 800
height: 800
visible: true
Slider {
id: slider
value: 200
to: 400
}
Rectangle {
id: rect
width: slider.value
y: 40
height: 40
color: "orange"
Behavior on width {
id: behavior
property real right
SequentialAnimation {
ScriptAction { script: behavior.right = rect.x + rect.width } // the width of the rectangle is the old one
PropertyAction { } // the width of the rectangle changes at this point
ScriptAction { script: rect.x = behavior.right - rect.width } // the width of the rectangle is the new one
}
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}

How to transform the center of a QQuickItem to a new center

I have a Qt QML application. Following is the complete code of the application:
Code:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: app_main_window
visible: true
width: 800
height: 600
title: qsTr("Hello QML")
Rectangle {
id: my_item_1
x: 100
y: 100
width: 100
height: 100
color: "grey"
visible: true
z: 1
}
Rectangle {
id: my_item_2
x: 400
y: 400
width: 100
height: 100
color: "grey"
visible: true
z: 1
MouseArea {
anchors.fill: parent
onClicked: {
// How can I change the center of my_item_1 to some arbitrary new center. lets say app_main_window's center. this involves another question. how can I get app_main_window's center?
}
}
}
}
Question:
My question is simple. How can change the center of any QQuickitem to a new center? So in above case how can change the center of my_item_1 to a new center (Qt.point(some_x_center, some_y_center)) when my_item_2 gets a click event.
Additionally, is possible to get another item's center? Like app_main_window or my_item_2's center to apply to the target my_item_1?
PS:
I have made the code simple to make the question objective. I have quite a complicated logic in my actual code where I need to realign something like my_item_1 to a new center without usage of anchors as the QQuickitem I am trying to do that with is scaled and panned into a new point.
If anchors can't be used, you have to calculate manually the center and assign it.
Quick example:
Item
{
anchors.fill: parent
Rectangle
{
id: nonParentOrSibling
width: 200
height: 200
x: 350
y: 240
color: "red"
}
}
Rectangle
{
id: rectToMove
width: 100
height: 100
y: 200
color: "blue"
MouseArea
{
anchors.fill: parent
onClicked:
{
var itemCenter = Qt.point(nonParentOrSibling.x + nonParentOrSibling.width / 2, nonParentOrSibling.y + nonParentOrSibling.height / 2)
rectToMove.x = itemCenter.x - rectToMove.width / 2
rectToMove.y = itemCenter.y - rectToMove.height / 2
}
}
}
Note that this is a one time moving only, and the rectangle won't move if the target is moved.
To make it follow the target you have to rebind it with Qt.binding.
If rectToMove is not in the same coordinate system as nonParentOrSibling, you can use Item.mapToItem() and mapFromItem() to adapt the coordinates.

Creating a horizontal Tumbler or circular SwipeView in QML

I'd like to replace a SwipeView with an Tumbler in QML as I prefer the notion, that there is no first and no last Item.
The problem is, I can't find any other way to get this Tumbler tumble horizontally instead of vertically, but to rotate it by -90° and then rotate the Items back by +90°
This is my code so far, and works as expected:
import QtQuick 2.5
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
Window {
id: root
visible: true
width: 640
height: 480
Row {
id: buttons
spacing: 2
Button {
text: '0'
onClicked: tumbl.currentIndex = 0
}
Button {
text: '1'
onClicked: tumbl.currentIndex = 1
}
Button {
text: '2'
onClicked: tumbl.currentIndex = 2
}
Button {
text: '3'
onClicked: tumbl.currentIndex = 3
}
}
Tumbler {
id: tumbl
rotation: -90 // <---- Rotate there
anchors {
top: buttons.bottom
left: buttons.left
right: buttons.right
bottom: parent.bottom
}
model: 4
delegate: Rectangle {
rotation: 90 // <---- Rotate back
color: 'red'
border.width: 15
Text {
anchors.centerIn: parent
text: index
}
}
visibleItemCount: 1
Component.onCompleted: contentItem.interactive = false
}
}
You can see the two lines, in which I do the rotation marked with a comment.
Does anybody know a way to either produce this circular behavior with a SwipeView or to change the tumble-orientation of the tumbler without this rotation trick?

Resources