Button animation while pressed - qt

still quite new in QML.
What I wish to do (and could not find out it on google) is to have background animation (like progress bar, green color moving from left to right in interval od 2sec = onMyPressAndHold).
So far all is working, i have to hold button for 2 sec otherwise nothing happens... during this period of holding the button I would like to have the background button of progress bar moving (filling) from left to right...
So far tried few things i googled up, but no luck at all.
Button {
id: stopButton
text: 'STOP' // STOP
width: parent.width
height: 50
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
signal myPressAndHold()
onPressed: {
pressAndHoldTimer.start();
}
onReleased: {
pressAndHoldTimer.stop();
}
onMyPressAndHold: {
stopButton.text = 'PLEASE WAIT...';
textProgress.text = 'STOPPED';
elapsedTimer.stop();
DatabaseJS.push_start(mainScreen);
}
Timer {
id: pressAndHoldTimer
interval: 2000
running: false
repeat: false
onTriggered: {
parent.myPressAndHold();
}
}
}

It looks like you are trying to reproduce the behavior of an already existing item DelayButton:
DelayButton {
text: progress > 0 ? 'PLEASE WAIT...' : 'STOP'
delay: 2000
background: Rectangle {
// here you can define your background, bound to your button 'progress' value
// ...
}
onActivated: DatabaseJS.push_start(mainScreen)
}

Related

Create conditional button background color in onClicked - QT(qml)

My goal is that when the button is clicked, the button color changes to orange and stays orange until the result of the if condition is declared. Then I want the button color to be green if the condition is met and red if not. However, when I click the button, the button color does not change until the condition result is clear, and when the condition is certain, the button color becomes either green or red. So the button color never turns orange.
I have shown my button usage below;
Button
{
...
...
onClicked: {
background.color = "orange"
if(some_condition))
background.color = "green"
else
background.color = "red"
}
}
NOTE: I am sending data to a device in an if condition and waiting for its response(this takes about 1-2 seconds). I expect the button to be orange until the answer comes, green if my condition is true after the answer, and red if it is false. But the color of the button does not turn orange.
Do you have any suggestions?
To achieve this, property bindings must be used.
In this example, I'll use Image to demonstrate your loading thing.
For this example, I am providing two methods.
Method one
Thus, the plan is as follows.
I press the Button.
The image's source changes, and it goes to the loading state.
And the Button turns orange as well.
The Button then turns green if Image enters the ready state.
Or else, the Button turns red.
Grid {
columns: 1
Image {
id: img
width: 200; height: 200
fillMode: Image.PreserveAspectFit
}
Button {
palette.button: img.status == Image.Loading ? "orange" :
img.status == Image.Ready ? "green" :
img.status == Image.Error ? "red" : "#259bd7"
onClicked: img.source = "https://thispersondoesnotexist.com/image";
}
}
Method two
Similar to method one in most ways, but not declaratively.
I press the Button, and the Button turns green or red anytime the image status changes.
Grid {
columns: 1
Image {
id: img
width: 200; height: 200
fillMode: Image.PreserveAspectFit
onStatusChanged: {
if(status == Image.Ready) btn.palette.button = "green";
else if(img.status == Image.Error) btn.palette.button = "red";
}
}
Button {
id: btn
palette.button: "#259bd7"
onClicked: {
palette.button = "orange"
img.source = "https://thispersondoesnotexist.com/image";
}
}
}
Now, if for some weird reason, you want to do everything in the button, you can put a boolean property and change the color as it changes.
Like:
Image {
...
onStatusChanged: {
// some code and calculation
btn.condition = // true or false
}
}
Button {
...
property bool condition
onConditionChanged: {
if(condition) palette.button = "green";
else palette.button = "red";
}
...
}
Example.qml
Okay, so we now know what the answer is, well let's improve it (In the case that a lazy programmer wants to copy :D).
Image.status is an Enum with values ranging from 0 to 3.
Image {
id: img
width: 200; height: 200
fillMode: Image.PreserveAspectFit
}
Button {
id: btn
implicitHeight: 30
property list<Item> content: [
Item {},
Text { text: " ✓" },
BusyIndicator { running: true },
Text { text: " ✖" }
]
contentItem: Item {
width: parent.implicitWidth
Grid {
x: (parent.width - width)/2
spacing: 6
Text { text: "button" }
Control { contentItem: btn.content[img.status] }
Behavior on x { SmoothedAnimation {}}
}
}
onClicked: img.source = "https://thispersondoesnotexist.com/image";
}
Preview
BusyIndicator
The BusyIndicator in the example: https://github.com/SMR76/qml-snow-white/blob/master/SnowWhite/BusyIndicator.qml
https://github.com/Furkanzmc/QML-Loaders
In this answer, I manipulate Button background by setting it to a Rectangle and I control the Rectangle's color to iterate between (1) default button color, (2) orange, (3) red and (4) green. Hence, I identified there are 4 unique states as detailed as follows:
button hasn't been clicked yet, the button background color is the default color
button has been clicked, but the user hasn't typed anything, the button background changes to an "orange" color
the text has been entered, but the answer is wrong, the button color changes to "red" color
the text has been entered, and the answer is correct. the button background changes to "green" color
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
property bool started: false
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 10
Label {
visible: started
text: qsTr("What is 1+1?")
}
Frame {
Layout.fillWidth: true
visible: started
TextEdit {
id: answer
width: parent.width
}
}
Button {
background: Rectangle {
color: started
? answer.text
? answer.text === "2"
? "green"
: "red"
: "orange"
: palette.button
}
text: qsTr("Start")
onClicked: {
if (!started) {
started = true;
answer.text = "";
} else {
started = false;
}
}
}
}
}
You can Try it Online!
I found a tricky and simple solution as the Pressed state occurs before the Clicked state;
Button {
...
...
onPressed: {
background.color = "orange"
}
onClicked: {
if(some_condition))
background.color = "green"
else
background.color = "red"
}
}
Thus, as soon as I press the button, the button color becomes orange and after the condition result is clear, the button color becomes red or green depending on the condition.

