QT frameless window resize snap - qt

I have a frameless window I am using MouseArea to allow it to be resizeable. It is working for the most part (I need to add some more resize handles). However, if I drag the window to the top or side on Windows it doesn't 'snap' like a framed one would to filling half the screen or filling the fullscreen. Is there a way with QML to handle this I've heard there is a WM_CHITTEST event I could potentially handle, but how would one be able to respond to a native event like this in QML?
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import "Themes"
ApplicationWindow
{
id: mainWindow
width: 640
height: 480
visible: true
color: Theme.primaryBackgroundColor
title: qsTr("SmartDraw")
flags: Qt.FramelessWindowHint | Qt.Window
readonly property real grabThickness: 4
header: Rectangle {
id: windowHeader
height: 38
width: parent.width
color: Theme.secondaryBackgroundColor
MouseArea {
id: windowResizeTopLeft
width: mainWindow.grabThickness
height: mainWindow.grabThickness
anchors.top: parent.top
anchors.left: parent.left
cursorShape: Qt.SizeFDiagCursor
property point lastMousePos: Qt.point(0,0)
onPressed: {
lastMousePos = Qt.point(mouse.x,mouse.y)
}
onMouseXChanged: {
var dx = (mouseX - lastMousePos.x)
mainWindow.x += dx
mainWindow.width -= dx
}
onMouseYChanged: {
var dy = (mouseY - lastMousePos.y)
mainWindow.y += dy
mainWindow.height -= dy
}
}
MouseArea {
id: windowResizeUp
height: mainWindow.grabThickness
anchors.top: parent.top
anchors.left: windowResizeTopLeft.right
anchors.right: minimize.left
cursorShape: Qt.SizeVerCursor
property real lastMousePosY: 0
onPressed: {
lastMousePosY = mouse.y
}
onMouseYChanged:
{
var dy = (mouseY - lastMousePosY)
mainWindow.y += dy
mainWindow.height -= dy
}
}
MouseArea {
id: windowDragArea
height: parent.height - mainWindow.grabThickness
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: minimize.left
property point lastMousePos: Qt.point(0, 0)
onPressed: { lastMousePos = Qt.point(mouseX, mouseY); }
onMouseXChanged: mainWindow.x += (mouseX - lastMousePos.x)
onMouseYChanged: mainWindow.y += (mouseY - lastMousePos.y)
}
Button {
id: minimize
width: 30
height: parent.height
anchors.right: maximize.left
onClicked: mainWindow.showMinimized()
background: Rectangle {
width: parent.width
height: parent.height
color: windowHeader.color
}
Rectangle {
color: "white"
height: 2
width: Math.round(parent.width*(2.0/3.0))
anchors.centerIn: parent
}
}
Button {
id: maximize
width: 30
height: parent.height
anchors.right: close.left
onClicked: mainWindow.visibility == Window.Maximized ? mainWindow.showNormal() : mainWindow.showMaximized()
background: Rectangle {
width: parent.width
height: parent.height
color: windowHeader.color
}
Rectangle {
color: "white"
width: 15
height: 15
}
}
Button {
id: close
width: 30
anchors.right: parent.right
height: parent.height
onClicked: Qt.quit()
background: Rectangle {
width: parent.width
height: parent.height
color: windowHeader.color
}
Text {
color: "white"
text: "X"
}
}
}
footer: Rectangle {
id: windowFooter
color: "#0e6afa"
height: 23
MouseArea {
id: windowResizeBottomLeft
width: mainWindow.grabThickness
height: mainWindow.grabThickness
anchors.left: parent.left
anchors.bottom: parent.bottom
cursorShape: Qt.SizeBDiagCursor
property point lastMousePos: Qt.point(0,0)
onPressed: {
lastMousePos = Qt.point(mouse.x,mouse.y)
}
onMouseYChanged:
{
var dx = (mouseX - lastMousePos.x)
var dy = (mouseY - lastMousePos.y)
mainWindow.x += dx
mainWindow.width -= dx
mainWindow.height += dy
}
}
MouseArea {
id: windowResizeBottom
x: mainWindow.grabThickness
height: mainWindow.grabThickness
anchors.bottom: parent.bottom
anchors.left: windowResizeBottomLeft.right
anchors.right: windowResizeBottomRight.left
cursorShape: Qt.SizeVerCursor
property real lastMousePosY: 0
onPressed: {
lastMousePosY = mouse.y
}
onMouseYChanged:
{
var dy = (mouseY - lastMousePosY)
mainWindow.height += dy
}
}
MouseArea {
id: windowResizeBottomRight
width: mainWindow.grabThickness
height: mainWindow.grabThickness
anchors.right: parent.right
anchors.bottom: parent.bottom
cursorShape: Qt.SizeFDiagCursor
property point lastMousePos: Qt.point(0,0)
onPressed: {
lastMousePos = Qt.point(mouse.x,mouse.y)
}
onMouseYChanged:
{
var dx = (mouseX - lastMousePos.x)
var dy = (mouseY - lastMousePos.y)
mainWindow.width += dx
mainWindow.height += dy
}
}
}
}

