StyleKit Custom Delegates Example¶
A PySide6 application that demonstrates the analogous example in Qt StyleKit Custom Delegates Example.
This example shows how to extend Qt Labs StyleKit styling with custom delegates that allow you to render a style beyond what the default rendering provides.
It demonstrates how to:
Create overlay delegates by subclassing StyledItem and placing custom items on top of the standard delegate rendering.
Create underlay delegates using a plain Item with delegateStyle and control properties, embedding a StyledItem on top for the standard look.
Pass per-state data to a custom delegate using the data property.
Apply shader-based visual effects using ShaderEffect and ShaderEffectSource in combination with StyledItem.
Implement a custom shadow delegate to replace the built-in shadow rendering.
Pass static configuration to a delegate via regular QML properties (e.g. distinguishing the first and second handle of a RangeSlider.
Note
This example requires Qt Shader Tools module.
"""
PySide6 port of StyleKit Custom Delegates Example example from Qt v6.x
"""
import sys
from pathlib import Path
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
import rc_stylekitcustomdelegates # noqa: F401
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
app.setOrganizationName("QtProject")
app.setApplicationName("StyleKitCustomExample")
engine = QQmlApplicationEngine()
engine.addImportPath(Path(__file__).parent)
engine.loadFromModule("StyleKitCustomExampleModule", "Main")
if not engine.rootObjects():
sys.exit(-1)
exit_code = app.exec()
del engine
sys.exit(exit_code)
#version 440
precision highp float;
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D source;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix; // reserved
float qt_Opacity; // reserved
vec2 sourceItemSize; // The size of the input item. The source is divided into a grid of cells.
vec4 borderColor; // The color of the border (to be masked away). Set to transparent to ignore.
vec4 particleColor; // The color of the noise particle
float borderMaskEnabled; // Enabled mask or not. If the bg and border color is the same, set this to 0
float borderMaskThreshold; // The threshold for determining if a pixel belongs to the border (taking anti-aliasing into account)
float particleSize; // The size of a dust particle (aka the cell size in the grid)
float particleOpacity; // The particleOpacity of the particle
float particleDensity; // The threshold deciding if a particle (aka cell in the grid) should be visible or not
float time; // time, for animating the noise
} args;
float random(vec2 st, float t) {
vec2 offsetSt = st + t;
return fract(sin(dot(offsetSt.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
void main() {
vec4 sourceColor = texture(source, qt_TexCoord0);
vec2 pixelCoord = qt_TexCoord0 * args.sourceItemSize;
vec2 noiseCoord = floor(pixelCoord / args.particleSize);
float randomValue = random(noiseCoord, args.time);
float noiseMix = step(randomValue, args.particleDensity);
float calculatedBorderMask = step(args.borderMaskThreshold, distance(sourceColor.rgba, args.borderColor.rgba));
float borderMask = mix(1.0, calculatedBorderMask, args.borderMaskEnabled);
float finalMask = sourceColor.a * borderMask;
float finalAlpha = sourceColor.a * args.qt_Opacity;
float maskedNoiseAlpha = noiseMix * args.particleOpacity * finalMask;
vec3 blendedColor = mix(sourceColor.rgb, args.particleColor.rgb, maskedNoiseAlpha);
vec3 preMultipliedColor = blendedColor * args.qt_Opacity;
fragColor = vec4(preMultipliedColor, finalAlpha);
}
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(binding = 1) uniform sampler2D sourceItem;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix; // reserved
float qt_Opacity; // reserved
float amplitude;
float frequency;
float time;
} args;
void main()
{
vec2 p = sin(args.time + args.frequency * qt_TexCoord0);
fragColor = texture(sourceItem, qt_TexCoord0 + args.amplitude * vec2(p.y, -p.x)) * args.qt_Opacity;
}
import QtQuick
import QtQuick.Shapes
import QtQuick.Effects
import QtQuick.Templates as T
import Qt.labs.StyleKit
Style {
/******************************************************************
* Defining some helper types for the custom delegates further down
******************************************************************/
component Star : Shape {
id: star
ShapePath {
fillColor: star.palette.accent
scale: Qt.size(star.width, star.height)
PathMove { x: 0.50; y: 0.00 }
PathLine { x: 0.59; y: 0.35 }
PathLine { x: 0.97; y: 0.35 }
PathLine { x: 0.66; y: 0.57 }
PathLine { x: 0.78; y: 0.91 }
PathLine { x: 0.50; y: 0.70 }
PathLine { x: 0.22; y: 0.91 }
PathLine { x: 0.34; y: 0.57 }
PathLine { x: 0.03; y: 0.35 }
PathLine { x: 0.41; y: 0.35 }
PathLine { x: 0.50; y: 0.00 }
}
NumberAnimation on rotation {
loops: Animation.Infinite
from: 0
to: 359
duration: 20000
}
}
component OverlayData: QtObject {
property real overlayScale: 1
}
/******************************************************************
* Define custom delegates. These replace the default StyledItem
* for selected controls in the style definition below.
******************************************************************/
component OverlayDelegate : StyledItem {
/* Using StyledItem as the base type is the easiest approach when creating
* a custom delegate. A StyledItem will draw the delegate as configured by
* the style, and give you the opportunity to place your own items on top. */
id: delegate
width: parent.width
height: parent.height
Star {
width: 40
height: 40
anchors.centerIn: parent
anchors.verticalCenterOffset: -10
scale: delegate.delegateStyle.data.overlayScale
Behavior on scale {
NumberAnimation {
duration: 300
easing.type: Easing.OutBounce
}
}
}
}
component UnderlayDelegate : Item {
/* Custom delegates that don't inherit from StyledItem needs to define
* 'delegateStyle' and 'control' properties, which are assinged to by StyleKit.
* Use 'delegateStyle' to bind to style attributes like color, radius, and opacity.
* Use 'control' to access the Quick Control the owns the delegate. */
id: delegate
required property DelegateStyle delegateStyle
required property QtObject control
implicitWidth: delegateStyle.width
implicitHeight: delegateStyle.height
width: parent.width
height: parent.height
Star {
visible: delegate.control.checked
anchors.centerIn: parent
anchors.verticalCenterOffset: -1
width: 60
height: 60
}
StyledItem {
// Embed a StyledItem to render the standard delegate on top
delegateStyle: delegate.delegateStyle
}
}
component SliderHandle : Item {
id: sliderHandle
implicitWidth: delegateStyle.width
implicitHeight: delegateStyle.height
width: parent.width
height: parent.height
/* Unlike the 'data' property, which varies per state, you can use regular
* QML properties to pass static information to a delegate. Here, 'isFirstHandle'
* distinguishes the first from the second handle in a RangeSlider, and
* 'control' gives access to the slider's current value(s). */
required property DelegateStyle delegateStyle
required property QtObject control
property bool isFirstHandle: false
Star {
anchors.centerIn: parent
width: parent.width
height: parent.height
}
Text {
color: "white"
anchors.centerIn: parent
font.pixelSize: 12
font.bold: true
text: {
if (sliderHandle.control instanceof T.RangeSlider) {
if (sliderHandle.isFirstHandle)
return sliderHandle.control.first.value.toFixed(0)
else
return sliderHandle.control.second.value.toFixed(0)
}
return sliderHandle.control.value.toFixed(0)
}
}
}
component NoiseDelegate : ShaderEffect {
/* Use graphical effects in combination with StyledItem to create more
* complex delegate appearances. In this delegate, we create a noise overlay. */
implicitWidth: unifiedSourceItem.implicitWidth
implicitHeight: unifiedSourceItem.implicitHeight
width: parent.width
height: parent.height
required property DelegateStyle delegateStyle
// The following properties are used by the shader (noise.frag)
property size sourceItemSize: Qt.size(unifiedSourceItem.width, unifiedSourceItem.height)
property color borderColor: delegateStyle.border.color
property real borderMaskEnabled: 1
property real borderMaskThreshold: 0.001
property real particleDensity: 0.1
property real particleSize: 1
property color particleColor: "black"
property Item source: ShaderEffectSource { live: true; sourceItem: unifiedSourceItem }
property real particleOpacity: 0.4
property real time
// Note: noise.frag is compiled to noise.qsb from CMakeLists.txt
fragmentShader: "qrc:/effects/noise.qsb"
NumberAnimation on time {
loops: Animation.Infinite
from: 0
to: Math.PI * 2
duration: 1000
}
StyledItem {
id: unifiedSourceItem
delegateStyle: parent.delegateStyle
width: parent.width
height: parent.height
visible: false
rotation: 0.0
scale: 1.0
}
}
component TwinklingStar : ShaderEffect {
id: twinklingStar
implicitWidth: delegateStyle.width
implicitHeight: delegateStyle.height
width: parent.width
height: parent.height
visible: delegateStyle.visible
required property DelegateStyle delegateStyle
// The following properties are used by the shader (wave.frag)
property real amplitude: 0.04 * 0.5
property real frequency: 10
property real time
NumberAnimation on time {
loops: Animation.Infinite
from: 0
to: Math.PI * 2
duration: 600
}
// Note: wave.frag is compiled to wave.qsb from CMakeLists.txt
fragmentShader: "qrc:/effects/wave.qsb"
property Item sourceItem: ShaderEffectSource {
sourceItem: Star {
width: twinklingStar.width
height: twinklingStar.height
visible: false
}
}
}
component CustomShadowDelegate : RectangularShadow {
required property QtObject control
required property DelegateStyle delegateStyle
width: parent.width
height: parent.height
color: delegateStyle.shadow.color
opacity: delegateStyle.shadow.opacity
blur: delegateStyle.shadow.blur
scale: delegateStyle.shadow.scale
radius: delegateStyle.radius
visible: delegateStyle.visible && delegateStyle.shadow.visible
offset: Qt.vector2d(
delegateStyle.shadow.horizontalOffset,
delegateStyle.shadow.verticalOffset)
}
/******************************************************************
* Define the style, assigning the custom delegates above to specific
* controls in place of the default StyledItem:
******************************************************************/
applicationWindow {
background.color: '#625c60'
}
control {
text.color: "ghostwhite"
background {
border.color: "#3d373b"
shadow.color: "#555555"
color: '#787176'
}
handle {
color: "#8e848a"
border.color: Qt.darker("#544e52", 1.5)
shadow.color: "#808080"
}
indicator {
color: Qt.darker("#8e848a", 1.6)
foreground.margins: 2
}
}
button {
// Make some room above the button text for the star shape
topPadding: 30
background.delegate: OverlayDelegate{}
// Use the 'data' property to pass custom, per-state information to a custom delegate
background.data: OverlayData { overlayScale: 0.5 }
hovered.background.data: OverlayData { overlayScale: 1.8 }
pressed.background.data: OverlayData { overlayScale: 1.6 }
checked.background.data: OverlayData { overlayScale: 1.4 }
}
checkBox {
indicator {
width: 40
height: 40
foreground.delegate: TwinklingStar {}
}
}
radioButton {
indicator.delegate: UnderlayDelegate {}
}
slider {
handle.width: 40
handle.height: 40
handle.delegate: SliderHandle { isFirstHandle: true }
handle.second.delegate: SliderHandle { isFirstHandle: false }
}
textField {
background {
shadow.verticalOffset: 4
shadow.horizontalOffset: 4
shadow.color: "white"
shadow.delegate: CustomShadowDelegate {}
}
}
switchControl {
background.visible: true
checked {
background.delegate: NoiseDelegate {}
indicator.foreground.delegate: NoiseDelegate {}
}
}
frame {
padding: 20
}
}
import QtQuick
import QtQuick.Layouts
import Qt.labs.StyleKit
ApplicationWindow {
id: app
width: 800
height: 500
visible: true
title: qsTr("StyleKit")
palette.accent: "darkseagreen"
StyleKit.style: CustomDelegatesStyle {}
GridLayout {
id: controls
anchors.fill: parent
anchors.margins: 20
columns: 2
columnSpacing: 20
rowSpacing: 20
GroupBox {
title: "Buttons with Overlay"
Layout.fillWidth: true
clip: true
RowLayout {
spacing: 10
Button { text: "Pizza" }
Button { text: "Calzone" }
Button { text: "Focaccia" }
}
}
GroupBox {
title: "RadioButtons with Underlay"
Layout.fillWidth: true
clip: true
RowLayout {
spacing: 10
RadioButton { text: "Pasta" }
RadioButton { text: "Lasagna"; checked: true }
RadioButton { text: "Burrita" }
}
}
GroupBox {
title: "CheckBoxes with Shader Effect"
Layout.fillWidth: true
clip: true
RowLayout {
spacing: 10
CheckBox { text: "Mango"; checked: true }
CheckBox { text: "Avocado" }
CheckBox { text: "Banano"; checked: true }
}
}
GroupBox {
title: "Sliders with Custom Handles"
Layout.fillWidth: true
clip: true
RowLayout {
spacing: 10
Slider { from: 0; to: 10; value: 5 }
RangeSlider { from: 0; to: 10; first.value: 2; second.value: 8 }
}
}
GroupBox {
title: "Switch with Noise Effect"
Layout.fillWidth: true
clip: true
RowLayout {
Switch { checked: true; text: "Jalapeño" }
}
}
GroupBox {
title: "TextField with Custom Shadow"
Layout.fillWidth: true
clip: true
RowLayout {
TextField { placeholderText: "Potato" }
}
}
}
}
module StyleKitCustomExampleModule
Main 1.0 Main.qml
CustomDelegatesStyle 1.0 CustomDelegatesStyle.qml
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="effects/noise.qsb">shaders/prebuilt/noise.qsb</file>
<file alias="effects/wave.qsb">shaders/prebuilt/wave.qsb</file>
</qresource>
</RCC>