QML SplitView auto collapse on handlebar mouse release

I have a QML Controls 2 SplitView and a redefined handle, which works well, but I want detect mouse release event on the handler, so I could collapse the SplitView under a certain threshold of width. Adding a MouseArea on top of the existing handle will absorb drag events, so I'm unable to move the handlebar. Any idea how could I gather the mouse release event, or any other solution which solves this problem?
Alright, I have created an example application. As you can see in this example, my MouseArea is marked with yellow and collapses the right view programmatically when double clicked, which is nice, but I also want to drag the handlebar and upon mouse release under a certain width threshold I want to collapse the view as well. The black part of the handlebar where my MouseArea is not covering the handlebar, responds to drag, but since there is no signal I can gather from it, the width threshold already set shouldCollapse boolean property, so the view won't update. Probably I could solve this issue with a timer, but I need a more sophisticated solution.
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 800
height: 400
visible: true
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
function toggleCollapse() { collapsibleRect.shouldCollapse = !collapsibleRect.shouldCollapse }
handle: Rectangle {
implicitWidth: 20
implicitHeight: 20
color: "black"
MouseArea {
anchors.centerIn: parent
width: parent.width
height: parent.height / 2
onDoubleClicked: splitView.toggleCollapse()
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
anchors.centerIn: parent
text: "Double click to collapse"
rotation: 90
}
}
}
}
Rectangle {
id: mainRect
color: "green"
SplitView.fillWidth: true
Text {
anchors.centerIn: parent
font.pixelSize: 24
text: "Main scene"
}
}
Rectangle {
id: collapsibleRect
property bool shouldCollapse: false
SplitView.preferredWidth: shouldCollapse ? 0 : 300
color: "purple"
clip: true
onWidthChanged: {
if(width < 200) shouldCollapse = true
else shouldCollapse = false
}
Text {
anchors.centerIn: parent
rotation: parent.shouldCollapse ? 90 : 0
font.pixelSize: 24
text: parent.shouldCollapse ? "SHOULD BE COLLAPSED" : "NOT COLLAPSED"
Behavior on rotation { NumberAnimation { duration: 100 } }
}
}
}
}
I had a similar problem and was able to solve it thanks to the hint of #Ponzifex that the SplitView's resizing property will be set to true as soon as the handle is clicked. Using a Timer I managed to detect whether the handle was quickly pressed twice in a row.
SplitView {
id: view
...
handle: Rectangle {
...
}
//============================================================
// double click behavior
Timer {
id: doubleClickTimer
interval: 300 // number of ms between clicks that should be considered a double click
}
property bool doubleClicked: false
// `resizing` will be set to true even if the handle is just pressed
onResizingChanged: {
if (view.resizing) {
if (!doubleClickTimer.running) {
doubleClickTimer.start();
return;
}
view.doubleClicked = true;
} else {
if (view.doubleClicked) {
// do any manual resizing in here
view.doubleClicked = false;
}
}
}
}
It is important to note, however, that it is only possible to resize the contents of a SplitView when resizing is false. That's why I need to have the doubleClicked helper property.
Add this to MouseArea:
onPressed: {
mouse.accepted = (mouse.flags & Qt.MouseEventCreatedDoubleClick);
}
propagateComposedEvents: true
cursorShape: Qt.SplitHCursor

Open and close additional window (QML)

