Span a Gradient on several Rectangle - qml

I want a single LinearGradient to be used as the background, spanned into several sibling Rectangles. In each Rectangle, you can see a different part of the gradient.
I use an OpacityMask to « fill » the Rectangles with the gradient.
The LinearGradient width is the sum of each Rectangle width.
If I use a single Rectangle, it works. Beginning with two, it does not behave correctly, whatever I try (I leave bits in the code below to imagine). The best result I had so far is having the same part of the LinearGradient repeated in each Rectangle.
I guess I could use one LinearGradient per Rectangle, changing the GradientStop values, but it looks complicated and I guess there is a simple an elegant solution.
Rectangle
{
id: page1
// anchors.fill: parent
// id: masqCont
anchors.centerIn: parent
border.color: "blue"
width: childrenRect.width
height: childrenRect.height
visible: false
Rectangle
{
id: masq1
y:0
border.color: "red"
border.width: 10
width: 100
height: 100
radius: 40
Text {text: "Un"}
visible: true
}
Rectangle
{
x:width
id: masq2
border.color: "red"
border.width: 10
width: 100
height: 100
radius: 40
Text {text: "deux"}
Text {text: "deux"}
visible: true
}
}
LinearGradient {
id:grad
width: 200 //masqCont.childrenRect.width
height: 100//masqCont.childrenRect.height
//anchors.fill: masqCont
start: Qt.point(0, 0)
end: Qt.point(200,100)//masqCont.width, masqCont.height)
gradient: Gradient {
GradientStop { position: 0.0; color: "white" }
GradientStop { position: 1.0; color: "black" }
}
visible: false
}
OpacityMask {
id: om21
anchors.fill: page1;
source: grad
maskSource: page1;
}
// OpacityMask {
// id: om21
// anchors.fill: masq1;
// source: grad
// maskSource: masq1;
// }
// OpacityMask {
// id: om22
// anchors.fill: masq2;
// source: grad
// maskSource: masq2;
// }
// }
// }

You were almost there.
The major problem is you chose a white Rectangle as a container for your child rectangles. This causes the entire LinearGradient to be displayed, as the mask is fully opaque (no alpha).
See below for a working example (you can move the rectangles by dragging them)
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
ApplicationWindow
{
visible: true
width: 500
height: 400
Item
{
id: page1
anchors.fill: parent
opacity: 0
Repeater
{
model: 20
Rectangle
{
id: masq1
x:Math.random()*(page1.width-width)
y:Math.random()*(page1.height-height)
width: 50
height: 50
radius: 5
MouseArea
{
anchors.fill: parent
drag.target: parent
}
}
}
}
LinearGradient {
id:grad
anchors.fill: page1
start: Qt.point(0, 0)
end: Qt.point(page1.width, 0)
gradient: Gradient {
GradientStop { position: 0.0; color: "white" }
GradientStop { position: 1.0; color: "black" }
}
visible: false
}
OpacityMask {
anchors.fill: page1;
source: grad
maskSource: page1;
}
}

If it is just Rectangles you want, OpacityMask is an overkill.
You might just use a ShaderEffectSource instead.
You set the soruceItem to be your gradient, and then select the sourceRect to be equal to you Rectangle. You might need to use map[From/To]Item to map the coordinates correctly.
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: win
visible: true
width: 400
height: 400
Rectangle {
id: grad
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0; color: 'blue' }
GradientStop { position: 0.33; color: 'red' }
GradientStop { position: 0.66; color: 'yellow' }
GradientStop { position: 1; color: 'blue' }
}
visible: false
}
ShaderEffectSource {
sourceItem: grad
x: 50
y: 50
height: 100
width: 200
sourceRect: Qt.rect(x, y, width, height)
}
ShaderEffectSource {
sourceItem: grad
x: 280
y: 80
height: 150
width: 70
sourceRect: Qt.rect(x, y, width, height)
}
}

Related

Computing linear gradients by section so as to mimic the effect of a larger one

