The look and feel I'm trying to go for is to have a solid color button, and text on it like "Hello World" where the text is completely transparent, and the background shows through the button.
In other words, having text as a transparency mask on a button element.
Here is one way to do it:
// TB.qml
MouseArea {
width: txt.contentWidth + 20
height: txt.contentHeight + 10
property alias text: txt.text
property alias color: sh.color
ShaderEffect {
id: sh
anchors.fill: parent
property color color: "red"
property var source : ShaderEffectSource {
sourceRect: Qt.rect(0, 0, sh.width, sh.height)
sourceItem: Item {
width: sh.width
height: sh.height
Text {
id: txt
anchors.centerIn: parent
font.bold: true
font.pointSize: 30
text: "test"
}
}
}
fragmentShader:
"varying highp vec2 qt_TexCoord0;
uniform highp vec4 color;
uniform sampler2D source;
void main() {
gl_FragColor = color * (1.0 - texture2D(source, qt_TexCoord0).w);
}"
}
}
Using it:
TB {
text: "HELLO WORLD!!!"
color: "red"
onClicked: console.log("hi world")
}
Result:
The button is red, the text is grey from the grey background, and it will accurately show anything that's beneath the button.
Obviously, the button is rudimentary, but the example outta be enough to get you going and implement something according to your needs.
The key element here is the custom shader, which is a very basic one - it colorizes every fragment and applies the mask as alpha. Obviously, you can use ShaderEffectSource
to turn any QML Item to a texture, and replace the ShaderEffectSource
with another sampler 2D
and mix the two textures in any way you want, cut using the alpha channel, or any of the RGB if you are using a grayscale mask. And unlike the rather limited OpacityMask
element, this will actually cut through and show anything that is underneath as it is supposed to.
What you want is an OpacityMask
that could be inverted.
This is planned in a future release of Qt (5.7.1 maybe ?) and in fact you can already take a look at it : https://github.com/qt/qtgraphicaleffects/blob/5.7.1/src/effects/OpacityMask.qml
Meanwhile you could copy the code in a file named MyOpacityMask.qml
or something else and use it this way with a Button from Qt Quick Controls 2 :
Button {
id: button
text: "Yolo"
background.visible: false
contentItem.visible: false
contentItem.anchors.fill: button //can be avoided at the cost of creating another Item and ShaderEffectSource
MyOpacityMask {
anchors.fill: parent
invert: true
source: button.background
maskSource: button.contentItem
}
}
This has previously been mentioned in Opposite for OpacityMask
I think this is what OpacityMask is for.
The sample below seems to illustrate the effect you're looking for. The background is a solid red Rectangle
. The foreground is a solid blue Rectangle
. A Text
object, which black text is on top of the blue foreground. The OpacityMask
uses the Text
as a mask for the background, which results in red text appearing.
import QtQuick 2.7
import QtGraphicalEffects 1.0
Rectangle {
width: 500
height: 500
Rectangle {
id: background
anchors.fill: parent
Rectangle {
id: left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width / 2
color: "red"
ColorAnimation on color {
from: "red"
to: "green"
duration: 5000
loops: Animation.Infinite
}
}
Rectangle {
id: right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width / 2
color: "red"
}
}
Rectangle {
id: foreground
anchors.fill: parent
color: "blue"
}
Text {
id: txt
anchors.centerIn: parent
text: "Test"
font.pointSize: 60
color: "black"
}
OpacityMask {
anchors.fill: txt
source: background
maskSource: txt
}
}
Update: Added a more complex background to better illustrate that the OpacityMask
is really computing cut through layer. Also added an animation to show the computed cut through changing over time.
You can achieve that using layer
attached property as follow without using OpacityMask
.
Also you does not any limitation and you can use any qml item, use any QtQuick.Controls
and style it as usual :)
Image {
id: bk
source: "http://l7.alamy.com/zooms/7b6f221aadd44ffab6a87c234065b266/sheikh-lotfollah-mosque-at-naqhsh-e-jahan-square-in-isfahan-iran-interior-g07fw2.jpg"
}
Button {
id: button
anchors.centerIn: bk
width: 210; height: 72
visible: true
opacity: 0.0
layer.enabled: true
layer.smooth: true
onClicked: console.log("Clicked")
}
Rectangle {
id: _mask
anchors.fill: button
color: "transparent"
visible: true
Text {
font { pointSize: 20; bold: true }
anchors.centerIn: parent
text: "Hello World!"
}
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: ShaderEffect {
property variant source: button
fragmentShader: "
varying highp vec2 qt_TexCoord0;
uniform highp float qt_Opacity;
uniform lowp sampler2D source;
uniform lowp sampler2D maskSource;
void main(void) {
gl_FragColor = texture2D(source, qt_TexCoord0.st) * (1.0-texture2D(maskSource, qt_TexCoord0.st).a) * qt_Opacity;
}
"
}
}