Best way to do this is to use startSystemResize which added to Qt QWindow since 5.15. You can use in different ways, but my specific way is like this :
MouseArea{
id : resizearea
width: 5
height: 5
anchors.bottom: parent.bottom
anchors.right: parent.right
cursorShape: Qt.SizeAllCursor
acceptedButtons: Qt.LeftButton
pressAndHoldInterval: 100
onPressAndHold:
{
mainview.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
}
I think this is the best way to resize frameless window in qml

Related

Creating a resizable Qml dialog

I am trying to make a resizable dialog:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.12
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Test")
Dialog {
id: dlg
x: 10
y: 10
width: 100
height: 100
visible: true
Rectangle {
anchors.fill: parent
color: "blue"
}
MouseArea {
height: 40
width: 40
anchors.right: parent.right
anchors.bottom: parent.bottom
Rectangle {
anchors.fill: parent
color: "red"
}
property real startX: 0
property real startY: 0
property real startWidth: 0
property real startHeight: 0
onPressed: {
startX = mouseX;
startY = mouseY;
startWidth = dlg.width;
startHeight = dlg.height;
}
function fnc_updatePos() {
if (pressed) {
var deltaX = mouseX-startX;
var deltaY = mouseY-startY;
dlg.width = startWidth + deltaX;
dlg.height = startHeight + deltaY;
}
}
onPositionChanged: fnc_updatePos()
}
}
}
The code resizes the dialog but the dialog flickers during dragging. The problem is that the mouse area is part of the dialog. How can the code be improved for proper scaling of the popup dialog?
Regards
I post the answer instead of deleting the question, just in case someone is stumbling upon the same problem.
mapToItem is the solution:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.12
ApplicationWindow {
id: mainWindow
width: 640
height: 480
visible: true
title: qsTr("Test")
Dialog {
id: dlg
x: 10
y: 10
width: 100
height: 100
visible: true
Rectangle {
anchors.fill: parent
color: "blue"
}
MouseArea {
height: 40
width: 40
anchors.right: parent.right
anchors.bottom: parent.bottom
Rectangle {
anchors.fill: parent
color: "red"
}
property real startX: 0
property real startY: 0
property real startWidth: 0
property real startHeight: 0
onPressed: {
var pos = mapToItem(mainWindow.contentItem, mouseX, mouseY)
startX = pos.x;
startY = pos.y;
startWidth = dlg.width;
startHeight = dlg.height;
}
function fnc_updatePos() {
if (pressed) {
var pos = mapToItem(mainWindow.contentItem, mouseX, mouseY)
//console.log(pos)
var deltaX = pos.x-startX;
var deltaY = pos.y-startY;
dlg.width = startWidth + deltaX;
dlg.height = startHeight + deltaY;
}
}
onPositionChanged: fnc_updatePos()
}
}
}