Currently I have a window openning in the following way:
property variant win
Button {
id: testButton
MouseArea {
onClicked: {
var component = Qt.createComponent("test.qml");
win = component.createObject(testButton);
win.show();
}
}
}
Is it ok to create a window like this or there is a better way to do it (from QML, not from C++)?
When I close this additional window (just by clicking "x" button), I want to connect it to another event (for example, changing color of the button). How to do it?
Thanks.
It is usually nicer to have it more declarative. If you want your button to only open one window, the usage of a Loader might be right for you.
I think this is what you want, as you store it in one variable, and if you click the button multiple times, you would lose access to your instance. If you need a larger amount of Windows created by the same Button, you might use a ListModel and a Instantiator to create the instances.
With the Loader this might look like this:
Button {
id: ldbutton
onClicked: winld.active = true
Rectangle {
id: ldindic
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: height
color: winld.active ? 'green' : 'red'
}
Loader {
id: winld
active: false
sourceComponent: Window {
width: 100
height: 100
color: 'green'
visible: true
onClosing: winld.active = false
}
}
}
In this code is also already the answer to your second question: The signal you are looking for is called closing - connect to it to do what ever is necessary.
In the case of the Loader it is necessary to unload the window, so it can be loaded again later, maybe. If you have the window created by a Instantiator, you need to remove the corresponding index from the Instantiator's ListModel.
This might look like this:
Button {
id: rpbutton
onClicked: rpmodel.append({})
text: 'Open Windows ' + rpmodel.count
ListModel {
id: rpmodel
}
Instantiator { // from QtQml 2.0
model: rpmodel
delegate: Window {
width: 100
height: 100
color: 'blue'
visible: true
onClosing: rpmodel.remove(index)
}
}
}
In your code you could connect to it, either by using a Connection-object, that connects to your property win, or by changing the JS onClicked like so:
onClicked: {
var component = Qt.createComponent("test.qml");
win = component.createObject(testButton);
win.closing.connect(function() { console.log('do something') })
win.show();
}

Qt Quick QML MouseArea autorepeat on press

Hopefully I'm not missing something obvious here.
I am writing an app and have made a zoom in button with an Image and a MouseArea. I need the button to repeat a method call after, say, every second to zoom in while holding the mouse button down. It isn't entirely obvious how to make this repeat. Right now I have:
Rectangle {
id:zoomInBtn
Image {
id: zoomInImg
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: zoomIn.pressed ? ":/img/zoom_in_sel" : ":/img/zoom_in_unsel"
}
MouseArea {
id: zoomIn
anchors.fill: parent
onPressed: { cameraController.zoomIn(0.5); }
}
I have also tried with
onPressAndHold: { cameraController.zoomIn(0.5); }
which does basically the same, although with a small delay as expected, but I need to repeat this action every second while the mouse button is held.
To perform the task you need you must use a Timer. the timer must remain active while the containsMouse is active. you must also enable triggeredOnStart to run immediately if the timer is activated.
Rectangle {
id:zoomInBtn
Image {
id: zoomInImg
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: zoomIn.pressed ? ":/img/zoom_in_sel" : ":/img/zoom_in_unsel"
}
MouseArea {
id: zoomIn
anchors.fill: parent
}
Timer {
id: timer
interval: 1000
repeat: true
triggeredOnStart: true
running: zoomIn.containsMouse
onTriggered: cameraController.zoomIn(0.5) //task
}
}
}

How to set pressed=false for mouse area QML

I've faced with next situation:
I have an Item with MouseArea. This is my button on the screen - keyboard.
I persorm long press for button on keyboard.
I don't release this button (holding it).
In onPressed{} signal handler I'm opening another screen.
My buton from keyboard doesn't receive anymore Release signal.
When I come back to the previous keyboard - my button still pressed.
I've tried to set "pressed"=false, but this is readonly property.
I've tried to emit released() signal, but it doesn't clear the "pressed' flag.
But I think that I did it in wrong way. Function release takes parameter- mause - mouseEvent. The mouse parameter provides information about the click, including the x and y position of the release of the click, and whether the click was held. And I didn't find how to set it correct.
I've tried to update MouseArea, it doesn't help.
I don't know what else I have to try to unsed the "pressed" flag.
My mouse area is simple:
MouseArea {
id: mouseArea
property bool haveToRelease: false
onHaveToReleaseChanged: {
if(haveToRelease)
{
console.log("BaseButton.qml: call canceled()")
released()
haveToRelease = false
}
}
anchors.fill: parent
hoverEnabled: true
onReleased: {
console.log("BaseButton.qml: onReleased")
}
onPressedChanged: {
console.log("BaseButton.qml: onPressedChanged, pressed = ", pressed)
}
}
In my button I have the next handler:
onVisibleChanged: {
if(config.isToyota && !visible && pressed) {
console.log("Key.qml :: config.isToyota && !visible && pressed")
releaseButton = true
}
}
I will be vary glad if somebody help me to solve this issue!
Thanks a lot!
as for me that works as expected:
Window {
visible: true
width: 640
height: 480
Rectangle {
width: 100
height: 100
anchors.centerIn: parent
color: "orange"
MouseArea {
anchors.fill: parent
onPressed: {
console.log("pressed");
wnd.show();
released(mouse);
}
onReleased: {
console.log("released");
}
onPressedChanged: console.log("pressed: " + pressed );
}
}
Window {
id: wnd
width: 200
height: 200
}
}
The output:
qml: pressed
qml: released
qml: pressed: true
qml: pressed: false

Resources