Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 29s
C++ / build (pull_request) Failing after 33s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Successful in 1m36s
Rust / fmt (pull_request) Successful in 43s
Rust / clippy (pull_request) Successful in 1m4s
C++ / fmt (pull_request) Failing after 29s
C++ / build (pull_request) Failing after 33s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 11s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 51s
Python / verify (pull_request) Successful in 1m36s
Rust / fmt (pull_request) Successful in 43s
Rust / clippy (pull_request) Successful in 1m4s
This commit is contained in:
@@ -16,11 +16,12 @@ Text {
|
|||||||
color: Colors.palette.m3onSurface
|
color: Colors.palette.m3onSurface
|
||||||
font: Tokens.font.body.small
|
font: Tokens.font.body.small
|
||||||
linkColor: Colors.palette.m3onPrimaryFixedVariant
|
linkColor: Colors.palette.m3onPrimaryFixedVariant
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.QtRendering
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
CAnim {}
|
CAnim {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Behavior on text {
|
Behavior on text {
|
||||||
enabled: root.animate
|
enabled: root.animate
|
||||||
@@ -32,7 +33,8 @@ Text {
|
|||||||
type: Anim.FastEffects
|
type: Anim.FastEffects
|
||||||
}
|
}
|
||||||
|
|
||||||
PropertyAction {}
|
PropertyAction {
|
||||||
|
}
|
||||||
|
|
||||||
TAnim {
|
TAnim {
|
||||||
target: root
|
target: root
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ CustomText {
|
|||||||
property int grade: Colors.light ? 0 : -25
|
property int grade: Colors.light ? 0 : -25
|
||||||
property font fontStyle: Tokens.font.icon.small
|
property font fontStyle: Tokens.font.icon.small
|
||||||
|
|
||||||
|
renderType: Text.NativeRendering
|
||||||
font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
|
font: Tokens.font.icon.size(fontStyle.pointSize).weight(fontStyle.weight).vaxes(fontStyle.variableAxes).fill(fill.toFixed(1)).grade(grade).build()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -151,7 +151,7 @@ Item {
|
|||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
clip: true
|
clip: offsetScale > 0
|
||||||
implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
|
implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
|
||||||
implicitWidth: dashboard.implicitWidth
|
implicitWidth: dashboard.implicitWidth
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Shapes
|
import Quickshell
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -12,6 +12,7 @@ GridLayout {
|
|||||||
property real animatedPercentage: 0
|
property real animatedPercentage: 0
|
||||||
readonly property real arcStartAngle: 0.75 * Math.PI
|
readonly property real arcStartAngle: 0.75 * Math.PI
|
||||||
readonly property real arcSweep: 1.5 * Math.PI
|
readonly property real arcSweep: 1.5 * Math.PI
|
||||||
|
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
|
||||||
required property bool horizontal
|
required property bool horizontal
|
||||||
property string icon
|
property string icon
|
||||||
required property color iconColor
|
required property color iconColor
|
||||||
@@ -22,12 +23,17 @@ GridLayout {
|
|||||||
property bool warning: percentage * 100 >= warningThreshold
|
property bool warning: percentage * 100 >= warningThreshold
|
||||||
property int warningThreshold: 80
|
property int warningThreshold: 80
|
||||||
|
|
||||||
|
function snap(v) {
|
||||||
|
return Math.round(v * dpr) / dpr;
|
||||||
|
}
|
||||||
|
|
||||||
columnSpacing: Tokens.spacing.medium
|
columnSpacing: Tokens.spacing.medium
|
||||||
columns: horizontal ? -1 : 1
|
columns: horizontal ? -1 : 1
|
||||||
percentage: 0
|
percentage: 0
|
||||||
|
|
||||||
Behavior on animatedPercentage {
|
Behavior on animatedPercentage {
|
||||||
Anim {}
|
Anim {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: animatedPercentage = percentage
|
Component.onCompleted: animatedPercentage = percentage
|
||||||
@@ -47,27 +53,35 @@ GridLayout {
|
|||||||
text: root.icon
|
text: root.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomClippingRect {
|
CustomRect {
|
||||||
|
id: track
|
||||||
|
|
||||||
Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.extraSmall : 4
|
Layout.preferredHeight: root.horizontal ? icon.implicitHeight - Tokens.padding.extraSmall : 4
|
||||||
Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
|
Layout.preferredWidth: root.horizontal ? 4 : icon.implicitWidth
|
||||||
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
color: Colors.layer(Colors.palette.m3surfaceContainerHigh, 2)
|
||||||
radius: Tokens.rounding.full
|
radius: Math.min(width, height) / 2
|
||||||
|
|
||||||
CustomRect {
|
Item {
|
||||||
id: fill
|
id: clipper
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: root.horizontal ? parent.bottom : undefined
|
||||||
anchors.left: parent.left
|
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
|
||||||
anchors.right: root.horizontal ? parent.right : undefined
|
anchors.left: root.horizontal ? undefined : parent.left
|
||||||
anchors.top: root.horizontal ? undefined : parent.top
|
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
|
||||||
color: root.mainColor
|
clip: true
|
||||||
implicitHeight: root.horizontal ? Math.ceil(root.percentage * parent.height) : 0
|
height: root.horizontal ? root.snap(root.percentage * track.height) : track.height * 2
|
||||||
implicitWidth: root.horizontal ? 0 : Math.ceil(root.percentage * parent.width)
|
width: root.horizontal ? track.width * 2 : root.snap(root.percentage * track.width)
|
||||||
|
|
||||||
// Behavior on implicitHeight {
|
CustomRect {
|
||||||
// Anim {
|
anchors.bottom: root.horizontal ? parent.bottom : undefined
|
||||||
// }
|
anchors.horizontalCenter: root.horizontal ? parent.horizontalCenter : undefined
|
||||||
// }
|
anchors.left: root.horizontal ? undefined : parent.left
|
||||||
|
anchors.verticalCenter: root.horizontal ? undefined : parent.verticalCenter
|
||||||
|
color: root.mainColor
|
||||||
|
height: track.height
|
||||||
|
radius: track.radius
|
||||||
|
width: track.width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
@@ -11,11 +10,6 @@ import qs.Components
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
|
|
||||||
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins + viewWrapper.anchors.topMargin
|
|
||||||
|
|
||||||
required property ScreenState screenState
|
|
||||||
|
|
||||||
readonly property var dashboardTabs: {
|
readonly property var dashboardTabs: {
|
||||||
const allTabs = [
|
const allTabs = [
|
||||||
{
|
{
|
||||||
@@ -45,9 +39,12 @@ Item {
|
|||||||
];
|
];
|
||||||
return allTabs.filter(tab => tab.enabled);
|
return allTabs.filter(tab => tab.enabled);
|
||||||
}
|
}
|
||||||
|
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins + viewWrapper.anchors.topMargin
|
||||||
|
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
|
||||||
|
required property ScreenState screenState
|
||||||
|
|
||||||
implicitWidth: nonAnimWidth
|
|
||||||
implicitHeight: nonAnimHeight
|
implicitHeight: nonAnimHeight
|
||||||
|
implicitWidth: nonAnimWidth
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitHeight {
|
||||||
Anim {
|
Anim {
|
||||||
@@ -63,15 +60,14 @@ Item {
|
|||||||
Tabs {
|
Tabs {
|
||||||
id: tabs
|
id: tabs
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.margins: Tokens.padding.small
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: ZUtils.clamp(anchors.margins - Config.bar.border, 0, anchors.margins)
|
||||||
nonAnimWidth: view.implicitWidth
|
nonAnimWidth: view.implicitWidth
|
||||||
screenState: root.screenState
|
screenState: root.screenState
|
||||||
tabs: root.dashboardTabs
|
tabs: root.dashboardTabs
|
||||||
anchors.topMargin: ZUtils.clamp(anchors.margins - Config.bar.border, 0, anchors.margins)
|
|
||||||
anchors.margins: Tokens.padding.small
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -79,13 +75,13 @@ Item {
|
|||||||
|
|
||||||
property int radius: Tokens.rounding.extraLarge - anchors.margins
|
property int radius: Tokens.rounding.extraLarge - anchors.margins
|
||||||
|
|
||||||
anchors.top: tabs.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.topMargin: Tokens.spacing.small
|
anchors.left: parent.left
|
||||||
anchors.margins: Tokens.padding.small
|
anchors.margins: Tokens.padding.small
|
||||||
clip: true
|
anchors.right: parent.right
|
||||||
|
anchors.top: tabs.bottom
|
||||||
|
anchors.topMargin: Tokens.spacing.small
|
||||||
|
clip: view.currentIndex !== 3 || view.contentX !== (view.currentItem?.x ?? 0)
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
id: view
|
id: view
|
||||||
@@ -97,16 +93,19 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
implicitWidth: currentItem?.implicitWidth ?? 0
|
|
||||||
implicitHeight: currentItem?.implicitHeight ?? 0
|
|
||||||
|
|
||||||
contentX: currentItem?.x ?? 0
|
|
||||||
contentWidth: row.implicitWidth
|
|
||||||
contentHeight: row.implicitHeight
|
contentHeight: row.implicitHeight
|
||||||
|
contentWidth: row.implicitWidth
|
||||||
|
contentX: currentItem?.x ?? 0
|
||||||
|
implicitHeight: currentItem?.implicitHeight ?? 0
|
||||||
|
implicitWidth: currentItem?.implicitWidth ?? 0
|
||||||
interactive: false
|
interactive: false
|
||||||
|
|
||||||
|
Behavior on contentX {
|
||||||
|
Anim {
|
||||||
|
type: Anim.DefaultEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onContentXChanged: {
|
onContentXChanged: {
|
||||||
if (!moving || !currentItem)
|
if (!moving || !currentItem)
|
||||||
return;
|
return;
|
||||||
@@ -124,10 +123,6 @@ Item {
|
|||||||
Repeater {
|
Repeater {
|
||||||
id: repeater
|
id: repeater
|
||||||
|
|
||||||
model: ScriptModel {
|
|
||||||
values: root.dashboardTabs
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
id: paneLoader
|
id: paneLoader
|
||||||
|
|
||||||
@@ -135,7 +130,6 @@ Item {
|
|||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
sourceComponent: modelData.component
|
sourceComponent: modelData.component
|
||||||
|
|
||||||
Component.onCompleted: active = Qt.binding(() => {
|
Component.onCompleted: active = Qt.binding(() => {
|
||||||
@@ -146,6 +140,9 @@ Item {
|
|||||||
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
|
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.dashboardTabs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +150,8 @@ Item {
|
|||||||
id: dashComponent
|
id: dashComponent
|
||||||
|
|
||||||
Dash {
|
Dash {
|
||||||
screenState: root.screenState
|
|
||||||
radius: viewWrapper.radius
|
radius: viewWrapper.radius
|
||||||
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +167,8 @@ Item {
|
|||||||
id: mediaComponent
|
id: mediaComponent
|
||||||
|
|
||||||
Media {
|
Media {
|
||||||
screenState: root.screenState
|
|
||||||
radius: viewWrapper.radius
|
radius: viewWrapper.radius
|
||||||
|
screenState: root.screenState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,12 +179,6 @@ Item {
|
|||||||
radius: viewWrapper.radius
|
radius: viewWrapper.radius
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on contentX {
|
|
||||||
Anim {
|
|
||||||
type: Anim.DefaultEffects
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,6 +272,10 @@ Item {
|
|||||||
retainWhileLoading: true
|
retainWhileLoading: true
|
||||||
smooth: true
|
smooth: true
|
||||||
source: Wallpapers.current
|
source: Wallpapers.current
|
||||||
|
sourceSize.height: {
|
||||||
|
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||||
|
return imageSlot.height * dpr;
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
Anim {
|
Anim {
|
||||||
|
|||||||
@@ -42,18 +42,6 @@ Item {
|
|||||||
objectName: "PageContainer"
|
objectName: "PageContainer"
|
||||||
|
|
||||||
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
|
Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
|
||||||
|
|
||||||
// CustomRect {
|
|
||||||
// anchors.fill: parent
|
|
||||||
// color: Qt.alpha(Colors.palette.m3shadow, 0.3)
|
|
||||||
// opacity: root.sState.dimmed ? 1 : 0
|
|
||||||
// visible: opacity > 0
|
|
||||||
// z: 0
|
|
||||||
//
|
|
||||||
// Behavior on opacity {
|
|
||||||
// Anim {}
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -189,6 +189,9 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
|
|
||||||
const int count = static_cast<int>(m_data.rects.size());
|
const int count = static_cast<int>(m_data.rects.size());
|
||||||
|
|
||||||
|
// Which shape is the source for the bar edge.
|
||||||
|
resolveSources(entryShapes);
|
||||||
|
|
||||||
// Exclusion gates only the smooth blend.
|
// Exclusion gates only the smooth blend.
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
int mask = 0;
|
int mask = 0;
|
||||||
@@ -251,8 +254,8 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
ri.radius[3] = std::max(ri.radius[3] * fTl, minR);
|
ri.radius[3] = std::max(ri.radius[3] * fTl, minR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverted rect. The frame term is evaluated for every pixel in the bounds,
|
// Inverted rect. The frame term is evaluated for every pixel in the bounds, so the
|
||||||
// so the group flag cannot be gated per shape.
|
// group flag cannot be gated per shape.
|
||||||
if (!m_invertedRect) return;
|
if (!m_invertedRect) return;
|
||||||
|
|
||||||
auto* inv = static_cast<BlobShape2*>(m_invertedRect);
|
auto* inv = static_cast<BlobShape2*>(m_invertedRect);
|
||||||
@@ -299,6 +302,28 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
2.0 * (static_cast<double>(outerHH) + extent)));
|
2.0 * (static_cast<double>(outerHH) + extent)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A point on the bar edge can only have one source. A shape that is not pushing is not
|
||||||
|
// the source: it still fills its own area, but inside the border strip it must not add
|
||||||
|
// its own outline to a span another shape is already shaping. That is the case that
|
||||||
|
// matters — one panel at rest next to one that is bouncing — where the edge showed
|
||||||
|
// several steps instead of one smooth indent of the closing panel. Only one shape can be
|
||||||
|
// pushing at a time, so the flag is just that, and the shader gates the strip with it.
|
||||||
|
void BlobGroup2::resolveSources(const QVector<BlobShape2*>& entryShapes) {
|
||||||
|
const int count = static_cast<int>(m_data.rects.size());
|
||||||
|
|
||||||
|
// With nothing pushing there is no span being shaped, so a shape at rest keeps its
|
||||||
|
// own outline and nothing is gated.
|
||||||
|
bool anyPushing = false;
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
if (entryShapes[i]->isMoving()) {
|
||||||
|
anyPushing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
m_data.rects[i].source = !anyPushing || entryShapes[i]->isMoving();
|
||||||
|
}
|
||||||
|
|
||||||
void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
||||||
material->m_paddedX = static_cast<float>(m_bounds.x());
|
material->m_paddedX = static_cast<float>(m_bounds.x());
|
||||||
material->m_paddedY = static_cast<float>(m_bounds.y());
|
material->m_paddedY = static_cast<float>(m_bounds.y());
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ class BlobInvertedRect2;
|
|||||||
// disagree with the first. That is what makes overlapping shapes read as one
|
// disagree with the first. That is what makes overlapping shapes read as one
|
||||||
// surface - one value, one writer, one alpha per pixel.
|
// surface - one value, one writer, one alpha per pixel.
|
||||||
struct BlobGroupData {
|
struct BlobGroupData {
|
||||||
// The shader reads rectData[80] = 16 entries, and excludeMask is a 32-bit
|
// The shader reads rectData[80] = 16 entries of 5 vec4, and excludeMask is a
|
||||||
// mask. The shared list is capped at 16, in group order, so the cap is the
|
// 32-bit mask. The shared list is capped at 16, in group order, so the cap is the
|
||||||
// same list for every pixel.
|
// same list for every pixel. Each entry is either a source for its span or not; the
|
||||||
|
// flag is in the third vec4 alongside the screen half-extents, so no extra data is
|
||||||
|
// needed.
|
||||||
static constexpr int kMaxRects = 16;
|
static constexpr int kMaxRects = 16;
|
||||||
|
|
||||||
QVector<BlobRectData> rects;
|
QVector<BlobRectData> rects;
|
||||||
@@ -109,6 +111,7 @@ class BlobGroup2 : public QQuickItem {
|
|||||||
private:
|
private:
|
||||||
void ensurePhysicsUpdated();
|
void ensurePhysicsUpdated();
|
||||||
void ensureDataBuilt();
|
void ensureDataBuilt();
|
||||||
|
void resolveSources(const QVector<BlobShape2*>& entryShapes);
|
||||||
void fillMaterial(BlobMaterial* material);
|
void fillMaterial(BlobMaterial* material);
|
||||||
|
|
||||||
qreal m_smoothing = 32.0;
|
qreal m_smoothing = 32.0;
|
||||||
|
|||||||
@@ -92,7 +92,12 @@ bool BlobMaterialShader::updateUniformData(
|
|||||||
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
|
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
|
||||||
const float d0[4] = {r.cx, r.cy, r.hw, r.hh};
|
const float d0[4] = {r.cx, r.cy, r.hw, r.hh};
|
||||||
const float d1[4] = {maskAsFloat, r.offsetX, r.offsetY, r.minEig};
|
const float d1[4] = {maskAsFloat, r.offsetX, r.offsetY, r.minEig};
|
||||||
const float d3[4] = {r.screenHalfX, r.screenHalfY, 0.0f, 0.0f};
|
const float d3[4] = {
|
||||||
|
r.screenHalfX,
|
||||||
|
r.screenHalfY,
|
||||||
|
r.source ? 1.0f : 0.0f,
|
||||||
|
0.0f,
|
||||||
|
};
|
||||||
memcpy(buf->data() + base, d0, 16);
|
memcpy(buf->data() + base, d0, 16);
|
||||||
memcpy(buf->data() + base + 16, d1, 16);
|
memcpy(buf->data() + base + 16, d1, 16);
|
||||||
memcpy(buf->data() + base + 32, r.invDeform, 16);
|
memcpy(buf->data() + base + 32, r.invDeform, 16);
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ struct BlobRectData {
|
|||||||
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
|
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
|
||||||
// Used by the shader to skip smin between excluded pairs.
|
// Used by the shader to skip smin between excluded pairs.
|
||||||
int excludeMask = 0;
|
int excludeMask = 0;
|
||||||
|
// Whether this entry is pushing on the bar edge. Only the shape that is pushing can
|
||||||
|
// shape the edge; a shape at rest still fills its own area, but the shader gives it
|
||||||
|
// no contribution inside the border strip, so it cannot add its own outline to a span
|
||||||
|
// another shape is shaping.
|
||||||
|
bool source = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlobMaterial : public QSGMaterial {
|
class BlobMaterial : public QSGMaterial {
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ class BlobRect2 : public BlobShape2 {
|
|||||||
|
|
||||||
bool isExcluded(const BlobShape2* other) const override;
|
bool isExcluded(const BlobShape2* other) const override;
|
||||||
bool isCornerExcluded(const BlobShape2* other) const override;
|
bool isCornerExcluded(const BlobShape2* other) const override;
|
||||||
|
// The shape that is pushing is the one that can shape the bar edge. A shape that is
|
||||||
|
// retiring still counts, because the retire curve is still moving the surface.
|
||||||
|
bool isMoving() const override { return m_physicsActive || m_retiring; }
|
||||||
void cornerRadii(float out[4]) const override;
|
void cornerRadii(float out[4]) const override;
|
||||||
|
|
||||||
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
|
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class BlobShape2 : public QQuickItem {
|
|||||||
|
|
||||||
virtual bool isExcluded(const BlobShape2*) const { return false; }
|
virtual bool isExcluded(const BlobShape2*) const { return false; }
|
||||||
|
|
||||||
|
// Whether this shape is currently pushing on a border. Only the shape that is pushing
|
||||||
|
// can shape the bar edge, so this decides the source flag.
|
||||||
|
virtual bool isMoving() const { return false; }
|
||||||
|
|
||||||
virtual bool isCornerExcluded(const BlobShape2* /*other*/) const {
|
virtual bool isCornerExcluded(const BlobShape2* /*other*/) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ float smaxSharpA(float a, float b, float k) {
|
|||||||
void main() {
|
void main() {
|
||||||
vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH);
|
vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH);
|
||||||
|
|
||||||
|
// The frame distances are needed before the per-rect pass. In the border strip the
|
||||||
|
// boundary is the frame, displaced only by the shape that is pushing, so a shape that
|
||||||
|
// is not the source must not define the boundary there. It still fills the inner area,
|
||||||
|
// so this only gates the strip, which is the bar edge.
|
||||||
|
float dOuter = 0.0;
|
||||||
|
float dInner = 0.0;
|
||||||
|
bool inStrip = false;
|
||||||
|
if (hasInverted != 0) {
|
||||||
|
dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
|
||||||
|
dInner = sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius);
|
||||||
|
inStrip = dOuter < 0.0 && dInner > 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 1: compute per-rect SDF, and track which rect owns the pixel for the
|
// Phase 1: compute per-rect SDF, and track which rect owns the pixel for the
|
||||||
// per-shape path (BlobGroup). The single group draw (BlobGroup2) has no owner:
|
// per-shape path (BlobGroup). The single group draw (BlobGroup2) has no owner:
|
||||||
// myIndex stays -2, so the ownership test is skipped and this pass writes every
|
// myIndex stays -2, so the ownership test is skipped and this pass writes every
|
||||||
@@ -78,10 +91,17 @@ void main() {
|
|||||||
vec4 props =
|
vec4 props =
|
||||||
rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig
|
rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig
|
||||||
vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
|
vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
|
||||||
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0
|
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, isSource, 0
|
||||||
vec4 radii =
|
vec4 radii =
|
||||||
rectData[i * 5 + 4]; // effective per-corner radii (tr, br, bl, tl)
|
rectData[i * 5 + 4]; // effective per-corner radii (tr, br, bl, tl)
|
||||||
|
|
||||||
|
// A shape that is not pushing does not shape the bar edge, so inside the strip it
|
||||||
|
// must not add its own outline to a span another shape is already shaping.
|
||||||
|
if (inStrip && sh.z < 0.5) {
|
||||||
|
dArr[i] = 1e10;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Offset center for asymmetric deformation
|
// Offset center for asymmetric deformation
|
||||||
vec2 center = rect.xy + props.yz;
|
vec2 center = rect.xy + props.yz;
|
||||||
|
|
||||||
@@ -182,10 +202,6 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasInverted != 0) {
|
if (hasInverted != 0) {
|
||||||
float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
|
|
||||||
float dInner =
|
|
||||||
sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius);
|
|
||||||
|
|
||||||
// Border sinks: track the opposite rect edge, clamped to border thickness
|
// Border sinks: track the opposite rect edge, clamped to border thickness
|
||||||
float innerTop = invertedInner.y - invertedInner.w;
|
float innerTop = invertedInner.y - invertedInner.w;
|
||||||
float innerBot = invertedInner.y + invertedInner.w;
|
float innerBot = invertedInner.y + invertedInner.w;
|
||||||
@@ -198,6 +214,10 @@ void main() {
|
|||||||
|
|
||||||
float sinkValue = 0.0;
|
float sinkValue = 0.0;
|
||||||
for (int i = 0; i < rectCount; i++) {
|
for (int i = 0; i < rectCount; i++) {
|
||||||
|
// Only a shape that is pushing can move the border.
|
||||||
|
if (rectData[i * 5 + 3].z < 0.5)
|
||||||
|
continue;
|
||||||
|
|
||||||
vec4 rect = rectData[i * 5];
|
vec4 rect = rectData[i * 5];
|
||||||
vec4 sinkProps = rectData[i * 5 + 1];
|
vec4 sinkProps = rectData[i * 5 + 1];
|
||||||
vec2 sinkSh = rectData[i * 5 + 3].xy;
|
vec2 sinkSh = rectData[i * 5 + 3].xy;
|
||||||
@@ -208,7 +228,7 @@ void main() {
|
|||||||
// Delay sink to absorb smin blend depth (cubic smin max = k/6)
|
// Delay sink to absorb smin blend depth (cubic smin max = k/6)
|
||||||
float preOff = smoothFactor * (1.0 / 6.0);
|
float preOff = smoothFactor * (1.0 / 6.0);
|
||||||
|
|
||||||
// Top border: track rect's BOTTOM edge, only within border thickness
|
// Top border: track rect's BOTTOM edge, clamped to border thickness
|
||||||
float topPen = clamp(innerTop - (ctr.y + sinkSh.y) - preOff, 0.0,
|
float topPen = clamp(innerTop - (ctr.y + sinkSh.y) - preOff, 0.0,
|
||||||
innerTop - outerTop);
|
innerTop - outerTop);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user