Implementing a slider in QML with a VU-meter like image as a background

I am trying to create a slider that I can move up and down with my mouse. However, I want to use my own image as the background. I am currently trying to implement this with a OpacityMask. Basically, I am trying to make the opacity 0 from where the handle is to the right end of the slider.
I would ordinarily just move a rectangle that is same color as the background over it. However, I want whatever element is under the slider to be displayed when the slider is pulled back.
How can I create this behavior?
import QtQuick 2.7
import QtQuick.Templates 2.0 as T
import QtGraphicalEffects 1.12
import "."
T.Slider {
id: control
implicitWidth: 200
implicitHeight: 26
handle: Rectangle {
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 15
radius: 5
color: control.pressed ? "#f0f0f0" : "#f6f6f6"
border.color: "gray"
}
background: OpacityMask {
anchors.fill: sliderImage
source: sliderImage
maskSource: mask
}
Image{
id: sliderImage
source: "./Jarvis2/images/volume_barH.png"
height: 20
width: parent.width
visible: false
}
Item{
id: mask
anchors.fill: sliderImage
Rectangle{
id: outer
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: control.visualPosition*parent.width
color: "gray"
opacity: 1
visible: false
}
Rectangle {
id: inner
color: "transparent"
z: 1
opacity: 1
anchors.left: outer.right
anchors.right: parent.right
anchors.top: outer.top
anchors.bottom: outer.bottom
visible: false
}
}
}
The slider at 100%:
The slider at around 70%:
The slider at around 24%
I think you can omit the OpacityMask and simply use a clipping Item:
Slider {
id: slider
width: 100
height: 300
orientation: Qt.Vertical
background: Item {
id: background
Item {
clip: true
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: (1 - slider.visualPosition) * slider.height
Rectangle { //Your image goes here
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: background.height
gradient: Gradient {
GradientStop { position: 0; color: "blue" }
GradientStop { position: 1; color: "red" }
}
}
}
}
}
You might have to fiddle a bit with the height of the clipping Item since there is some padding involved.
If you nevertheless want to use OpacityMask (because you want a different shape), you should put them together in the background:
Slider {
id: slider
width: 100
height: 300
orientation: Qt.Vertical
background: Item {
id: background
Rectangle { //Your image goes here
id: image
anchors.fill: parent
visible: false
gradient: Gradient {
GradientStop { position: 0; color: "blue" }
GradientStop { position: 1; color: "red" }
}
}
OpacityMask {
anchors.fill: parent
source: image
maskSource: mask
}
Item {
id: mask
visible: false
anchors.fill: parent
Rectangle {
radius: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: parent.height * slider.position
color: "white"
}
}
}
}

QML ListView::contentWidth is wider than actual content