Here is my issue. I need a see through hole (square one) in my application. The problem is that my real application contains a gradient over its entire background. To simulate this problem I've written a MWE.
In order to create the hole, I need to create 4 rectangles that I will fill with the proper gradient so as to simulate the over all gradient shown in the code.
Like so:
I'm asking for help on how to write the gradient description in each of the rectangles (1 through 4) so that they would look exactly the same as they do in the picture which is showing 1 big gradient.
import QtQuick
import QtQuick.Controls
import QtQuick.Window
import Qt5Compat.GraphicalEffects
ApplicationWindow {
width: 1920
height: 1080
visible: true
title: qsTr("Render Server Development Helper App")
color: "transparent"
Rectangle {
id: mainRect
anchors.fill: parent
LinearGradient {
anchors.fill: parent
start: Qt.point(0, 0)
end: Qt.point(width,height)
gradient: Gradient {
GradientStop { position: 0.0; color: "#ff0000" }
GradientStop { position: 0.034; color: "#ff0000" }
GradientStop { position: 0.7792; color: "#0000ff" }
GradientStop { position: 1.0; color: "#0000ff" }
}
}
Column {
id: buttonRow
width: 0.1*parent.width
anchors.left: parent.left
anchors.top: parent.top
Button {
id: btnConnect
hoverEnabled: false;
width: parent.width
text: "CONNECT"
onClicked: {
console.log("Connecting ....");
}
}
}
Rectangle {
id: frame
width: parent.width*0.7;
height: parent.height*0.8;
border.color: "#000000";
border.width: 5;
//anchors.centerIn: renderServerView
anchors.centerIn: parent
color: "transparent"
}
}
}
I would use the same approach as we discussed here and avoid complicated gradient stitching.
Be aware that translucent windows only work on systems that have compositing enabled. Have a look here.
Canvas
import QtQuick
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
color: "transparent"
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
var gradient = ctx.createLinearGradient(0, 0, root.width, root.height)
gradient.addColorStop(0.0, "red")
gradient.addColorStop(1.0, "blue")
ctx.fillStyle = gradient
ctx.beginPath()
ctx.fillRect(0, 0, root.width, root.height)
ctx.fill()
ctx.clearRect(220, 140, 200, 200)
}
}
}
OpacityMask
import QtQuick
import Qt5Compat.GraphicalEffects
Window {
id: root
width: 640
height: 480
visible: true
color: "transparent"
LinearGradient {
id: bg
visible: false
anchors.fill: parent
start: Qt.point(0, 0)
end: Qt.point(root.width, root.height)
gradient: Gradient {
GradientStop { position: 0.0; color: "red" }
GradientStop { position: 1.0; color: "blue" }
}
}
Item {
id: cutout
visible: false
anchors.fill: parent
Rectangle {
width: 200
height: 200
anchors.centerIn: parent
}
}
OpacityMask {
anchors.fill: bg
source: bg
maskSource: cutout
invert: true
}
}

QML ListView delegate with Component Row does't render correctly

And now I have a problem with my qml script. here's the simple code:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: root
anchors.fill: parent
color: "green"
SystemPalette { id: activePalette }
ColumnLayout {
id: rightPanel
Layout.fillHeight: true
Layout.fillWidth: true
Layout.rightMargin: 10
anchors.fill: parent
Component.onCompleted: {
console.log(this, width, parent, parent.width)
}
ListView {
spacing: 2
Layout.fillHeight: true
Layout.fillWidth: true
model: ListModel {
ListElement {
name: "nihao"
value: "1"
}
ListElement {
name: "fds"
value: "2"
}
ListElement {
name: "fdssd"
value: "4"
}
}
delegate: Component {
// Rectangle {
// color: "yellow"
// height: 40
// width: parent.width
Row {
anchors.fill: parent
spacing: 2
height: 40
width: 200
Rectangle {
color: activePalette.window
height: 25
width: 100
border.color: "white"
border.width: 3
Text {
anchors.centerIn: parent
text: name
}
}
Rectangle {
color: activePalette.window
height: 25
width: 100
border.color: "white"
border.width: 3
Text {
anchors.centerIn: parent
text: value
}
}
}
//}
}
}
}
}
}
The code does't display correctly when I use qmlscene to render it, it even render nothing if the ListModel is too long.
But, if I uncomment out the "Rectangle" code in delegate component, it works well. So I'm comfused with the difference between Reactangle and Row for that they are all inherited from Item. And what should be placed into the delegate component as its direct child?

