fix: reduce shader usage

This commit is contained in:
2026-10-23 00:05:08 +02:00
parent 6781078467
commit 36cddc96c3
32 changed files with 959 additions and 84 deletions
+41
View File
@@ -0,0 +1,41 @@
import QtQuick
import ZShell.Components
Item {
id: root
property real radius
property real topLeftRadius: radius
property real topRightRadius: radius
property real bottomRightRadius: radius
property real bottomLeftRadius: radius
property color color: "transparent"
default property alias contentData: contentItem.data
Item {
id: contentItem
anchors.fill: parent
}
ShaderEffectSource {
id: source
hideSource: true
sourceItem: contentItem
}
RoundedMask {
anchors.fill: parent
source: source
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomRightRadius: root.bottomRightRadius
bottomLeftRadius: root.bottomLeftRadius
color: root.color
}
Behavior on color {
CAnim {}
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ TextField {
implicitHeight: contentHeight + topPadding + bottomPadding implicitHeight: contentHeight + topPadding + bottomPadding
implicitWidth: contentWidth + leftPadding + rightPadding implicitWidth: contentWidth + leftPadding + rightPadding
placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom
renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.QtRendering
selectedTextColor: color selectedTextColor: color
selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4) selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4)
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
+3
View File
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell
import qs.Effects import qs.Effects
CustomFlickable { CustomFlickable {
@@ -9,6 +10,7 @@ CustomFlickable {
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1 property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
property real fadeAmount: 0.1 property real fadeAmount: 0.1
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1 property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
readonly property real dpr: Math.ceil((QsWindow.window as QsWindow)?.devicePixelRatio ?? 1)
function fadeShouldBeActive(isStart: bool): bool { function fadeShouldBeActive(isStart: bool): bool {
// When content is smaller than flickable size, hide fade when rebound starts // When content is smaller than flickable size, hide fade when rebound starts
@@ -28,6 +30,7 @@ CustomFlickable {
type: Anim.SlowEffects type: Anim.SlowEffects
} }
} }
// layer.textureSize: Qt.size(width * dpr, height * dpr)
layer.effect: Mask { layer.effect: Mask {
maskSource: mask maskSource: mask
+3 -1
View File
@@ -19,7 +19,7 @@ Item {
required property Wrapper popouts required property Wrapper popouts
function resolveIcon(app: string, icon: string): string { function resolveIcon(app: string, icon: string): string {
if (app === "chrome_status_icon_1") { if (app === "discord_status_icon_1" && Config.bar.tray.replaceIcons) {
return Quickshell.iconPath("discord-tray"); return Quickshell.iconPath("discord-tray");
} else if (app === "AyuGramDesktop") { } else if (app === "AyuGramDesktop") {
if (icon === Quickshell.iconPath("com.ayugram.desktop-attention-symbolic")) if (icon === Quickshell.iconPath("com.ayugram.desktop-attention-symbolic"))
@@ -50,6 +50,8 @@ Item {
radius: Tokens.rounding.full radius: Tokens.rounding.full
StateLayer { StateLayer {
id: layer
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent anchors.fill: parent
color: icon.layer.enabled && root.current ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface color: icon.layer.enabled && root.current ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
+32 -28
View File
@@ -33,8 +33,7 @@ Item {
y: parent.y y: parent.y
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {}
}
} }
} }
@@ -101,8 +100,7 @@ Item {
value: Audio.volume value: Audio.volume
Behavior on value { Behavior on value {
Anim { Anim {}
}
} }
onInteraction: value => Audio.setVolume(value) onInteraction: value => Audio.setVolume(value)
@@ -123,7 +121,7 @@ Item {
} }
} }
CustomClippingRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 65 + Tokens.spacing.small * 2 Layout.preferredHeight: 65 + Tokens.spacing.small * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
@@ -136,17 +134,21 @@ Item {
node: Audio.source node: Audio.source
} }
CustomRect { CustomClippingRect {
id: sourcePeakFill anchors.fill: parent
radius: parent.radius
anchors.bottom: parent.bottom CustomRect {
anchors.left: parent.left id: sourcePeakFill
anchors.top: parent.top
color: Qt.alpha(Colors.palette.m3primary, 0.15)
implicitWidth: parent.width * sourcePeak.peak
Behavior on implicitWidth { anchors.bottom: parent.bottom
Anim { anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(Colors.palette.m3primary, 0.15)
implicitWidth: parent.width * sourcePeak.peak
Behavior on implicitWidth {
Anim {}
} }
} }
} }
@@ -195,8 +197,7 @@ Item {
value: Audio.sourceVolume value: Audio.sourceVolume
Behavior on value { Behavior on value {
Anim { Anim {}
}
} }
onInteraction: value => Audio.setSourceVolume(value) onInteraction: value => Audio.setSourceVolume(value)
@@ -230,7 +231,7 @@ Item {
model: Audio.streams.filter(s => s.isSink) model: Audio.streams.filter(s => s.isSink)
CustomClippingRect { CustomRect {
id: appBox id: appBox
required property int index required property int index
@@ -248,17 +249,21 @@ Item {
node: appBox.modelData node: appBox.modelData
} }
CustomRect { CustomClippingRect {
id: peakFill anchors.fill: parent
radius: parent.radius
anchors.bottom: parent.bottom CustomRect {
anchors.left: parent.left id: peakFill
anchors.top: parent.top
color: Qt.alpha(Colors.palette.m3primary, 0.15)
implicitWidth: parent.width * peak.peak
Behavior on implicitWidth { anchors.bottom: parent.bottom
Anim { anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(Colors.palette.m3primary, 0.15)
implicitWidth: parent.width * peak.peak
Behavior on implicitWidth {
Anim {}
} }
} }
} }
@@ -316,8 +321,7 @@ Item {
value: appBox.modelData.audio.volume value: appBox.modelData.audio.volume
Behavior on value { Behavior on value {
Anim { Anim {}
}
} }
onInteraction: value => Audio.setStreamVolume(appBox.modelData, value) onInteraction: value => Audio.setStreamVolume(appBox.modelData, value)
+2 -1
View File
@@ -9,7 +9,7 @@ import qs.Modules
import qs.Helpers import qs.Helpers
import qs.Services import qs.Services
CustomClippingRect { CustomRect {
id: root id: root
readonly property bool hasUpdates: Object.keys(Updates.updates)?.length > 0 readonly property bool hasUpdates: Object.keys(Updates.updates)?.length > 0
@@ -73,6 +73,7 @@ CustomClippingRect {
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing) implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth implicitWidth: contentWidth
spacing: Tokens.spacing.medium spacing: Tokens.spacing.medium
clip: true
delegate: CustomRect { delegate: CustomRect {
id: update id: update
-2
View File
@@ -1,6 +1,4 @@
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick import QtQuick
import qs.Components import qs.Components
import ZShell.Config import ZShell.Config
+7 -11
View File
@@ -3,8 +3,9 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
import qs.Components
import ZShell.Config import ZShell.Config
import ZShell.Components
import qs.Components
import qs.Daemons import qs.Daemons
import qs.Helpers import qs.Helpers
@@ -27,7 +28,7 @@ Item {
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0; height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0;
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x) if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || screenState.dashboard)
return 0; return 0;
if (screenState.osd) { if (screenState.osd) {
@@ -36,25 +37,20 @@ Item {
height = h; height = h;
} }
if (screenState.session) {
const h = panels.session.y - Tokens.spacing.small * 2 - padding * 2;
if (height > h)
height = h;
}
return Math.min(((QsWindow.window as QsWindow)?.screen?.height ?? 0) - 1 * 2, height + padding * 2); return Math.min(((QsWindow.window as QsWindow)?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
} }
implicitWidth: Config.notifs.sizes.width + padding * 2 implicitWidth: Config.notifs.sizes.width + padding * 2
Behavior on implicitHeight { Behavior on implicitHeight {
Anim {} Anim {}
} }
ClippingWrapperRectangle { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.padding anchors.margins: root.padding
color: "transparent" clip: true
radius: Tokens.rounding.large - root.padding // radius: Tokens.rounding.large - root.padding
CustomListView { CustomListView {
id: list id: list
@@ -6,7 +6,7 @@ import qs.Effects
import qs.Components import qs.Components
import qs.Services import qs.Services
CustomClippingRect { CustomRect {
id: root id: root
property bool expanded: false property bool expanded: false
@@ -185,7 +185,7 @@ Item {
} }
} }
CustomClippingRect { CustomRect {
id: fabMenuItem id: fabMenuItem
required property var modelData required property var modelData
@@ -197,6 +197,7 @@ Item {
radius: sLayer.pressed ? Tokens.rounding.small : implicitHeight / 2 radius: sLayer.pressed ? Tokens.rounding.small : implicitHeight / 2
color: Colors.palette.m3primaryContainer color: Colors.palette.m3primaryContainer
visible: !(modelData.name === "pop out in window" && ChatState.isWindow) visible: !(modelData.name === "pop out in window" && ChatState.isWindow)
clip: true
Behavior on radius { Behavior on radius {
Anim { Anim {
@@ -24,7 +24,7 @@ Item {
implicitHeight: bubble.implicitHeight + (actionsRow.shouldBeActive ? actionsRow.implicitHeight + actionsRow.anchors.topMargin : 0) implicitHeight: bubble.implicitHeight + (actionsRow.shouldBeActive ? actionsRow.implicitHeight + actionsRow.anchors.topMargin : 0)
CustomClippingRect { CustomRect {
id: bubble id: bubble
property real liveTargetWidth: root.isUser ? Math.min(msgText.implicitWidth + Tokens.padding.medium * 2, root.contentMaxWidth) : root.contentMaxWidth property real liveTargetWidth: root.isUser ? Math.min(msgText.implicitWidth + Tokens.padding.medium * 2, root.contentMaxWidth) : root.contentMaxWidth
@@ -37,8 +37,7 @@ Item {
implicitWidth: layoutFrozen ? frozenWidth : layoutWidth implicitWidth: layoutFrozen ? frozenWidth : layoutWidth
implicitHeight: root.isUser ? msgText.contentHeight + Tokens.padding.medium * 2 : markdown.implicitHeight + markdown.anchors.topMargin * 2 implicitHeight: root.isUser ? msgText.contentHeight + Tokens.padding.medium * 2 : markdown.implicitHeight + markdown.anchors.topMargin * 2
anchors.right: root.isUser ? parent.right : undefined anchors.right: root.isUser ? parent.right : undefined
layer.enabled: false clip: true
layer.smooth: true
transform: Scale { transform: Scale {
id: stretchScale id: stretchScale
@@ -223,12 +223,13 @@ MouseArea {
height: width height: width
opacity: modelData ? 1 : 0 opacity: modelData ? 1 : 0
CustomClippingRect { CustomRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.spacing.extraSmall anchors.margins: Tokens.spacing.extraSmall
radius: Tokens.rounding.medium radius: Tokens.rounding.medium
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
clip: true
FadeImage { FadeImage {
id: attImage id: attImage
+1 -3
View File
@@ -63,7 +63,7 @@ Item {
} }
} }
ClippingRectangle { Item {
id: clipRect id: clipRect
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -71,8 +71,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: title.bottom anchors.top: title.bottom
anchors.topMargin: 10 anchors.topMargin: 10
color: "transparent"
radius: 6
Loader { Loader {
active: opacity > 0 active: opacity > 0
+14 -9
View File
@@ -4,7 +4,7 @@ import ZShell.Config
import qs.Components import qs.Components
import qs.Services import qs.Services
CustomClippingRect { CustomRect {
id: root id: root
required property color accent required property color accent
@@ -19,15 +19,20 @@ CustomClippingRect {
implicitWidth: 450 implicitWidth: 450
radius: Tokens.rounding.largeIncreased radius: Tokens.rounding.largeIncreased
CustomRect { CustomClippingRect {
anchors.bottom: parent.bottom anchors.fill: parent
anchors.left: parent.left radius: parent.radius
anchors.top: parent.top
color: Qt.alpha(root.accent, 0.05)
implicitWidth: parent.width * root.usage
Behavior on implicitWidth { CustomRect {
Anim {} anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
color: Qt.alpha(root.accent, 0.05)
implicitWidth: parent.width * root.usage
Behavior on implicitWidth {
Anim {}
}
} }
} }
+3 -2
View File
@@ -149,7 +149,7 @@ ColumnLayout {
} }
} }
VerticalFadeFlickable { Flickable {
id: flickable id: flickable
Layout.fillHeight: true Layout.fillHeight: true
@@ -158,7 +158,8 @@ ColumnLayout {
bottomMargin: Tokens.padding.extraLarge bottomMargin: Tokens.padding.extraLarge
topMargin: Tokens.padding.large topMargin: Tokens.padding.large
fadeAmount: 0.1 clip: true
// fadeAmount: 0.1
contentHeight: root.contentChild?.implicitHeight ?? 0 contentHeight: root.contentChild?.implicitHeight ?? 0
contentItem.children: [root.contentChild] contentItem.children: [root.contentChild]
+3 -2
View File
@@ -6,7 +6,7 @@ import qs.Components
import ZShell.Config import ZShell.Config
import qs.Services import qs.Services
CustomClippingRect { Item {
id: root id: root
property color blobColor: Colors.tPalette.m3surfaceContainerLow property color blobColor: Colors.tPalette.m3surfaceContainerLow
@@ -21,7 +21,6 @@ CustomClippingRect {
implicitHeight: sState.screen.height * 0.7 implicitHeight: sState.screen.height * 0.7
implicitWidth: implicitHeight * ratio implicitWidth: implicitHeight * ratio
radius: Tokens.rounding.largeIncreased + Tokens.padding.small
Behavior on blobColor { Behavior on blobColor {
CAnim {} CAnim {}
@@ -46,6 +45,8 @@ CustomClippingRect {
borderTop: Tokens.padding.small borderTop: Tokens.padding.small
group: blobGroup group: blobGroup
opacity: root.blobColor.a opacity: root.blobColor.a
// outerRadius: 10
outerRadius: Tokens.rounding.largeIncreased + Tokens.padding.small
radius: Tokens.rounding.largeIncreased radius: Tokens.rounding.largeIncreased
} }
+11 -14
View File
@@ -2,8 +2,9 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components
import ZShell.Config import ZShell.Config
import ZShell.Components
import qs.Components
Item { Item {
id: root id: root
@@ -27,21 +28,17 @@ Item {
} }
} }
CustomClippingRect { Loader {
anchors.fill: parent id: content
Loader { active: root.shouldBeActive || root.visible
id: content anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
active: root.shouldBeActive || root.visible sourceComponent: Content {
anchors.bottom: parent.bottom sState.animatingContainer: content.opacity < 1
anchors.horizontalCenter: parent.horizontalCenter sState.currentPageIdx: ["wallpaper"].indexOf(root.queuedMode)
sState.screen: root.screen
sourceComponent: Content {
sState.animatingContainer: content.opacity < 1
sState.currentPageIdx: ["wallpaper"].indexOf(root.queuedMode)
sState.screen: root.screen
}
} }
} }
} }
+63
View File
@@ -5,6 +5,7 @@
#include <qsggeometry.h> #include <qsggeometry.h>
#include <qsgnode.h> #include <qsgnode.h>
#include <algorithm>
#include <cstring> #include <cstring>
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {} BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {}
@@ -129,6 +130,10 @@ QSGNode* BlobInvertedRect::updatePaintNode(
material->m_invertedInner, material->m_invertedInner,
m_cachedInvertedInner, m_cachedInvertedInner,
sizeof(m_cachedInvertedInner)); sizeof(m_cachedInvertedInner));
memcpy(
material->m_invertedOuterRadii,
m_cachedInvertedOuterRadii,
sizeof(m_cachedInvertedOuterRadii));
const int count = const int count =
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16))); static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
@@ -173,6 +178,64 @@ void BlobInvertedRect::setBorderBottom(qreal v) {
if (m_group) m_group->markDirty(); if (m_group) m_group->markDirty();
} }
void BlobInvertedRect::setOuterRadius(qreal v) {
if (qFuzzyCompare(m_outerRadius, v)) return;
m_outerRadius = v;
emit outerRadiusChanged();
if (m_group) m_group->markDirty();
}
void BlobInvertedRect::setOuterTopLeftRadius(qreal v) {
if (qFuzzyCompare(m_outerTopLeftRadius, v)) return;
m_outerTopLeftRadius = v;
emit outerTopLeftRadiusChanged();
if (m_group) m_group->markDirty();
}
void BlobInvertedRect::setOuterTopRightRadius(qreal v) {
if (qFuzzyCompare(m_outerTopRightRadius, v)) return;
m_outerTopRightRadius = v;
emit outerTopRightRadiusChanged();
if (m_group) m_group->markDirty();
}
void BlobInvertedRect::setOuterBottomLeftRadius(qreal v) {
if (qFuzzyCompare(m_outerBottomLeftRadius, v)) return;
m_outerBottomLeftRadius = v;
emit outerBottomLeftRadiusChanged();
if (m_group) m_group->markDirty();
}
void BlobInvertedRect::setOuterBottomRightRadius(qreal v) {
if (qFuzzyCompare(m_outerBottomRightRadius, v)) return;
m_outerBottomRightRadius = v;
emit outerBottomRightRadiusChanged();
if (m_group) m_group->markDirty();
}
void BlobInvertedRect::outerCornerRadii(float out[4]) const {
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
out[0] = std::min(
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
: base,
maxR);
out[1] = std::min(
m_outerBottomRightRadius >= 0
? static_cast<float>(m_outerBottomRightRadius)
: base,
maxR);
out[2] = std::min(
m_outerBottomLeftRadius >= 0
? static_cast<float>(m_outerBottomLeftRadius)
: base,
maxR);
out[3] = std::min(
m_outerTopLeftRadius >= 0 ? static_cast<float>(m_outerTopLeftRadius)
: base,
maxR);
}
void BlobInvertedRect::registerWithGroup() { void BlobInvertedRect::registerWithGroup() {
if (m_group) m_group->setInvertedRect(this); if (m_group) m_group->setInvertedRect(this);
} }
+53
View File
@@ -19,6 +19,21 @@ class BlobInvertedRect : public BlobShape {
Q_PROPERTY( Q_PROPERTY(
qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY
borderBottomChanged) borderBottomChanged)
Q_PROPERTY(
qreal outerRadius READ outerRadius WRITE setOuterRadius NOTIFY
outerRadiusChanged)
Q_PROPERTY(
qreal outerTopLeftRadius READ outerTopLeftRadius WRITE
setOuterTopLeftRadius NOTIFY outerTopLeftRadiusChanged)
Q_PROPERTY(
qreal outerTopRightRadius READ outerTopRightRadius WRITE
setOuterTopRightRadius NOTIFY outerTopRightRadiusChanged)
Q_PROPERTY(
qreal outerBottomLeftRadius READ outerBottomLeftRadius WRITE
setOuterBottomLeftRadius NOTIFY outerBottomLeftRadiusChanged)
Q_PROPERTY(
qreal outerBottomRightRadius READ outerBottomRightRadius WRITE
setOuterBottomRightRadius NOTIFY outerBottomRightRadiusChanged)
public: public:
explicit BlobInvertedRect(QQuickItem* parent = nullptr); explicit BlobInvertedRect(QQuickItem* parent = nullptr);
@@ -40,15 +55,47 @@ class BlobInvertedRect : public BlobShape {
void setBorderBottom(qreal v); void setBorderBottom(qreal v);
[[nodiscard]] qreal outerRadius() const { return m_outerRadius; }
void setOuterRadius(qreal v);
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
[[nodiscard]] qreal outerTopLeftRadius() const { return m_outerTopLeftRadius; }
void setOuterTopLeftRadius(qreal v);
[[nodiscard]] qreal outerTopRightRadius() const { return m_outerTopRightRadius; }
void setOuterTopRightRadius(qreal v);
[[nodiscard]] qreal outerBottomLeftRadius() const {
return m_outerBottomLeftRadius;
}
void setOuterBottomLeftRadius(qreal v);
[[nodiscard]] qreal outerBottomRightRadius() const {
return m_outerBottomRightRadius;
}
void setOuterBottomRightRadius(qreal v);
signals: signals:
void borderLeftChanged(); void borderLeftChanged();
void borderRightChanged(); void borderRightChanged();
void borderTopChanged(); void borderTopChanged();
void borderBottomChanged(); void borderBottomChanged();
void outerRadiusChanged();
void outerTopLeftRadiusChanged();
void outerTopRightRadiusChanged();
void outerBottomLeftRadiusChanged();
void outerBottomRightRadiusChanged();
protected: protected:
[[nodiscard]] bool isInvertedRect() const override { return true; } [[nodiscard]] bool isInvertedRect() const override { return true; }
void outerCornerRadii(float out[4]) const override;
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
void registerWithGroup() override; void registerWithGroup() override;
@@ -59,4 +106,10 @@ class BlobInvertedRect : public BlobShape {
qreal m_borderRight = 0; qreal m_borderRight = 0;
qreal m_borderTop = 0; qreal m_borderTop = 0;
qreal m_borderBottom = 0; qreal m_borderBottom = 0;
qreal m_outerRadius = 0;
qreal m_outerTopLeftRadius = -1;
qreal m_outerTopRightRadius = -1;
qreal m_outerBottomLeftRadius = -1;
qreal m_outerBottomRightRadius = -1;
}; };
+4 -1
View File
@@ -33,7 +33,7 @@ bool BlobMaterialShader::updateUniformData(
Q_UNUSED(oldMaterial); Q_UNUSED(oldMaterial);
auto* mat = static_cast<BlobMaterial*>(newMaterial); auto* mat = static_cast<BlobMaterial*>(newMaterial);
QByteArray* buf = state.uniformData(); QByteArray* buf = state.uniformData();
Q_ASSERT(buf->size() >= 1472); Q_ASSERT(buf->size() >= 1488);
if (state.isMatrixDirty()) { if (state.isMatrixDirty()) {
const QMatrix4x4 m = state.combinedMatrix(); const QMatrix4x4 m = state.combinedMatrix();
@@ -105,5 +105,8 @@ bool BlobMaterialShader::updateUniformData(
memcpy(buf->data() + 1440, &mat->m_shadowRadius, 4); memcpy(buf->data() + 1440, &mat->m_shadowRadius, 4);
memcpy(buf->data() + 1456, mat->m_shadowColor, 16); memcpy(buf->data() + 1456, mat->m_shadowColor, 16);
// Outer edge corner radii, after shadowColor, at 1472.
memcpy(buf->data() + 1472, mat->m_invertedOuterRadii, 16);
return true; return true;
} }
+3
View File
@@ -43,6 +43,9 @@ class BlobMaterial : public QSGMaterial {
// puts these after rectData. // puts these after rectData.
float m_shadowRadius = 0.0f; float m_shadowRadius = 0.0f;
float m_shadowColor[4] = {0.0f, 0.0f, 0.0f, 0.5f}; float m_shadowColor[4] = {0.0f, 0.0f, 0.0f, 0.5f};
// Outer-edge corner radii (tr, br, bl, tl). Matches the uniform block, which
// puts this after shadowColor.
float m_invertedOuterRadii[4] = {};
BlobRectData m_rects[16] = {}; BlobRectData m_rects[16] = {};
}; };
+9
View File
@@ -240,6 +240,8 @@ void BlobShape::updatePolish() {
m_cachedInvertedRadius = 0; m_cachedInvertedRadius = 0;
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter)); memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner)); memset(m_cachedInvertedInner, 0, sizeof(m_cachedInvertedInner));
memset(
m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
auto* inv = m_group->invertedRect(); auto* inv = m_group->invertedRect();
if (inv) { if (inv) {
@@ -290,6 +292,9 @@ void BlobShape::updatePolish() {
m_cachedInvertedInner[1] = innerCY; m_cachedInvertedInner[1] = innerCY;
m_cachedInvertedInner[2] = innerHW; m_cachedInvertedInner[2] = innerHW;
m_cachedInvertedInner[3] = innerHH; m_cachedInvertedInner[3] = innerHH;
static_cast<BlobShape*>(inv)->outerCornerRadii(
m_cachedInvertedOuterRadii);
} }
} }
@@ -417,6 +422,10 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
material->m_invertedInner, material->m_invertedInner,
m_cachedInvertedInner, m_cachedInvertedInner,
sizeof(m_cachedInvertedInner)); sizeof(m_cachedInvertedInner));
memcpy(
material->m_invertedOuterRadii,
m_cachedInvertedOuterRadii,
sizeof(m_cachedInvertedOuterRadii));
const int count = const int count =
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16))); static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
+11
View File
@@ -61,6 +61,16 @@ class BlobShape : public QQuickItem {
virtual void cornerRadii(float out[4]) const; virtual void cornerRadii(float out[4]) const;
// Outer-edge corner radii. Only the inverted rect draws an outer frame edge,
// so the base has no outer rounding and returns zeros. Subclasses that expose
// outer corner properties override this (order: tr, br, bl, tl).
virtual void outerCornerRadii(float out[4]) const {
out[0] = 0;
out[1] = 0;
out[2] = 0;
out[3] = 0;
}
virtual void updatePhysics() {} virtual void updatePhysics() {}
virtual void registerWithGroup(); virtual void registerWithGroup();
@@ -88,4 +98,5 @@ class BlobShape : public QQuickItem {
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
float m_cachedInvertedOuterRadii[4] = {};
}; };
+16 -3
View File
@@ -21,6 +21,7 @@ layout(std140, binding = 0) uniform buf {
vec4 rectData[80]; vec4 rectData[80];
float shadowRadius; float shadowRadius;
vec4 shadowColor; vec4 shadowColor;
vec4 invertedOuterRadii;
}; };
float sdRoundedBox(vec2 p, vec2 center, vec2 halfSize, float radius) { float sdRoundedBox(vec2 p, vec2 center, vec2 halfSize, float radius) {
@@ -55,7 +56,8 @@ float smax(float a, float b, float k) {
float smaxSharpA(float a, float b, float k) { float smaxSharpA(float a, float b, float k) {
// smax variant that keeps a's boundary sharp (no inward rounding at a = 0). // smax variant that keeps a's boundary sharp (no inward rounding at a = 0).
// Used for the frame outer edge so it always fills to the edges. // Used for the frame inner side, so the outer edge keeps its own shape and
// always fills to the edges.
float h = max(k - abs(a - b), 0.0) / k; float h = max(k - abs(a - b), 0.0) / k;
float blend = h * h * h * k * (1.0 / 6.0); float blend = h * h * h * k * (1.0 / 6.0);
blend *= smoothstep(0.0, k * 0.5, -a); blend *= smoothstep(0.0, k * 0.5, -a);
@@ -182,7 +184,12 @@ void main() {
} }
if (hasInverted != 0) { if (hasInverted != 0) {
float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0; // Outer edge rounds with its own per-corner radii. With all-zero radii
// sdRoundedBox4 equals sdBox, so a frame that sets no outer radius is
// unchanged. smaxSharpA keeps this boundary authoritative for the fill.
float dOuter =
sdRoundedBox4(pixel, invertedOuter.xy, invertedOuter.zw,
invertedOuterRadii) - 1.0;
float dInner = float dInner =
sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius); sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius);
@@ -248,7 +255,13 @@ void main() {
dInner -= sinkValue; dInner -= sinkValue;
float dFrame = smaxSharpA(dOuter, -dInner, smoothFactor); // dOuter is already a true circle (sdRoundedBox4). Keep it exact whenever the
// outer arc is the active boundary; a smax term added there would pull the
// arc's sides inward (curvature/tangent mismatch), so the corner would not be
// a circle. Only blend on the inner side, where the inner boundary is active.
float negInner = -dInner;
float dFrame = dOuter >= negInner ? dOuter
: smaxSharpA(dOuter, negInner, smoothFactor);
mergedSdf = smin(mergedSdf, dFrame, smoothFactor); mergedSdf = smin(mergedSdf, dFrame, smoothFactor);
if (dFrame < minDist) { if (dFrame < minDist) {
+11
View File
@@ -10,6 +10,8 @@ qml_module(ZShell-components
sparklinecontent.hpp sparklinecontent.cpp sparklinecontent.hpp sparklinecontent.cpp
sparklineitem.hpp sparklineitem.cpp sparklineitem.hpp sparklineitem.cpp
sparklinesceneitem.hpp sparklinesceneitem.cpp sparklinesceneitem.hpp sparklinesceneitem.cpp
roundedmask.hpp roundedmask.cpp
roundedclip.hpp roundedclip.cpp
wheelinverter.hpp wheelinverter.hpp
LIBRARIES LIBRARIES
Qt::Quick Qt::Quick
@@ -17,3 +19,12 @@ qml_module(ZShell-components
Qt::CanvasPainter Qt::CanvasPainter
ZShell-internal ZShell-internal
) )
qt_add_shaders(ZShell-components "components_shaders"
BATCHABLE OPTIMIZED NOHLSL NOMSL
GLSL "300es,330"
PREFIX "/"
FILES
shaders/roundedmask.frag
shaders/roundedmask.vert
)
+131
View File
@@ -0,0 +1,131 @@
#include "roundedclip.hpp"
#include <private/qquickclipnode_p.h>
#include <private/qquickitem_p.h>
#include <qsggeometry.h>
#include <algorithm>
#include <cmath>
namespace ZShell::components {
RoundedClip::RoundedClip(QQuickItem* parent) : QQuickItem(parent) {
setFlag(ItemHasContents);
setClip(true);
}
bool RoundedClip::updateRadius(qreal& member, qreal radius) {
if (qFuzzyCompare(member + 1.0, radius + 1.0)) return false;
member = radius;
update();
return true;
}
void RoundedClip::geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry) {
QQuickItem::geometryChange(newGeometry, oldGeometry);
if (newGeometry.size() != oldGeometry.size()) update();
}
QSGNode* RoundedClip::updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* data) {
Q_UNUSED(oldNode)
Q_UNUSED(data)
QQuickItemPrivate* priv = QQuickItemPrivate::get(this);
QQuickDefaultClipNode* clip = priv->clipNode();
if (!clip) return nullptr;
// Clip node geometry is in item-local coordinates. The clip node is a
// child of the item's transform node, so the clip rect is
// QRectF(0, 0, width, height), i.e. boundingRect()/clipRect().
const QRectF r(0, 0, width(), height());
// Per-corner radii fall back to the shared `radius` shorthand.
const qreal tl = m_topLeftRadius > 0 ? m_topLeftRadius : m_radius;
const qreal tr = m_topRightRadius > 0 ? m_topRightRadius : m_radius;
const qreal br = m_bottomRightRadius > 0 ? m_bottomRightRadius : m_radius;
const qreal bl = m_bottomLeftRadius > 0 ? m_bottomLeftRadius : m_radius;
// Nothing rounded: plain rectangular clip.
if (tl <= 0 && tr <= 0 && br <= 0 && bl <= 0) {
clip->setRect(r);
clip->update();
return nullptr;
}
// All corners equal: let QQuickDefaultClipNode build its own known-correct
// scanline geometry. This is the working path.
if (qFuzzyCompare(tl, tr) && qFuzzyCompare(tr, br) &&
qFuzzyCompare(br, bl)) {
clip->setRect(r);
clip->setRadius(tl);
clip->update();
return nullptr;
}
// Per-corner radii. setRadius() supports only a single value, so we
// must supply the geometry ourselves. Build it as a triangle fan from an
// interior point so the polygon interior fills for the stencil clip.
const qreal half = std::min(r.width() / 2, r.height() / 2);
const qreal rtl = std::min(tl, half);
const qreal rtr = std::min(tr, half);
const qreal rbr = std::min(br, half);
const qreal rbl = std::min(bl, half);
auto segments = [](qreal radius) {
return qMin(30, int(std::ceil(radius)));
};
const int s_tl = segments(rtl);
const int s_tr = segments(rtr);
const int s_br = segments(rbr);
const int s_bl = segments(rbl);
clip->setRect(r);
clip->setIsRectangular(false);
QSGGeometry* g = clip->geometry();
g->setDrawingMode(QSGGeometry::DrawTriangleFan);
// One apex + one point per arc step + one point per zero-radius corner,
// plus a closing apex to complete the fan.
g->allocate(1 + s_tl + s_tr + s_br + s_bl + 5);
QSGGeometry::Point2D* vertices = g->vertexDataAsPoint2D();
int vi = 0;
// Apex at an interior point.
vertices[vi++] =
QSGGeometry::Point2D(float(r.center().x()), float(r.center().y()));
// Each corner contributes its arc points (i = 0..segments) from its
// edge-entry tangent to its edge-exit tangent. Consecutive corners are
// joined by the implicit straight edges, forming the rounded rectangle.
auto addArc =
[&](int segs, qreal cx, qreal cy, qreal rad, qreal a0, qreal a1) {
for (int i = 0; i <= segs; ++i) {
const qreal angle = a0 + (a1 - a0) * qreal(i) / qreal(segs);
vertices[vi++] = QSGGeometry::Point2D(
float(cx + rad * std::cos(angle)),
float(cy + rad * std::sin(angle)));
}
};
addArc(s_tl, rtl, rtl, rtl, M_PI, 3.0 * M_PI / 2.0);
addArc(s_tr, r.width() - rtr, rtr, rtr, -M_PI / 2.0, 0.0);
addArc(s_br, r.width() - rbr, r.height() - rbr, rbr, 0.0, M_PI / 2.0);
addArc(s_bl, rbl, r.height() - rbl, rbl, M_PI / 2.0, M_PI);
// Close the fan back to the first perimeter vertex.
vertices[vi] = vertices[1];
clip->markDirty(QSGNode::DirtyGeometry);
return nullptr;
}
} // namespace ZShell::components
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include <qqmlintegration.h>
#include <qquickitem.h>
#include <qsgnode.h>
namespace ZShell::components {
class RoundedClip : public QQuickItem {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
Q_PROPERTY(
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
topLeftRadiusChanged)
Q_PROPERTY(
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
topRightRadiusChanged)
Q_PROPERTY(
qreal bottomRightRadius READ bottomRightRadius WRITE
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
Q_PROPERTY(
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
NOTIFY bottomLeftRadiusChanged)
public:
explicit RoundedClip(QQuickItem* parent = nullptr);
qreal radius() const { return m_radius; }
void setRadius(qreal radius) {
if (updateRadius(m_radius, radius)) emit radiusChanged();
}
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
void setTopLeftRadius(qreal radius) {
if (updateRadius(m_topLeftRadius, radius)) emit topLeftRadiusChanged();
}
[[nodiscard]] qreal topRightRadius() const { return m_topRightRadius; }
void setTopRightRadius(qreal radius) {
if (updateRadius(m_topRightRadius, radius))
emit topRightRadiusChanged();
}
[[nodiscard]] qreal bottomRightRadius() const {
return m_bottomRightRadius;
}
void setBottomRightRadius(qreal radius) {
if (updateRadius(m_bottomRightRadius, radius))
emit bottomRightRadiusChanged();
}
[[nodiscard]] qreal bottomLeftRadius() const { return m_bottomLeftRadius; }
void setBottomLeftRadius(qreal radius) {
if (updateRadius(m_bottomLeftRadius, radius))
emit bottomLeftRadiusChanged();
}
signals:
void radiusChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomRightRadiusChanged();
void bottomLeftRadiusChanged();
protected:
QSGNode* updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* data) override;
void geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry) override;
private:
bool updateRadius(qreal& member, qreal radius);
qreal m_radius = 0;
qreal m_topLeftRadius = 0;
qreal m_topRightRadius = 0;
qreal m_bottomRightRadius = 0;
qreal m_bottomLeftRadius = 0;
};
} // namespace ZShell::components
+268
View File
@@ -0,0 +1,268 @@
#include "roundedmask.hpp"
#include <qimage.h>
#include <qquickwindow.h>
#include <qsggeometry.h>
#include <algorithm>
#include <cstring>
namespace ZShell::components {
using Qt::StringLiterals::operator""_s;
RoundedMask::RoundedMask(QQuickItem* parent) : QQuickItem(parent) {
setFlag(ItemHasContents);
}
QQuickItem* RoundedMask::source() const {
return m_source;
}
void RoundedMask::setSource(QQuickItem* source) {
if (m_source == source) return;
m_source = source;
emit sourceChanged();
update();
}
qreal RoundedMask::topLeftRadius() const {
return m_topLeftRadius;
}
void RoundedMask::setTopLeftRadius(qreal radius) {
if (updateRadius(m_topLeftRadius, radius)) emit topLeftRadiusChanged();
}
qreal RoundedMask::topRightRadius() const {
return m_topRightRadius;
}
void RoundedMask::setTopRightRadius(qreal radius) {
if (updateRadius(m_topRightRadius, radius)) emit topRightRadiusChanged();
}
qreal RoundedMask::bottomRightRadius() const {
return m_bottomRightRadius;
}
void RoundedMask::setBottomRightRadius(qreal radius) {
if (updateRadius(m_bottomRightRadius, radius))
emit bottomRightRadiusChanged();
}
qreal RoundedMask::bottomLeftRadius() const {
return m_bottomLeftRadius;
}
void RoundedMask::setBottomLeftRadius(qreal radius) {
if (updateRadius(m_bottomLeftRadius, radius))
emit bottomLeftRadiusChanged();
}
bool RoundedMask::updateRadius(qreal& member, qreal radius) {
if (qFuzzyCompare(member + 1.0, radius + 1.0)) return false;
member = radius;
update();
return true;
}
QColor RoundedMask::color() const {
return m_color;
}
void RoundedMask::setColor(const QColor& color) {
if (m_color == color) return;
m_color = color;
emit colorChanged();
update();
}
void RoundedMask::geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry) {
QQuickItem::geometryChange(newGeometry, oldGeometry);
if (newGeometry.size() != oldGeometry.size()) update();
}
void RoundedMask::releaseResources() {
m_node = nullptr;
}
void RoundedMask::invalidateSceneGraph() {
m_node = nullptr;
}
void RoundedMask::watchProvider(QSGTextureProvider* provider) {
if (m_provider == provider) return;
if (m_provider) QObject::disconnect(m_provider, nullptr, this, nullptr);
m_provider = provider;
if (!m_provider) return;
QObject::connect(
m_provider,
&QSGTextureProvider::textureChanged,
this,
&QQuickItem::update);
// Emitted on the render thread by the provider's cleanup job; drop the texture before it is rendered again
QObject::connect(
m_provider,
&QObject::destroyed,
this,
[this] {
m_provider = nullptr;
if (m_node) {
static_cast<RoundedMaskMaterial*>(m_node->material())
->m_texture = nullptr;
m_node->markDirty(QSGNode::DirtyMaterial);
}
update();
},
Qt::DirectConnection);
}
QSGNode* RoundedMask::updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* data) {
Q_UNUSED(data);
auto* const provider = m_source && m_source->isTextureProvider()
? m_source->textureProvider()
: nullptr;
watchProvider(provider);
auto* const texture = provider ? provider->texture() : nullptr;
if (!texture || width() <= 0 || height() <= 0) {
delete oldNode;
m_node = nullptr;
return nullptr;
}
auto* node = static_cast<RoundedMaskNode*>(oldNode);
if (!node) node = new RoundedMaskNode;
m_node = node;
QSGGeometry::updateTexturedRectGeometry(
node->geometry(), boundingRect(), QRectF(0, 0, 1, 1));
node->markDirty(QSGNode::DirtyGeometry);
const auto dpr = static_cast<float>(window()->effectiveDevicePixelRatio());
auto* const material = static_cast<RoundedMaskMaterial*>(node->material());
if (!material->m_fallback) {
QImage blank(1, 1, QImage::Format_RGBA8888_Premultiplied);
blank.fill(Qt::transparent);
material->m_fallback.reset(window()->createTextureFromImage(blank));
}
material->m_texture = texture;
material->m_size[0] = static_cast<float>(width()) * dpr;
material->m_size[1] = static_cast<float>(height()) * dpr;
const auto maxRadius = static_cast<float>(std::min(width(), height()) / 2);
const auto clampRadius = [&](qreal radius) {
return std::clamp(static_cast<float>(radius), 0.0f, maxRadius) * dpr;
};
material->m_radii[0] = clampRadius(m_topLeftRadius);
material->m_radii[1] = clampRadius(m_topRightRadius);
material->m_radii[2] = clampRadius(m_bottomRightRadius);
material->m_radii[3] = clampRadius(m_bottomLeftRadius);
const auto alpha = m_color.alphaF();
material->m_color[0] = m_color.redF() * alpha;
material->m_color[1] = m_color.greenF() * alpha;
material->m_color[2] = m_color.blueF() * alpha;
material->m_color[3] = alpha;
node->markDirty(QSGNode::DirtyMaterial);
return node;
}
QSGMaterialType* RoundedMaskMaterial::type() const {
static QSGMaterialType s_type;
return &s_type;
}
QSGMaterialShader* RoundedMaskMaterial::createShader(
QSGRendererInterface::RenderMode mode) const {
Q_UNUSED(mode);
return new RoundedMaskMaterialShader;
}
int RoundedMaskMaterial::compare(const QSGMaterial* other) const {
const auto* const o = static_cast<const RoundedMaskMaterial*>(other);
if (m_texture != o->m_texture) return m_texture < o->m_texture ? -1 : 1;
const auto fuzzyEqual = [](float a, float b) {
return qFuzzyCompare(a + 1.0f, b + 1.0f);
};
if (!std::ranges::equal(m_size, o->m_size, fuzzyEqual) ||
!std::ranges::equal(m_radii, o->m_radii, fuzzyEqual) ||
!std::ranges::equal(m_color, o->m_color, fuzzyEqual))
return this < other ? -1 : 1;
return 0;
}
RoundedMaskMaterialShader::RoundedMaskMaterialShader() {
setShaderFileName(VertexStage, u":/shaders/roundedmask.vert.qsb"_s);
setShaderFileName(FragmentStage, u":/shaders/roundedmask.frag.qsb"_s);
}
bool RoundedMaskMaterialShader::updateUniformData(
RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
Q_UNUSED(oldMaterial);
auto* const mat = static_cast<RoundedMaskMaterial*>(newMaterial);
QByteArray* const buf = state.uniformData();
Q_ASSERT(buf->size() >= 112);
if (state.isMatrixDirty()) {
const QMatrix4x4 m = state.combinedMatrix();
std::memcpy(buf->data(), m.constData(), 64);
}
if (state.isOpacityDirty()) {
const float opacity = state.opacity();
std::memcpy(buf->data() + 64, &opacity, 4);
}
std::memcpy(buf->data() + 72, mat->m_size, 8);
std::memcpy(buf->data() + 80, mat->m_radii, 16);
std::memcpy(buf->data() + 96, mat->m_color, 16);
return true;
}
void RoundedMaskMaterialShader::updateSampledImage(
RenderState& state,
int binding,
QSGTexture** texture,
QSGMaterial* newMaterial,
QSGMaterial* oldMaterial) {
Q_UNUSED(oldMaterial);
if (binding != 1) return;
const auto* const mat =
static_cast<const RoundedMaskMaterial*>(newMaterial);
auto* const tex = mat->m_texture ? mat->m_texture : mat->m_fallback.data();
tex->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
*texture = tex;
}
RoundedMaskNode::RoundedMaskNode() {
auto* const geometry =
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
setGeometry(geometry);
setFlag(OwnsGeometry);
auto* const material = new RoundedMaskMaterial;
material->setFlag(QSGMaterial::Blending);
setMaterial(material);
setFlag(OwnsMaterial);
setFlag(UsePreprocess);
}
void RoundedMaskNode::preprocess() {
auto* const texture = qobject_cast<QSGDynamicTexture*>(
static_cast<RoundedMaskMaterial*>(material())->m_texture);
if (texture && texture->updateTexture()) markDirty(DirtyMaterial);
}
} // namespace ZShell::components
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include <qcolor.h>
#include <qpointer.h>
#include <qqmlintegration.h>
#include <qquickitem.h>
#include <qscopedpointer.h>
#include <qsgmaterial.h>
#include <qsgmaterialshader.h>
#include <qsgnode.h>
#include <qsgtexture.h>
#include <qsgtextureprovider.h>
namespace ZShell::components {
class RoundedMaskNode;
class RoundedMask : public QQuickItem {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(
QQuickItem* source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
topLeftRadiusChanged)
Q_PROPERTY(
qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY
topRightRadiusChanged)
Q_PROPERTY(
qreal bottomRightRadius READ bottomRightRadius WRITE
setBottomRightRadius NOTIFY bottomRightRadiusChanged)
Q_PROPERTY(
qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius
NOTIFY bottomLeftRadiusChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
explicit RoundedMask(QQuickItem* parent = nullptr);
[[nodiscard]] QQuickItem* source() const;
void setSource(QQuickItem* source);
[[nodiscard]] qreal topLeftRadius() const;
void setTopLeftRadius(qreal radius);
[[nodiscard]] qreal topRightRadius() const;
void setTopRightRadius(qreal radius);
[[nodiscard]] qreal bottomRightRadius() const;
void setBottomRightRadius(qreal radius);
[[nodiscard]] qreal bottomLeftRadius() const;
void setBottomLeftRadius(qreal radius);
[[nodiscard]] QColor color() const;
void setColor(const QColor& color);
signals:
void sourceChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomRightRadiusChanged();
void bottomLeftRadiusChanged();
void colorChanged();
protected:
QSGNode* updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* data) override;
void geometryChange(
const QRectF& newGeometry, const QRectF& oldGeometry) override;
void releaseResources() override;
private slots:
void invalidateSceneGraph();
private:
void watchProvider(QSGTextureProvider* provider);
bool updateRadius(qreal& member, qreal radius);
QPointer<QQuickItem> m_source;
qreal m_topLeftRadius = 0;
qreal m_topRightRadius = 0;
qreal m_bottomRightRadius = 0;
qreal m_bottomLeftRadius = 0;
QColor m_color = Qt::transparent;
QSGTextureProvider* m_provider = nullptr;
RoundedMaskNode* m_node = nullptr;
};
class RoundedMaskMaterial : public QSGMaterial {
public:
[[nodiscard]] QSGMaterialType* type() const override;
[[nodiscard]] QSGMaterialShader* createShader(
QSGRendererInterface::RenderMode mode) const override;
int compare(const QSGMaterial* other) const override;
QSGTexture* m_texture = nullptr;
QScopedPointer<QSGTexture> m_fallback;
float m_size[2] = {};
float m_radii[4] = {};
float m_color[4] = {};
};
class RoundedMaskMaterialShader : public QSGMaterialShader {
public:
RoundedMaskMaterialShader();
bool updateUniformData(
RenderState& state,
QSGMaterial* newMaterial,
QSGMaterial* oldMaterial) override;
void updateSampledImage(
RenderState& state,
int binding,
QSGTexture** texture,
QSGMaterial* newMaterial,
QSGMaterial* oldMaterial) override;
};
class RoundedMaskNode : public QSGGeometryNode {
public:
RoundedMaskNode();
void preprocess() override;
};
} // namespace ZShell::components
@@ -0,0 +1,26 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 size;
vec4 radii;
vec4 color;
};
layout(binding = 1) uniform sampler2D source;
void main() {
vec2 p = qt_TexCoord0 * size - size * 0.5;
float radius = p.x < 0.0 ? (p.y < 0.0 ? radii.x : radii.w) : (p.y < 0.0 ? radii.y : radii.z);
vec2 q = abs(p) - (size * 0.5 - radius);
float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
float mask = 1.0 - smoothstep(-0.5, 0.5, d);
vec4 content = texture(source, qt_TexCoord0);
vec4 blended = content + color * (1.0 - content.a);
fragColor = blended * mask * qt_Opacity;
}
@@ -0,0 +1,19 @@
#version 440
layout(location = 0) in vec4 qt_VertexPosition;
layout(location = 1) in vec2 qt_VertexTexCoord;
layout(location = 0) out vec2 qt_TexCoord0;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 size;
vec4 radii;
vec4 color;
};
void main() {
gl_Position = qt_Matrix * qt_VertexPosition;
qt_TexCoord0 = qt_VertexTexCoord;
}
+1
View File
@@ -21,6 +21,7 @@ class Popouts : public settings::ObjectNode {
class Tray : public settings::ObjectNode { class Tray : public settings::ObjectNode {
CONFIG_NODE(Tray, settings::ObjectNode) CONFIG_NODE(Tray, settings::ObjectNode)
CONFIG_PROPERTY(bool, replaceIcons, false)
CONFIG_PROPERTY(bool, recolorIcons, false) CONFIG_PROPERTY(bool, recolorIcons, false)
CONFIG_PROPERTY(bool, showOnHover, true) CONFIG_PROPERTY(bool, showOnHover, true)
CONFIG_PROPERTY(int, trayIconSize, 24) CONFIG_PROPERTY(int, trayIconSize, 24)