Trying to implement ListView's content scroll by clicking on a button. When scrolling towards the end of the view ListView's content does not stop at the end of the last picture it overscrolls. Below I provided the minimum working example as well as the preview what goes wrong. Just change the .img path to make it work on your PC. I was looking for some help in sources of ListView and its inherited parent Flickable but nothing that could help to resolve the problem. How to make it stop at the end of the last picture?
import QtQuick 2.14
import QtQuick.Window 2.14
Window {
visible: true
width: 1024
height: 300
Item {
id: root
anchors.fill: parent
property var imagesUrlModel: ["file:///C:/Users/mikha/OneDrive/Изображения/toyota.jpg",
"file:///C:/Users/mikha/OneDrive/Изображения/toyota.jpg"
]
property int _width: 0
Component {
id: imageDelegate
Image {
id: image
sourceSize.height: 300
source: modelData
fillMode: Image.Stretch
}
}
Rectangle {
id: leftButton
anchors.top: root.top
anchors.bottom: parent.bottom
anchors.topMargin: 15
anchors.leftMargin: 10
anchors.left: parent.left
color: "green"
width: 25
MouseArea {
anchors.fill: parent
onClicked: {
listView.contentX = listView.contentX > 0
? listView.contentX - 50 > 0 ? listView.contentX - 50 : 0
: 0
}
}
}
Rectangle {
id: rightButton
anchors.top: root.top
anchors.bottom: parent.bottom
anchors.topMargin: 15
anchors.rightMargin: 10
anchors.right: parent.right
color: "green"
width: 25
MouseArea {
anchors.fill: parent
onClicked: {
listView.contentX = listView.contentX < listView.contentWidth
? listView.contentX + 50
: listView.contentWidth
//wrong content width
}
}
}
ListView{
id: listView
clip: true
boundsBehavior: Flickable.StopAtBounds
anchors.topMargin: 15
anchors.left: leftButton.right
anchors.right: rightButton.left
anchors.top: root.top
anchors.bottom: parent.bottom
spacing: 5
orientation: ListView.Horizontal
model: root.imagesUrlModel
delegate: imageDelegate
}
}
}
In your example just change listView.contentWidth to listView.contentWidth-listView.width in onClicked event for rightButton. But that's not enough. You should check whether the listView.contentX+50 is not overflowing listView.contentWidth-listView.width before you update the listView.contentX. In such case you need to update listView.contentX with difference between listView.contentWidth and listView.width.
Here it is:
listView.contentX = listView.contentX+50 <= listView.contentWidth-listView.width
? listView.contentX + 50
: listView.contentWidth - listView.width
I used another approach with repeater and scrollview and it has worked!
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Window {
visible: true
width: 1024
height: 300
Item {
id: contentItem
anchors.fill: parent
Rectangle {
id: rightButton
anchors.top: contentItem.top
anchors.bottom: contentItem.bottom
anchors.rightMargin: 10
anchors.right: contentItem.right
color: "green"
width: 25
MouseArea {
anchors.fill: parent
onClicked: {
var allowedWidth = scrollView.flickableItem.contentWidth - scrollView.viewport.width
if(row.width < scrollView.viewport.width){
return
}
var offset = scrollView.flickableItem.contentX + 50
if(offset <= allowedWidth){
scrollView.flickableItem.contentX += 50
}
else {
scrollView.flickableItem.contentX = allowedWidth
}
}
}
}
ScrollView {
id: scrollView
anchors.left: contentItem.left
anchors.right: rightButton.left
anchors.top: contentItem.top
anchors.bottom: contentItem.bottom
clip: true
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
property var imagesUrlModel: [
"file:///C:/Users/mikha/OneDrive/Изображения/toyota.jpg",
"file:///C:/Users/mikha/OneDrive/Изображения/toyota.jpg"
]
Row {
id: row
spacing: 15
Repeater {
id: repeater
anchors.left: parent.left
anchors.right: parent.right
model: scrollView.imagesUrlModel
delegate: Component {
id: imageDelegate
Image {
id: image
sourceSize.height: 300
source: modelData
fillMode: Image.Stretch
}
}
}
}
}
}
}

How to set visible flag correctly for Text item in QML based on a model count of ListView?