QML: Problems with mousearea overlapping

I have a QML application and problems with MouseAreas.
In a small test app, there is a red rectangle and when mouse enters this rect, a grey menu appears below (created with a Loader).
This grey menu must be open while mouse is over the red rectangle or the menu. For this purpose, I have 2 MouseAreas, 1 over the red rect and 1 over the menu.
Both are 'hoverEnabled' and with 'enter' and 'exit' I control 'hoverDialog' and 'hoverTopZone'.
When both are false, it means that the mouse is out, so I close the menu (using a signal, the Loader gets inactive).
The timer is required since when passing from 'mouseAreaTopZone' to 'mouseAreaDialog' there is just a moment with 'hoverDialog' and 'hoverTopZone' are both false.
Fixed with the timer.
In the middle of the menu there is a green rect, and (only) when mouse is over there, a yellow rect must be visible.
There is my problem. I have a MouseArea inside the green rect, but the yellow rect is not visible when required.
If I move 'rectGreen' below 'mouseAreaTopZone' and 'mouseAreaDialog' (that is, at the end of the file) I get the yellow rect visible when the mouse is over green rect, since its mouse area is then 'topmost'
BUT in this case, the menu dialog is closed, since when the mouse enters the MouseArea inside green rect, hoverDialog and hoverTopZone are false...
I hope U can understand my problem... Here is my code:
Test.qml
import QtQuick 2.5
import QtQuick.Controls 1.3
import QtQuick.Window 2.0
Item {
width: 800
height: 800
Rectangle{
id: rect
anchors { top: parent.top; topMargin: 100; horizontalCenter: parent.horizontalCenter }
height: 50; width: 50
color: "red"
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: loader_dialog.active = true
}
}
Loader {
id: loader_dialog
anchors { top: rect.bottom; horizontalCenter: rect.horizontalCenter}
active: false
sourceComponent: TestMenu {
onClose: loader_dialog.active = false;
}
}
}
TestMenu.qml
import QtQuick 2.0
Rectangle {
id: id_dialog
signal close()
width: 400
height: 600
color: "lightgrey"
property bool hoverDialog: false
property bool hoverTopZone: false
function update() {
if (!hoverDialog && !hoverTopZone)
timer.start();
}
function check() {
if (!hoverDialog && !hoverTopZone)
{
console.log("close");
id_dialog.close();
}
}
Timer {
id: timer
interval: 100
running: false
repeat: false
onTriggered: check();
}
Rectangle {
id: rectGreen
width: 200; height: 100
anchors.centerIn: parent
color: "green"
Rectangle {
id: rectYellow
anchors.centerIn: parent
width: 50; height: 50
color: "yellow"
visible: false
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: { rectYellow.visible = true; }
onExited: { rectYellow.visible = false }
}
}
MouseArea {
id: mouseAreaTopZone
anchors { bottom: parent.top; horizontalCenter: parent.horizontalCenter}
width: 50; height: 50
hoverEnabled: true
onEntered: { hoverTopZone = true; id_dialog.update(); }
onExited: { hoverTopZone = false; id_dialog.update(); }
}
MouseArea {
id: mouseAreaDialog
anchors.fill: parent
hoverEnabled: true
onEntered: { hoverDialog = true; id_dialog.update(); }
onExited: { hoverDialog = false; id_dialog.update(); }
}
}
Thanks in advance,
Diego
Thanks Mark Ch for your help.
I need to close the dialog when the mouse exits, so I think I can not use 'Popup' control...
I solved the problem. Using only one variable to know if the mouse is over my dialog ('m_iNumHovered'), I add a reference every time I enter in a Mouse Area, and I decrease it when I exit. The key was to add/remove a reference in the MouseArea over the green rectangle, to keep it 'm_iNumHovered=true' (dialog visible)
New code for TestMenu.qml:
import QtQuick 2.0
Rectangle {
id: id_dialog
signal close()
width: 400
height: 600
color: "lightgrey"
property int m_iNumHovered: 0
onM_iNumHoveredChanged: update();
function update() {
if (m_iNumHovered == 0)
timer.start();
}
function check() {
if (m_iNumHovered == 0)
id_dialog.close();
}
Timer {
id: timer
interval: 100
running: false
repeat: false
onTriggered: check();
}
MouseArea {
id: mouseAreaTopZone
anchors { bottom: parent.top; horizontalCenter: parent.horizontalCenter}
width: 50; height: 50
hoverEnabled: true
onEntered: m_iNumHovered++;
onExited: m_iNumHovered--;
}
MouseArea {
id: mouseAreaDialog
anchors.fill: parent
hoverEnabled: true
onEntered: m_iNumHovered++;
onExited: m_iNumHovered--;
}
Rectangle {
id: rectGreen
width: 200; height: 100
anchors.centerIn: parent
color: "green"
Rectangle {
id: rectYellow
anchors.centerIn: parent
width: 50; height: 50
color: "yellow"
visible: false
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: { m_iNumHovered++; rectYellow.visible = true; }
onExited: { m_iNumHovered--; rectYellow.visible = false }
}
}
}