I have the following QML code where I show list view based on a model fetched from a C++ AbstractListModel.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.12
import QtQuick.Controls.Styles 1.4
import HackNews 1.0
import QtWebEngine 1.8
Item {
width: 640
height: 480
property alias dummyModel: kidsListView.model
ListView {
width: 100;
id: listView
anchors.left: parent.left
anchors.leftMargin: 20
anchors.top: parent.top
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
model: HackNewsModel
delegate: Rectangle
{
width: 100; height: 40
Button {
id: pushButton
anchors.fill: parent
anchors.margins:
{
right: 5
left: 5
top: 5
bottom: 5
}
text: "id "+model.id
style: ButtonStyle{
background: Rectangle {
implicitWidth: 100
implicitHeight: 25
border.width: control.activeFocus ? 2 : 1
border.color: "#888"
radius: 4
gradient: Gradient {
GradientStop { position: 0 ; color: control.pressed ? "#ccc" : "#eee" }
GradientStop { position: 1 ; color: control.pressed ? "#aaa" : "#ccc" }
}
}
}
onClicked:
{
dummyModel = HackNewsModel.get(model.listId).kids
}
}
}
}
Rectangle
{
id: rectangle
anchors.left: listView.right
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.margins:
{
left: 20
right: 20
top: 20
bottom: 20
}
Text {
id: kidsHeader
anchors.top: parent.top
anchors.left: parent.left
width: 100
height: 20
visible: (dummyModel.count!==0)?true:false
text: "kids"
}
ListView {
anchors.top: kidsHeader.bottom
anchors.left: parent.left
id: kidsListView
anchors.leftMargin: 10
model: ListModel{}
height: 50
delegate: Text {
text: modelData
}
ScrollBar.vertical: ScrollBar {}
}
}
}
The model can empty sometimes. When it's not empty, I would like to show a Text item otherwise I want to hide it. But somehow it's set visible forever when I have visible: (dummyModel.count!==0)?true:false or never when I have visible: (dummyModel.count>0)?true:false.
When model isn't empty it looks as the following.
But when the model isn't empty the text item doesn't get hidden.
Could you please tell me what am I doing wrong?
Thanks.
There is a property count available on the ListView as well, you should use that:
Text {
id: kidsHeader
anchors.top: parent.top
anchors.left: parent.left
width: 100
height: 20
visible: kids_listview.count !== 0 //Important change here
text: "kids"
}
ListView {
id: kids_listview
anchors.top: kidsHeader.bottom
anchors.left: parent.left
id: kidsListView
anchors.leftMargin: 10
model: ListModel{}
height: 50
delegate: Text {
text: modelData
}
ScrollBar.vertical: ScrollBar {}
}

QML : Drop-down menu that opens upward

I need to have drop-down that opens upwards on clicking on Menu button in QML.
I have tried to use the listview for the same, but in the implementation we are getting drop-down which opens downwards.
Any suggestions with reference to below snippet.
import QtQuick 1.1
Rectangle {
width: 800
height: 480
Rectangle {
id:comboBox
property variant items: ["Red", "Blue", "Green"]
signal comboClicked;
x: 651
y: 344
width: 141
height: 30;
z: 0
smooth:true;
Rectangle {
id:chosenItem
radius:4;
width:parent.width;
height:comboBox.height;
color: "#454b4d"
smooth:true;
Text {
anchors.top: parent.top;
anchors.margins: 8;
id:chosenItemText
x: 11
y: 5
color: "#ffffff"
text:"Menu";
anchors.topMargin: 5
anchors.left: parent.left
anchors.leftMargin: 12
font.family: "Arial"
font.pointSize: 14;
smooth:true
}
MouseArea {
width: 400
height: 30
anchors.bottomMargin: 0
anchors.fill: parent;
onClicked: {
comboBox.state = comboBox.state==="dropDown"?"":"dropDown"
}
}
}
Rectangle {
id:dropDown
width:comboBox.width;
height:0;
clip:true;
radius:4;
anchors.top: chosenItem.bottom;
anchors.margins: 2;
color: "lightblue"
ListView {
id:listView
height:500;
model: comboBox.items
currentIndex: 0
delegate: Item{
width:comboBox.width;
height: comboBox.height;
Text {
text: modelData
anchors.top: parent.top;
anchors.left: parent.left;
anchors.margins: 5;
}
MouseArea {
anchors.fill: parent;
onClicked: {
comboBox.state = ""
chosenItemText.text = modelData;
listView.currentIndex = index;
}
}
}
}
}
states: State {
name: "dropDown";
PropertyChanges { target: dropDown; height:30*comboBox.items.length }
}
transitions: Transition {
NumberAnimation { target: dropDown; properties: "height"; easing.type: Easing.OutExpo; duration: 1000 }
}
}
}
Try to change the anchors of the dropDown item:
that
anchors.top: chosenItem.bottom;
should become
anchors.bottom: chosenItem.top;

Resources