How to highlight the clicked (by mouse) element of a delegate w.r.t FolderListModel?

import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate: Text { id: h; text: fileName }
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
This works only when I use keyboard. How to make it work on mouse clicks?
To react on mouse events you need to place MouseArea item.
In the sample below (being an expanded version of the code you provided) I have added a MouseArea to the delegate item that upon being clicked sets the ListView's currentIndex to the delegate's index (a special property visible in the ListView's delegate).
import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate:
Text {
id: h;
text: fileName
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
As an alternative approach you might try placing a single MouseArea filling the whole ListView and use ListView's indexAt(int x, int y) method to check which delegate was clicked. However, you would need to care about more edge-conditions in such case.

Scale Element in StateELement

I can write lines below and got it work:
states: State {
name: "active"; when:myitem.activeFocus;
PropertyChanges { target: myitem; z:1000; scale: 1.2 }
}
transitions: Transition {
NumberAnimation { properties: scale; duration: 1000 }
}
But in these lines i can not give specific origin to scale property!
I found Scale Element
transform: Scale { origin.x: 25; origin.y: 25; xScale: 3}
How can i inject this into state property above, because i want to use "when" property of state,
i want scaling to run on that "when" condition.
Or is there any other way to scale in a condition with specifying origins?
Thanks for any idea.
You should set an id for the Scale element. Then you can change its properties in the "active" state.
Here a minimal working example:
import QtQuick 1.0
Item {
height: 200; width: 500
Rectangle {
id: myitem
height: 10; width: 100
anchors.centerIn: parent
color: "blue"
transform: Scale { id: scaleTransform; origin.x: 25; origin.y: 25 }
states: State {
name: "active"; when: mouseArea.pressed
PropertyChanges { target: scaleTransform; xScale: 3 }
}
transitions: Transition {
NumberAnimation { property: "xScale"; duration: 1000 }
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
Another possibility without states could be:
import QtQuick 1.0
Item {
height: 200; width: 500
Rectangle {
id: myitem
height: 10; width: 100
anchors.centerIn: parent
color: "blue"
transform: Scale {
id: scaleTransform
origin.x: 25; origin.y: 25
xScale: mouseArea.pressed ? 3 : 1 // <-
Behavior on xScale { // for animation
NumberAnimation { duration: 1000 }
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
xScale is set to 3 when mouse is pressed, else to 1 (if you don't know the " ternary operation" look here).