Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
566b71d386 | ||
|
|
c5608dc9b9 | ||
|
|
78a9577209 | ||
|
|
90184bff58 | ||
|
|
abdde2450d | ||
|
|
fd05b3d4c4 | ||
|
|
067291f478 | ||
|
|
395d1dad3d | ||
|
|
118b8f482a | ||
|
|
b58b4fdc9c | ||
|
|
a56afd7fac | ||
|
|
b0cb43f75c | ||
|
|
e655ec5bc7 | ||
|
|
dc0f222ace | ||
|
|
de19fd6ac4 | ||
|
|
9edbcbd610 | ||
|
|
cd44d27b74 | ||
|
|
0d0faecb1c |
@@ -4,12 +4,16 @@ import Quickshell
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import ZShell.Internal
|
import ZShell.Internal
|
||||||
import qs.Components
|
import qs.Components
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property alias content: contentLoader.item
|
readonly property alias content: contentLoader.item
|
||||||
|
|
||||||
required property ScreenState screenState
|
required property ScreenState screenState
|
||||||
|
property rect holeRect
|
||||||
|
property real holeRadius: 0
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
@@ -18,6 +22,9 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
sourceComponent: StrokeCanvas {
|
sourceComponent: StrokeCanvas {
|
||||||
|
anchors.fill: parent
|
||||||
|
hole: root.holeRect
|
||||||
|
holeRadius: root.holeRadius
|
||||||
penColor: root.screenState.penColor
|
penColor: root.screenState.penColor
|
||||||
penWidth: root.screenState.penWidth
|
penWidth: root.screenState.penWidth
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import ZShell.Config
|
||||||
|
import qs.Components
|
||||||
|
|
||||||
|
CustomMouseArea {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property real borderThickness
|
||||||
|
required property Drawing drawing
|
||||||
|
required property Panels panels
|
||||||
|
required property ScreenState screenState
|
||||||
|
required property EdgeGeometry geometry
|
||||||
|
|
||||||
|
function inLeftPanel(panel: Item, x: real, y: real): bool {
|
||||||
|
return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withinPanelHeight(panel: Item, x: real, y: real): bool {
|
||||||
|
const panelY = panel.y + geometry.insetTop(borderThickness);
|
||||||
|
return y >= panelY && y <= panelY + panel.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
cursorShape: Qt.BlankCursor
|
||||||
|
enabled: z > 0
|
||||||
|
hoverEnabled: true
|
||||||
|
focus: root.screenState.isDrawing
|
||||||
|
z: 2
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: root.drawing.content
|
||||||
|
property: "hoverPoint"
|
||||||
|
value: Qt.point(root.mouseX, root.mouseY)
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: e => {
|
||||||
|
const mod = e.modifiers;
|
||||||
|
const key = e.key;
|
||||||
|
|
||||||
|
if ((key == Qt.Key_Z) && (mod & Qt.ControlModifier) && (mod & Qt.ShiftModifier)) {
|
||||||
|
root.drawing.content.redo();
|
||||||
|
e.accepted = true;
|
||||||
|
} else if ((key == Qt.Key_Z) && (mod & Qt.ControlModifier)) {
|
||||||
|
root.drawing.content.undoLastGroup();
|
||||||
|
e.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: event => {
|
||||||
|
const x = event.x;
|
||||||
|
const y = event.y;
|
||||||
|
|
||||||
|
if (!drawing.content.hoverVisible)
|
||||||
|
drawing.content.showHover();
|
||||||
|
|
||||||
|
if (event.buttons & Qt.LeftButton) {
|
||||||
|
drawing.content.appendPoint(x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event.buttons & Qt.LeftButton) && inLeftPanel(panels.drawing, x, y)) {
|
||||||
|
z = -2;
|
||||||
|
panels.drawing.expand();
|
||||||
|
drawing.content.hideHover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onPressed: event => {
|
||||||
|
const x = event.x;
|
||||||
|
const y = event.y;
|
||||||
|
|
||||||
|
panels.drawing.collapse();
|
||||||
|
|
||||||
|
if (event.buttons & Qt.LeftButton) {
|
||||||
|
panels.drawing.collapse();
|
||||||
|
drawing.content.beginStroke(x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.button & Qt.RightButton) {
|
||||||
|
drawing.content.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReleased: event => {
|
||||||
|
const x = event.x;
|
||||||
|
const y = event.y;
|
||||||
|
|
||||||
|
if (event.button & Qt.LeftButton) {
|
||||||
|
drawing.content.endStroke();
|
||||||
|
drawing.content.showHover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+121
-198
@@ -1,25 +1,33 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
import ZShell.Internal
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Modules.Bar.Popouts
|
import qs.Modules.Bar.Popouts
|
||||||
|
|
||||||
Item {
|
CustomMouseArea {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
required property Drawing drawing
|
||||||
|
required property DrawingInput input
|
||||||
|
|
||||||
required property Item bar
|
required property Item bar
|
||||||
required property real borderThickness
|
required property real borderThickness
|
||||||
property bool dashboardShortcutActive
|
|
||||||
required property Drawing drawing
|
|
||||||
required property EdgeGeometry geometry
|
required property EdgeGeometry geometry
|
||||||
property bool osdShortcutActive
|
|
||||||
required property Panels panels
|
required property Panels panels
|
||||||
required property Wrapper popouts
|
required property Wrapper popouts
|
||||||
|
|
||||||
required property ShellScreen screen
|
required property ShellScreen screen
|
||||||
|
required property ScreenState screenState
|
||||||
|
|
||||||
|
property bool dashboardShortcutActive
|
||||||
|
property point dragStart
|
||||||
|
property bool osdShortcutActive
|
||||||
|
property bool setInitialPoint: false
|
||||||
property bool singleGestureTriggered: false
|
property bool singleGestureTriggered: false
|
||||||
property bool utilitiesShortcutActive
|
property bool utilitiesShortcutActive
|
||||||
required property ScreenState screenState
|
|
||||||
|
|
||||||
function inBottomPanel(panel: Item, x: real, y: real): bool {
|
function inBottomPanel(panel: Item, x: real, y: real): bool {
|
||||||
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
|
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
|
||||||
@@ -56,124 +64,116 @@ Item {
|
|||||||
return x >= panelX && x <= panelX + panel.width;
|
return x >= panelX && x <= panelX + panel.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
DragHandler {
|
onContainsMouseChanged: {
|
||||||
id: singleHandler
|
if (!containsMouse) {
|
||||||
|
if (!osdShortcutActive) {
|
||||||
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
|
screenState.osd = false;
|
||||||
dragThreshold: 0
|
panels.osd.hovered = false;
|
||||||
enabled: !root.screenState.isDrawing
|
|
||||||
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything
|
|
||||||
maximumPointCount: 1
|
|
||||||
minimumPointCount: 1
|
|
||||||
target: null
|
|
||||||
|
|
||||||
onActiveChanged: {
|
|
||||||
if (!active)
|
|
||||||
root.singleGestureTriggered = false;
|
|
||||||
}
|
|
||||||
onCentroidChanged: {
|
|
||||||
const x = centroid.position.x;
|
|
||||||
const y = centroid.position.y;
|
|
||||||
const pressX = centroid.pressPosition.x;
|
|
||||||
const pressY = centroid.pressPosition.y;
|
|
||||||
const dragX = x - pressX;
|
|
||||||
const dragY = y - pressY;
|
|
||||||
|
|
||||||
if (pressY >= root.screen.height - Config.bar.border && pressX > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold)
|
|
||||||
root.screenState.launcher = true;
|
|
||||||
|
|
||||||
if (root.singleGestureTriggered)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const inDashboard = root.inTopPanel(root.panels.dashboardWrapper, pressX, pressY);
|
|
||||||
|
|
||||||
if (inDashboard && root.withinPanelWidth(root.panels.dashboardWrapper, x, y)) {
|
|
||||||
if (dragY > Config.dashboard.dragThreshold) {
|
|
||||||
root.screenState.dashboard = true;
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
} else if (dragY < -Config.dashboard.dragThreshold) {
|
|
||||||
root.screenState.dashboard = false;
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.geometry.insetTop(root.borderThickness) > pressY && (Config.dashboard.showOnHover || !inDashboard)) {
|
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
|
||||||
if (dragY > 20) {
|
popouts.hasCurrent = false;
|
||||||
root.screenState.settings = true;
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
} else if (dragY < -20) {
|
|
||||||
root.screenState.settings = false;
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pressY > root.screen.height - root.geometry.insetBottom(root.borderThickness) && pressX < root.screen.width / 5 && dragY < -50)
|
if (Config.bar.autoHide || bar.fullscreen)
|
||||||
root.screenState.clipboard = true;
|
bar.isHovered = false;
|
||||||
|
|
||||||
if (!Config.dock.hoverToReveal && pressY > root.screen.height - root.geometry.insetTop(root.borderThickness) && pressX > root.screen.width / 5 && !root.screenState.launcher)
|
if (screenState.dashboard && Config.dashboard.showOnHover)
|
||||||
if (dragY < -10) {
|
screenState.dashboard = false;
|
||||||
root.screenState.dock = true;
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressX > root.screen.width - Config.bar.border && pressY < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) {
|
|
||||||
root.screenState.sidebar = true;
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressX >= root.screen.width - Config.bar.border && pressY > (root.screen.height / 2) && dragX < -20) {
|
|
||||||
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressX <= Config.bar.border && dragX > 20) {
|
|
||||||
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
|
|
||||||
root.singleGestureTriggered = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onPositionChanged: event => {
|
||||||
|
const x = event.x;
|
||||||
|
const y = event.y;
|
||||||
|
const dragX = x - dragStart.x;
|
||||||
|
const dragY = y - dragStart.y;
|
||||||
|
|
||||||
PointHandler {
|
if (screenState.isDrawing && !inLeftPanel(panels.drawing, x, y)) {
|
||||||
id: drawingHandler
|
input.z = 2;
|
||||||
|
panels.drawing.collapse();
|
||||||
|
}
|
||||||
|
|
||||||
property bool setInitialPoint: false
|
if (root.bar.fullscreen && !root.bar.isHovered)
|
||||||
|
unhideTimer.start();
|
||||||
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
|
||||||
enabled: root.screenState.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded)
|
screenState.launcher = true;
|
||||||
|
|
||||||
onActiveChanged: {
|
const entryAt = bar.entryAt(geometry.axisPos(x, y));
|
||||||
if (!active) {
|
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboardWrapper, x, y) && (!entryAt || entryAt === "activeWindow");
|
||||||
setInitialPoint = false;
|
|
||||||
root.drawing.content.endStroke();
|
if (!dashboardShortcutActive) {
|
||||||
} else {
|
screenState.dashboard = showDashboard;
|
||||||
root.panels.drawing.collapse();
|
} else if (showDashboard) {
|
||||||
|
dashboardShortcutActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && withinPanelWidth(panels.dashboardWrapper, x, y)) {
|
||||||
|
if (dragY > Config.dashboard.dragThreshold)
|
||||||
|
screenState.dashboard = true;
|
||||||
|
else if (dragY < -Config.dashboard.dragThreshold)
|
||||||
|
screenState.dashboard = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && !inTopPanel(panels.dashboardWrapper, dragStart.x, dragStart.y)) {
|
||||||
|
if (dragY > 20) {
|
||||||
|
screenState.settings = true;
|
||||||
|
} else if (dragY < -20) {
|
||||||
|
screenState.settings = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onPointChanged: {
|
|
||||||
if (!active)
|
|
||||||
return;
|
|
||||||
const x = point.position.x;
|
|
||||||
const y = point.position.y;
|
|
||||||
const origX = point.pressPosition.x;
|
|
||||||
const origY = point.pressPosition.y;
|
|
||||||
|
|
||||||
if (point.pressedButtons & Qt.RightButton) {
|
if (pressed && dragStart.y > screen.height - geometry.insetBottom(borderThickness) && dragStart.x < screen.width / 5 && dragY < -50)
|
||||||
root.drawing.content.clear();
|
screenState.clipboard = true;
|
||||||
return;
|
|
||||||
|
if (pressed && !Config.dock.hoverToReveal && dragStart.y > screen.height - geometry.insetTop(borderThickness) && dragStart.x > screen.width / 5 && !screenState.launcher)
|
||||||
|
if (dragY < -10)
|
||||||
|
screenState.dock = true;
|
||||||
|
|
||||||
|
if (pressed && dragStart.x > screen.width - Config.bar.border && dragStart.y < (screen.height / 2) && dragX < -Config.sidebar.dragThreshold)
|
||||||
|
screenState.sidebar = true;
|
||||||
|
|
||||||
|
if (pressed && dragStart.x >= screen.width - Config.bar.border && dragStart.y > (screen.height / 2) && dragX < -20)
|
||||||
|
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
|
||||||
|
|
||||||
|
if (pressed && dragStart.x <= Config.bar.border && dragX > 20)
|
||||||
|
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
|
||||||
|
|
||||||
|
if (!screenState.bar && Config.bar.autoHide && geometry.barContains(x, y, true))
|
||||||
|
bar.isHovered = true;
|
||||||
|
|
||||||
|
if (panels.sidebar.offsetScale === 1) {
|
||||||
|
const showOsd = inRightPanel(panels.osdWrapper, x, y);
|
||||||
|
|
||||||
|
if (showOsd) {
|
||||||
|
osdShortcutActive = false;
|
||||||
|
panels.osd.hovered = true;
|
||||||
}
|
}
|
||||||
if (x === 0 && y === 0 && origX === 0 && origY === 0)
|
} else {
|
||||||
return;
|
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
|
||||||
|
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
|
||||||
|
|
||||||
if (!setInitialPoint) {
|
if (!osdShortcutActive) {
|
||||||
setInitialPoint = true;
|
screenState.osd = showOsd;
|
||||||
root.drawing.content.beginStroke(origX, origY);
|
panels.osd.hovered = showOsd;
|
||||||
return;
|
} else if (showOsd) {
|
||||||
|
osdShortcutActive = false;
|
||||||
|
panels.osd.hovered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.drawing.content.appendPoint(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Config.dock.enable && Config.dock.hoverToReveal && !screenState.dock && !screenState.launcher && inBottomPanel(panels.dock, x, y))
|
||||||
|
screenState.dock = true;
|
||||||
|
|
||||||
|
if (geometry.barContains(x, y))
|
||||||
|
bar.checkPopout(geometry.axisPos(x, y));
|
||||||
|
}
|
||||||
|
onPressed: event => {
|
||||||
|
dragStart = Qt.point(event.x, event.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -184,98 +184,20 @@ Item {
|
|||||||
running: false
|
running: false
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (hoverHandler.hovered)
|
if (root.containsMouse)
|
||||||
root.bar.isHovered = true;
|
root.bar.isHovered = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: hoverHandler
|
|
||||||
|
|
||||||
cursorShape: root.screenState.isDrawing && !root.inLeftPanel(root.panels.drawing, point.position.x, point.position.y) ? Qt.BlankCursor : undefined
|
|
||||||
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (!hovered) {
|
|
||||||
if (!root.osdShortcutActive) {
|
|
||||||
root.screenState.osd = false;
|
|
||||||
root.panels.osd.hovered = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!root.popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
|
|
||||||
root.popouts.hasCurrent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.bar.autoHide || root.bar.fullscreen)
|
|
||||||
root.bar.isHovered = false;
|
|
||||||
|
|
||||||
if (root.screenState.dashboard && Config.dashboard.showOnHover)
|
|
||||||
root.screenState.dashboard = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPointChanged: {
|
|
||||||
const x = point.position.x;
|
|
||||||
const y = point.position.y;
|
|
||||||
|
|
||||||
if (root.screenState.isDrawing) {
|
|
||||||
if (root.inLeftPanel(root.panels.drawing, x, y) && !(drawingHandler.point.pressedButtons & Qt.LeftButton)) {
|
|
||||||
root.panels.drawing.expand();
|
|
||||||
root.drawing.content.hideHover();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.drawing.content.showHover(x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!root.screenState.bar && Config.bar.autoHide && root.geometry.barContains(x, y, true))
|
|
||||||
root.bar.isHovered = true;
|
|
||||||
|
|
||||||
if (root.bar.fullscreen && !root.bar.isHovered)
|
|
||||||
unhideTimer.start();
|
|
||||||
|
|
||||||
if (root.panels.sidebar.offsetScale === 1) {
|
|
||||||
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
|
|
||||||
|
|
||||||
if (showOsd) {
|
|
||||||
root.osdShortcutActive = false;
|
|
||||||
root.panels.osd.hovered = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const outOfSidebar = x < root.width - root.panels.sidebar.width * (1 - root.panels.sidebar.offsetScale);
|
|
||||||
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
|
|
||||||
|
|
||||||
if (!root.osdShortcutActive) {
|
|
||||||
root.screenState.osd = showOsd;
|
|
||||||
root.panels.osd.hovered = showOsd;
|
|
||||||
} else if (showOsd) {
|
|
||||||
root.osdShortcutActive = false;
|
|
||||||
root.panels.osd.hovered = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.dock.enable && Config.dock.hoverToReveal && !root.screenState.dock && !root.screenState.launcher && root.inBottomPanel(root.panels.dock, x, y))
|
|
||||||
root.screenState.dock = true;
|
|
||||||
|
|
||||||
const inDashboard = root.inTopPanel(root.panels.dashboardWrapper, x, y);
|
|
||||||
|
|
||||||
if (Config.dashboard.showOnHover)
|
|
||||||
root.screenState.dashboard = inDashboard;
|
|
||||||
|
|
||||||
if (root.geometry.barContains(x, y))
|
|
||||||
root.bar.checkPopout(root.geometry.axisPos(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onDashboardChanged() {
|
function onDashboardChanged(): void {
|
||||||
if (root.screenState.dashboard) {
|
if (root.screenState.dashboard) {
|
||||||
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
|
const inDashboardArea = root.inTopPanel(root.panels.dashboardWrapper, root.mouseX, root.mouseY);
|
||||||
if (!inDashboardArea) {
|
if (!inDashboardArea) {
|
||||||
root.dashboardShortcutActive = true;
|
root.dashboardShortcutActive = true;
|
||||||
}
|
}
|
||||||
if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x)
|
|
||||||
root.screenState.launcher = false;
|
|
||||||
|
|
||||||
|
root.screenState.launcher = false;
|
||||||
root.screenState.settings = false;
|
root.screenState.settings = false;
|
||||||
root.screenState.sidebar = false;
|
root.screenState.sidebar = false;
|
||||||
root.screenState.resources = false;
|
root.screenState.resources = false;
|
||||||
@@ -285,12 +207,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onIsDrawingChanged() {
|
function onIsDrawingChanged(): void {
|
||||||
if (!root.screenState.isDrawing)
|
if (root.screenState.isDrawing) {
|
||||||
root.drawing.clear();
|
root.drawing.content.showHover();
|
||||||
|
} else {
|
||||||
|
root.drawing.content.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLauncherChanged() {
|
function onLauncherChanged(): void {
|
||||||
if (!root.screenState.launcher) {
|
if (!root.screenState.launcher) {
|
||||||
root.dashboardShortcutActive = false;
|
root.dashboardShortcutActive = false;
|
||||||
root.osdShortcutActive = false;
|
root.osdShortcutActive = false;
|
||||||
@@ -305,16 +230,13 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (root.screenState.launcher) {
|
if (root.screenState.launcher) {
|
||||||
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
|
root.screenState.dashboard = false;
|
||||||
root.screenState.dashboard = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.screenState.dock = false;
|
root.screenState.dock = false;
|
||||||
root.screenState.settings = false;
|
root.screenState.settings = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOsdChanged() {
|
function onOsdChanged(): void {
|
||||||
if (root.screenState.osd) {
|
if (root.screenState.osd) {
|
||||||
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
|
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
|
||||||
if (!inOsdArea) {
|
if (!inOsdArea) {
|
||||||
@@ -325,7 +247,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResourcesChanged() {
|
function onResourcesChanged(): void {
|
||||||
if (root.screenState.resources) {
|
if (root.screenState.resources) {
|
||||||
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
|
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
|
||||||
root.popouts.hasCurrent = false;
|
root.popouts.hasCurrent = false;
|
||||||
@@ -335,7 +257,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSettingsChanged() {
|
function onSettingsChanged(): void {
|
||||||
if (root.screenState.settings) {
|
if (root.screenState.settings) {
|
||||||
root.screenState.resources = false;
|
root.screenState.resources = false;
|
||||||
root.screenState.dashboard = false;
|
root.screenState.dashboard = false;
|
||||||
@@ -345,9 +267,10 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSidebarChanged() {
|
function onSidebarChanged(): void {
|
||||||
if (root.screenState.sidebar) {
|
if (root.screenState.sidebar) {
|
||||||
root.screenState.dashboard = false;
|
if (root.panels.dashboardWrapper.x + root.panels.dashboardWrapper.width > root.panels.sidebar.x)
|
||||||
|
root.screenState.dashboard = false;
|
||||||
root.screenState.settings = false;
|
root.screenState.settings = false;
|
||||||
root.popouts.hasCurrent = false;
|
root.popouts.hasCurrent = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,315 +0,0 @@
|
|||||||
import Quickshell
|
|
||||||
import QtQuick
|
|
||||||
import ZShell.Config
|
|
||||||
import ZShell.Internal
|
|
||||||
import qs.Components
|
|
||||||
import qs.Helpers
|
|
||||||
import qs.Modules.Bar.Popouts
|
|
||||||
|
|
||||||
CustomMouseArea {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property Item bar
|
|
||||||
required property real borderThickness
|
|
||||||
property bool dashboardShortcutActive
|
|
||||||
property point dragStart
|
|
||||||
required property Drawing drawing
|
|
||||||
required property EdgeGeometry geometry
|
|
||||||
property bool osdShortcutActive
|
|
||||||
required property Panels panels
|
|
||||||
required property Wrapper popouts
|
|
||||||
required property ShellScreen screen
|
|
||||||
required property ScreenState screenState
|
|
||||||
property bool setInitialPoint: false
|
|
||||||
property bool singleGestureTriggered: false
|
|
||||||
property bool utilitiesShortcutActive
|
|
||||||
|
|
||||||
function inBottomPanel(panel: Item, x: real, y: real): bool {
|
|
||||||
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
|
|
||||||
return y > root.height - (panelHeight + geometry.insetBottom(borderThickness)) && withinPanelWidth(panel, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function inLeftPanel(panel: Item, x: real, y: real): bool {
|
|
||||||
return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function inPopoutArea(x: real, y: real): bool {
|
|
||||||
const panel = panels.popoutsWrapper;
|
|
||||||
if (!geometry.horizontal)
|
|
||||||
return inLeftPanel(panel, x, y);
|
|
||||||
return withinPanelWidth(panel, x, y) && withinPanelHeight(panel, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function inRightPanel(panel: Item, x: real, y: real): bool {
|
|
||||||
return x > panel.x + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function inTopPanel(panel: Item, x: real, y: real): bool {
|
|
||||||
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
|
|
||||||
return y < geometry.insetTop(borderThickness) + panelHeight && withinPanelWidth(panel, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function withinPanelHeight(panel: Item, x: real, y: real): bool {
|
|
||||||
const panelY = panel.y + geometry.insetTop(borderThickness);
|
|
||||||
return y >= panelY && y <= panelY + panel.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
function withinPanelWidth(panel: Item, x: real, y: real): bool {
|
|
||||||
const panelX = panel.x + geometry.insetLeft(borderThickness);
|
|
||||||
return x >= panelX && x <= panelX + panel.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
if (!containsMouse) {
|
|
||||||
if (!osdShortcutActive) {
|
|
||||||
screenState.osd = false;
|
|
||||||
panels.osd.hovered = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
|
|
||||||
popouts.hasCurrent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.bar.autoHide || bar.fullscreen)
|
|
||||||
bar.isHovered = false;
|
|
||||||
|
|
||||||
if (screenState.dashboard && Config.dashboard.showOnHover)
|
|
||||||
screenState.dashboard = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPositionChanged: event => {
|
|
||||||
const x = event.x;
|
|
||||||
const y = event.y;
|
|
||||||
const dragX = x - dragStart.x;
|
|
||||||
const dragY = y - dragStart.y;
|
|
||||||
|
|
||||||
if (screenState.isDrawing) {
|
|
||||||
if (pressed) {
|
|
||||||
drawing.content.appendPoint(x, y);
|
|
||||||
} else {
|
|
||||||
if (inLeftPanel(panels.drawing, x, y)) {
|
|
||||||
if (!(pressedButtons & Qt.LeftButton)) {
|
|
||||||
panels.drawing.expand();
|
|
||||||
drawing.content.hideHover();
|
|
||||||
Cursor.clearOverride();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Cursor.setOverride(Qt.BlankCursor);
|
|
||||||
drawing.content.showHover(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (drawing.content.hoverVisible)
|
|
||||||
drawing.content.showHover(x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.bar.fullscreen && !root.bar.isHovered)
|
|
||||||
unhideTimer.start();
|
|
||||||
|
|
||||||
if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
|
|
||||||
screenState.launcher = true;
|
|
||||||
|
|
||||||
const entryAt = bar.entryAt(geometry.axisPos(x, y));
|
|
||||||
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboardWrapper, x, y) && (!entryAt || entryAt === "activeWindow");
|
|
||||||
|
|
||||||
if (!dashboardShortcutActive) {
|
|
||||||
screenState.dashboard = showDashboard;
|
|
||||||
} else if (showDashboard) {
|
|
||||||
dashboardShortcutActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && withinPanelWidth(panels.dashboardWrapper, x, y)) {
|
|
||||||
if (dragY > Config.dashboard.dragThreshold)
|
|
||||||
screenState.dashboard = true;
|
|
||||||
else if (dragY < -Config.dashboard.dragThreshold)
|
|
||||||
screenState.dashboard = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressed && geometry.insetTop(borderThickness) > dragStart.y && !inTopPanel(panels.dashboardWrapper, dragStart.x, dragStart.y)) {
|
|
||||||
if (dragY > 20) {
|
|
||||||
screenState.settings = true;
|
|
||||||
} else if (dragY < -20) {
|
|
||||||
screenState.settings = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressed && dragStart.y > screen.height - geometry.insetBottom(borderThickness) && dragStart.x < screen.width / 5 && dragY < -50)
|
|
||||||
screenState.clipboard = true;
|
|
||||||
|
|
||||||
if (pressed && !Config.dock.hoverToReveal && dragStart.y > screen.height - geometry.insetTop(borderThickness) && dragStart.x > screen.width / 5 && !screenState.launcher)
|
|
||||||
if (dragY < -10)
|
|
||||||
screenState.dock = true;
|
|
||||||
|
|
||||||
if (pressed && dragStart.x > screen.width - Config.bar.border && dragStart.y < (screen.height / 2) && dragX < -Config.sidebar.dragThreshold)
|
|
||||||
screenState.sidebar = true;
|
|
||||||
|
|
||||||
if (pressed && dragStart.x >= screen.width - Config.bar.border && dragStart.y > (screen.height / 2) && dragX < -20)
|
|
||||||
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
|
|
||||||
|
|
||||||
if (pressed && dragStart.x <= Config.bar.border && dragX > 20)
|
|
||||||
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
|
|
||||||
|
|
||||||
if (!screenState.bar && Config.bar.autoHide && geometry.barContains(x, y, true))
|
|
||||||
bar.isHovered = true;
|
|
||||||
|
|
||||||
if (panels.sidebar.offsetScale === 1) {
|
|
||||||
const showOsd = inRightPanel(panels.osdWrapper, x, y);
|
|
||||||
|
|
||||||
if (showOsd) {
|
|
||||||
osdShortcutActive = false;
|
|
||||||
panels.osd.hovered = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
|
|
||||||
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
|
|
||||||
|
|
||||||
if (!osdShortcutActive) {
|
|
||||||
screenState.osd = showOsd;
|
|
||||||
panels.osd.hovered = showOsd;
|
|
||||||
} else if (showOsd) {
|
|
||||||
osdShortcutActive = false;
|
|
||||||
panels.osd.hovered = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config.dock.enable && Config.dock.hoverToReveal && !screenState.dock && !screenState.launcher && inBottomPanel(panels.dock, x, y))
|
|
||||||
screenState.dock = true;
|
|
||||||
|
|
||||||
if (geometry.barContains(x, y))
|
|
||||||
bar.checkPopout(geometry.axisPos(x, y));
|
|
||||||
}
|
|
||||||
onPressed: event => {
|
|
||||||
dragStart = Qt.point(event.x, event.y);
|
|
||||||
|
|
||||||
if (screenState.isDrawing) {
|
|
||||||
if (event.button & Qt.RightButton) {
|
|
||||||
drawing.content.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
|
|
||||||
drawing.content.beginStroke(event.x, event.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPressedChanged: {
|
|
||||||
if (!pressed) {
|
|
||||||
if (screenState.isDrawing) {
|
|
||||||
setInitialPoint = false;
|
|
||||||
drawing.content.endStroke();
|
|
||||||
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
|
|
||||||
drawing.content.showHover(mouseX, mouseY);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (screenState.isDrawing) {
|
|
||||||
panels.drawing.collapse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: unhideTimer
|
|
||||||
|
|
||||||
interval: Config.bar.revealDelay
|
|
||||||
repeat: false
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
if (root.containsMouse)
|
|
||||||
root.bar.isHovered = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onDashboardChanged(): void {
|
|
||||||
if (root.screenState.dashboard) {
|
|
||||||
const inDashboardArea = root.inTopPanel(root.panels.dashboardWrapper, root.mouseX, root.mouseY);
|
|
||||||
if (!inDashboardArea) {
|
|
||||||
root.dashboardShortcutActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.screenState.launcher = false;
|
|
||||||
root.screenState.settings = false;
|
|
||||||
root.screenState.sidebar = false;
|
|
||||||
root.screenState.resources = false;
|
|
||||||
root.popouts.hasCurrent = false;
|
|
||||||
} else {
|
|
||||||
root.dashboardShortcutActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onIsDrawingChanged(): void {
|
|
||||||
if (root.screenState.isDrawing) {
|
|
||||||
Cursor.setOverride(Qt.BlankCursor);
|
|
||||||
} else {
|
|
||||||
Cursor.clearOverride();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onLauncherChanged(): void {
|
|
||||||
if (!root.screenState.launcher) {
|
|
||||||
root.dashboardShortcutActive = false;
|
|
||||||
root.osdShortcutActive = false;
|
|
||||||
root.utilitiesShortcutActive = false;
|
|
||||||
|
|
||||||
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
|
|
||||||
|
|
||||||
if (!inOsdArea) {
|
|
||||||
root.screenState.osd = false;
|
|
||||||
root.panels.osd.hovered = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.screenState.launcher) {
|
|
||||||
root.screenState.dashboard = false;
|
|
||||||
root.screenState.dock = false;
|
|
||||||
root.screenState.settings = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOsdChanged(): void {
|
|
||||||
if (root.screenState.osd) {
|
|
||||||
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
|
|
||||||
if (!inOsdArea) {
|
|
||||||
root.osdShortcutActive = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
root.osdShortcutActive = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onResourcesChanged(): void {
|
|
||||||
if (root.screenState.resources) {
|
|
||||||
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
|
|
||||||
root.popouts.hasCurrent = false;
|
|
||||||
|
|
||||||
root.screenState.settings = false;
|
|
||||||
root.screenState.dashboard = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSettingsChanged(): void {
|
|
||||||
if (root.screenState.settings) {
|
|
||||||
root.screenState.resources = false;
|
|
||||||
root.screenState.dashboard = false;
|
|
||||||
root.screenState.sidebar = false;
|
|
||||||
root.panels.popouts.hasCurrent = false;
|
|
||||||
root.screenState.launcher = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSidebarChanged(): void {
|
|
||||||
if (root.screenState.sidebar) {
|
|
||||||
if (root.panels.dashboardWrapper.x + root.panels.dashboardWrapper.width > root.panels.sidebar.x)
|
|
||||||
root.screenState.dashboard = false;
|
|
||||||
root.screenState.settings = false;
|
|
||||||
root.popouts.hasCurrent = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target: root.screenState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+8
-3
@@ -154,8 +154,10 @@ Item {
|
|||||||
|
|
||||||
property real offsetScale: dashboard.shouldBeActive ? 0 : 1
|
property real offsetScale: dashboard.shouldBeActive ? 0 : 1
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
||||||
|
x: ZUtils.snap((parent.width - implicitWidth) / 2, dashboardWrapper)
|
||||||
|
|
||||||
clip: offsetScale > 0
|
clip: offsetScale > 0
|
||||||
implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
|
implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
|
||||||
implicitWidth: dashboard.implicitWidth
|
implicitWidth: dashboard.implicitWidth
|
||||||
@@ -194,8 +196,11 @@ Item {
|
|||||||
Settings.Wrapper {
|
Settings.Wrapper {
|
||||||
id: settings
|
id: settings
|
||||||
|
|
||||||
anchors.centerIn: parent
|
x: ZUtils.snap((parent.width - implicitWidth) / 2, settings)
|
||||||
anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
|
y: ZUtils.snap((parent.height - implicitHeight) / 2 - (implicitHeight + 5 + (parent.height - implicitHeight) / 2) * offsetScale, settings)
|
||||||
|
|
||||||
|
// anchors.centerIn: parent
|
||||||
|
// anchors.verticalCenterOffset: (-implicitHeight - 5 - ((root.height - implicitHeight) / 2)) * offsetScale
|
||||||
panels: root
|
panels: root
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
screenState: root.screenState
|
screenState: root.screenState
|
||||||
|
|||||||
+24
-2
@@ -56,7 +56,7 @@ CustomWindow {
|
|||||||
property color surfaceColor: Colors.tPalette.m3surface
|
property color surfaceColor: Colors.tPalette.m3surface
|
||||||
|
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
WlrLayershell.keyboardFocus: screenState.screenshot ? WlrKeyboardFocus.Exclusive : screenState.launcher || screenState.settings || screenState.sidebar ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: screenState.screenshot ? WlrKeyboardFocus.Exclusive : screenState.launcher || screenState.settings || screenState.sidebar || screenState.isDrawing ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||||
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
|
WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
mask: screenState.isDrawing || screenState.screenshot ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
|
mask: screenState.isDrawing || screenState.screenshot ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
|
||||||
@@ -347,12 +347,31 @@ CustomWindow {
|
|||||||
Drawing {
|
Drawing {
|
||||||
id: drawing
|
id: drawing
|
||||||
|
|
||||||
|
readonly property int extraWidth: drawingBg.radius * 2
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
holeRect: Qt.rect(root.borderThickness + panels.drawing.x - extraWidth, panels.drawing.y + bar.implicitHeight, panels.drawing.implicitWidth + extraWidth, panels.drawing.implicitHeight)
|
||||||
|
holeRadius: drawingBg.radius
|
||||||
screenState: root.screenState
|
screenState: root.screenState
|
||||||
z: 2
|
z: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
Interactions2 {
|
LazyLoader {
|
||||||
|
id: lazyLoader
|
||||||
|
|
||||||
|
activeAsync: root.screenState.isDrawing
|
||||||
|
|
||||||
|
component: DrawingInput {
|
||||||
|
borderThickness: root.borderLayoutThickness
|
||||||
|
drawing: drawing
|
||||||
|
panels: panels
|
||||||
|
screenState: root.screenState
|
||||||
|
geometry: geometry
|
||||||
|
parent: root.contentItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Interactions {
|
||||||
id: interactions
|
id: interactions
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -365,6 +384,8 @@ CustomWindow {
|
|||||||
popouts: panels.popouts
|
popouts: panels.popouts
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
screenState: root.screenState
|
screenState: root.screenState
|
||||||
|
input: lazyLoader.item
|
||||||
|
z: 1
|
||||||
|
|
||||||
Panels {
|
Panels {
|
||||||
id: panels
|
id: panels
|
||||||
@@ -442,6 +463,7 @@ CustomWindow {
|
|||||||
parent: root.contentItem
|
parent: root.contentItem
|
||||||
screen: root.screen
|
screen: root.screen
|
||||||
screenState: root.screenState
|
screenState: root.screenState
|
||||||
|
z: 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -8,6 +8,7 @@ import ZShell.Components
|
|||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Modules.Bar.Components
|
import qs.Modules.Bar.Components
|
||||||
import qs.Modules.Bar.Popouts
|
import qs.Modules.Bar.Popouts
|
||||||
|
import ZShell
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
id: root
|
id: root
|
||||||
@@ -23,7 +24,7 @@ SnapLayout {
|
|||||||
|
|
||||||
function axisCenterOf(item: Item): real {
|
function axisCenterOf(item: Item): real {
|
||||||
const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
|
const c = item.mapToItem(root, item.implicitWidth / 2, item.implicitHeight / 2);
|
||||||
return horizontal ? Math.floor(c.x) : Math.floor(c.y);
|
return horizontal ? ZUtils.snap(c.x, item) : ZUtils.snap(c.y, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPopout(pos: real): void {
|
function checkPopout(pos: real): void {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
import ZShell
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -36,7 +37,7 @@ Item {
|
|||||||
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
|
const diff = parent.width - Math.floor(off + content.nonAnimWidth);
|
||||||
if (diff < 0)
|
if (diff < 0)
|
||||||
return off + diff;
|
return off + diff;
|
||||||
return Math.floor(Math.max(off, 0));
|
return ZUtils.snap(Math.max(off, 0), root);
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
if (horizontal)
|
if (horizontal)
|
||||||
@@ -46,12 +47,11 @@ Item {
|
|||||||
const diff = parent.height - Math.floor(off + content.nonAnimHeight);
|
const diff = parent.height - Math.floor(off + content.nonAnimHeight);
|
||||||
if (diff < 0)
|
if (diff < 0)
|
||||||
return off + diff;
|
return off + diff;
|
||||||
return Math.max(off, 0);
|
return ZUtils.snap(Math.max(off, 0), root);
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on offsetScale {
|
Behavior on offsetScale {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
enabled: !root.horizontal || root.offsetScale < 1
|
enabled: !root.horizontal || root.offsetScale < 1
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Quickshell
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
|
import ZShell
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -26,8 +27,8 @@ Item {
|
|||||||
detachedMode = "";
|
detachedMode = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: nonAnimHeight
|
implicitHeight: ZUtils.snap(nonAnimHeight, root)
|
||||||
implicitWidth: nonAnimWidth
|
implicitWidth: ZUtils.snap(nonAnimWidth, root)
|
||||||
|
|
||||||
Behavior on implicitHeight {
|
Behavior on implicitHeight {
|
||||||
enabled: root.offsetScale < 1
|
enabled: root.offsetScale < 1
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Item {
|
|||||||
implicitWidth: btn.implicitWidth * 0.9
|
implicitWidth: btn.implicitWidth * 0.9
|
||||||
implicitHeight: btn.implicitHeight * 0.9
|
implicitHeight: btn.implicitHeight * 0.9
|
||||||
|
|
||||||
BlobGroup {
|
BlobGroup2 {
|
||||||
id: blobGroup
|
id: blobGroup
|
||||||
|
|
||||||
color: Colors.palette.m3surfaceContainerHighest
|
color: Colors.palette.m3surfaceContainerHighest
|
||||||
@@ -29,7 +29,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BlobRect {
|
BlobRect2 {
|
||||||
id: btnRect
|
id: btnRect
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -42,7 +42,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BlobRect {
|
BlobRect2 {
|
||||||
id: rect
|
id: rect
|
||||||
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -143,8 +143,8 @@ Item {
|
|||||||
]
|
]
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
id: layout
|
id: layout
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Tokens.spacing.extraSmall
|
spacing: Tokens.spacing.extraSmall
|
||||||
|
|||||||
@@ -53,24 +53,10 @@ Item {
|
|||||||
lock: root.lock
|
lock: root.lock
|
||||||
}
|
}
|
||||||
|
|
||||||
SnapLayout {
|
NotifDock {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
spacing: Tokens.spacing.medium
|
lock: root.lock
|
||||||
orientation: Qt.Vertical
|
|
||||||
|
|
||||||
CustomRect {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
bottomRightRadius: Tokens.rounding.largeIncreased
|
|
||||||
color: Colors.tPalette.m3surfaceContainer
|
|
||||||
radius: Tokens.rounding.small
|
|
||||||
topRightRadius: Tokens.rounding.largeIncreased
|
|
||||||
|
|
||||||
NotifDock {
|
|
||||||
lock: root.lock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ CustomRect {
|
|||||||
topRightRadius: Tokens.rounding.largeIncreased
|
topRightRadius: Tokens.rounding.largeIncreased
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Tokens.padding.large
|
anchors.margins: Tokens.padding.large
|
||||||
|
|
||||||
@@ -52,16 +51,10 @@ CustomRect {
|
|||||||
duration: Tokens.anim.durations.extraLarge
|
duration: Tokens.anim.durations.extraLarge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceComponent: SnapLayout {
|
sourceComponent: CustomText {
|
||||||
orientation: Qt.Vertical
|
color: Colors.palette.m3outlineVariant
|
||||||
spacing: Tokens.spacing.largeIncreased
|
font: Tokens.font.headline.builders.small.width(90).build()
|
||||||
|
text: qsTr("No Notifications")
|
||||||
CustomText {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
color: Colors.palette.m3outlineVariant
|
|
||||||
font: Tokens.font.headline.builders.small.width(90).build()
|
|
||||||
text: qsTr("No Notifications")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import ZShell.Config
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool animateScroll: false
|
property bool animateScroll: false
|
||||||
@@ -69,6 +68,7 @@ orientation: Qt.Vertical
|
|||||||
}
|
}
|
||||||
|
|
||||||
spacing: Tokens.spacing.largeIncreased
|
spacing: Tokens.spacing.largeIncreased
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
Component.onCompleted: applySearchAnchor()
|
Component.onCompleted: applySearchAnchor()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import ZShell.Components
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -10,7 +11,7 @@ CustomText {
|
|||||||
Layout.bottomMargin: Tokens.spacing.extraSmall
|
Layout.bottomMargin: Tokens.spacing.extraSmall
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: Tokens.padding.extraSmall
|
Layout.leftMargin: Tokens.padding.extraSmall
|
||||||
Layout.topMargin: first ? 0 : Tokens.spacing.largeIncreased - ((parent as ColumnLayout).spacing ?? 0)
|
Layout.topMargin: first ? 0 : Tokens.spacing.largeIncreased - ((parent as SnapLayout).spacing ?? 0)
|
||||||
color: Colors.palette.m3onSurfaceVariant
|
color: Colors.palette.m3onSurfaceVariant
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font: Tokens.font.label.medium
|
font: Tokens.font.label.medium
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ PageBase {
|
|||||||
title: qsTr("All apps")
|
title: qsTr("All apps")
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
||||||
spacing: Tokens.spacing.extraSmall / 2
|
spacing: Tokens.spacing.extraSmall / 2
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
width: root.cappedWidth
|
width: root.cappedWidth
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -54,6 +56,7 @@ orientation: Qt.Vertical
|
|||||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||||
anchors.margins: Tokens.padding.small
|
anchors.margins: Tokens.padding.small
|
||||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||||
|
|
||||||
spacing: Tokens.spacing.medium
|
spacing: Tokens.spacing.medium
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
@@ -63,8 +66,9 @@ orientation: Qt.Vertical
|
|||||||
}
|
}
|
||||||
|
|
||||||
SnapLayout {
|
SnapLayout {
|
||||||
orientation: Qt.Vertical
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
orientation: Qt.Vertical
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
CustomText {
|
CustomText {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ find_package(Qt6 REQUIRED COMPONENTS
|
|||||||
GuiPrivate
|
GuiPrivate
|
||||||
Quick
|
Quick
|
||||||
CanvasPainter
|
CanvasPainter
|
||||||
|
CanvasPainterTools
|
||||||
QuickPrivate
|
QuickPrivate
|
||||||
QuickLayouts
|
QuickLayouts
|
||||||
QuickLayoutsPrivate
|
QuickLayoutsPrivate
|
||||||
|
|||||||
@@ -2,7 +2,17 @@
|
|||||||
#include "blobinvertedrect.hpp"
|
#include "blobinvertedrect.hpp"
|
||||||
#include "blobshape.hpp"
|
#include "blobshape.hpp"
|
||||||
|
|
||||||
BlobGroup::BlobGroup(QObject* parent) : QObject(parent) {}
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int k_pumpMs = 16;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BlobGroup::BlobGroup(QObject* parent) : QObject(parent) {
|
||||||
|
m_physicsPump.setInterval(k_pumpMs);
|
||||||
|
m_physicsPump.setTimerType(Qt::CoarseTimer);
|
||||||
|
connect(&m_physicsPump, &QTimer::timeout, this, &BlobGroup::pumpPhysics);
|
||||||
|
}
|
||||||
|
|
||||||
BlobGroup::~BlobGroup() {
|
BlobGroup::~BlobGroup() {
|
||||||
for (auto* shape : std::as_const(m_shapes))
|
for (auto* shape : std::as_const(m_shapes))
|
||||||
@@ -12,6 +22,7 @@ BlobGroup::~BlobGroup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::setSmoothing(qreal s) {
|
void BlobGroup::setSmoothing(qreal s) {
|
||||||
|
if (s < 0.5) s = 0.5;
|
||||||
if (qFuzzyCompare(m_smoothing, s)) return;
|
if (qFuzzyCompare(m_smoothing, s)) return;
|
||||||
m_smoothing = s;
|
m_smoothing = s;
|
||||||
emit smoothingChanged();
|
emit smoothingChanged();
|
||||||
@@ -73,7 +84,6 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
|
|||||||
source->polish();
|
source->polish();
|
||||||
source->update();
|
source->update();
|
||||||
|
|
||||||
// Use cached padded rects to find spatial neighbors
|
|
||||||
const float pad = static_cast<float>(m_smoothing) * 2.0f;
|
const float pad = static_cast<float>(m_smoothing) * 2.0f;
|
||||||
const QRectF srcRect(
|
const QRectF srcRect(
|
||||||
static_cast<double>(source->m_cachedPaddedX - pad),
|
static_cast<double>(source->m_cachedPaddedX - pad),
|
||||||
@@ -103,6 +113,33 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
|
|||||||
void BlobGroup::ensurePhysicsUpdated() {
|
void BlobGroup::ensurePhysicsUpdated() {
|
||||||
if (m_physicsUpdated) return;
|
if (m_physicsUpdated) return;
|
||||||
m_physicsUpdated = true;
|
m_physicsUpdated = true;
|
||||||
|
for (auto* shape : std::as_const(m_shapes)) {
|
||||||
|
shape->updatePhysics();
|
||||||
|
if (shape->needsFrame()) {
|
||||||
|
shape->polish();
|
||||||
|
shape->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobGroup::requestPhysics() {
|
||||||
|
if (!m_physicsPump.isActive()) m_physicsPump.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobGroup::pumpPhysics() {
|
||||||
for (auto* shape : std::as_const(m_shapes))
|
for (auto* shape : std::as_const(m_shapes))
|
||||||
shape->updatePhysics();
|
shape->updatePhysics();
|
||||||
|
|
||||||
|
const bool anyMoving = std::any_of(
|
||||||
|
m_shapes.cbegin(), m_shapes.cend(), [](const BlobShape* shape) {
|
||||||
|
return shape->needsFrame();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!anyMoving) {
|
||||||
|
m_physicsPump.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
markDirty();
|
||||||
|
m_physicsUpdated = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
|
#include <qtimer.h>
|
||||||
|
|
||||||
class BlobShape;
|
class BlobShape;
|
||||||
class BlobInvertedRect;
|
class BlobInvertedRect;
|
||||||
@@ -51,16 +52,21 @@ class BlobGroup : public QObject {
|
|||||||
void markShapeDirty(BlobShape* source);
|
void markShapeDirty(BlobShape* source);
|
||||||
void ensurePhysicsUpdated();
|
void ensurePhysicsUpdated();
|
||||||
|
|
||||||
|
void requestPhysics();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void smoothingChanged();
|
void smoothingChanged();
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
void cornerFillChanged();
|
void cornerFillChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void pumpPhysics();
|
||||||
|
|
||||||
qreal m_smoothing = 32.0;
|
qreal m_smoothing = 32.0;
|
||||||
QColor m_color{0x44, 0x88, 0xff};
|
QColor m_color{0x44, 0x88, 0xff};
|
||||||
bool m_cornerFill = true;
|
bool m_cornerFill = true;
|
||||||
QList<BlobShape*> m_shapes;
|
QList<BlobShape*> m_shapes;
|
||||||
BlobInvertedRect* m_invertedRect = nullptr;
|
BlobInvertedRect* m_invertedRect = nullptr;
|
||||||
bool m_physicsUpdated = false;
|
bool m_physicsUpdated = false;
|
||||||
|
QTimer m_physicsPump;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,8 +29,15 @@ static float cornerFillFactor(float sd, float smoothFactor) {
|
|||||||
return std::max(outside, inside);
|
return std::max(outside, inside);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int k_pumpMs = 16;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
BlobGroup2::BlobGroup2(QQuickItem* parent) : QQuickItem(parent) {
|
BlobGroup2::BlobGroup2(QQuickItem* parent) : QQuickItem(parent) {
|
||||||
setFlag(ItemHasContents);
|
setFlag(ItemHasContents);
|
||||||
|
m_physicsPump.setInterval(k_pumpMs);
|
||||||
|
m_physicsPump.setTimerType(Qt::CoarseTimer);
|
||||||
|
connect(&m_physicsPump, &QTimer::timeout, this, &BlobGroup2::pumpPhysics);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlobGroup2::~BlobGroup2() {
|
BlobGroup2::~BlobGroup2() {
|
||||||
@@ -41,6 +48,7 @@ BlobGroup2::~BlobGroup2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup2::setSmoothing(qreal s) {
|
void BlobGroup2::setSmoothing(qreal s) {
|
||||||
|
if (s < 0.5) s = 0.5;
|
||||||
if (qFuzzyCompare(m_smoothing, s)) return;
|
if (qFuzzyCompare(m_smoothing, s)) return;
|
||||||
m_smoothing = s;
|
m_smoothing = s;
|
||||||
emit smoothingChanged();
|
emit smoothingChanged();
|
||||||
@@ -101,17 +109,11 @@ void BlobGroup2::clearInvertedRect(BlobInvertedRect2* rect) {
|
|||||||
void BlobGroup2::markDirty() {
|
void BlobGroup2::markDirty() {
|
||||||
m_physicsUpdated = false;
|
m_physicsUpdated = false;
|
||||||
m_dataBuilt = false;
|
m_dataBuilt = false;
|
||||||
// Physics runs during polish, before the render pass. The panel content is
|
|
||||||
// transformed by deformMatrix from QML, so the matrix has to be final for this
|
|
||||||
// frame; building the data in updatePaintNode would let the transform lag one
|
|
||||||
// frame behind the material.
|
|
||||||
polish();
|
polish();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup2::updatePolish() {
|
void BlobGroup2::updatePolish() {
|
||||||
// The group is the only thing that draws, so it also drives the shapes'
|
|
||||||
// physics.
|
|
||||||
ensureDataBuilt();
|
ensureDataBuilt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,32 +124,53 @@ void BlobGroup2::ensurePhysicsUpdated() {
|
|||||||
shape->updatePhysics();
|
shape->updatePhysics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlobGroup2::requestPhysics() {
|
||||||
|
if (!m_physicsPump.isActive()) m_physicsPump.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobGroup2::pumpPhysics() {
|
||||||
|
for (auto* shape : std::as_const(m_shapes))
|
||||||
|
shape->updatePhysics();
|
||||||
|
|
||||||
|
m_physicsUpdated = true;
|
||||||
|
|
||||||
|
const bool anyMoving = std::any_of(
|
||||||
|
m_shapes.cbegin(), m_shapes.cend(), [](const BlobShape2* shape) {
|
||||||
|
return shape->needsFrame();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!anyMoving) {
|
||||||
|
m_physicsPump.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dataBuilt = false;
|
||||||
|
polish();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
void BlobGroup2::ensureDataBuilt() {
|
void BlobGroup2::ensureDataBuilt() {
|
||||||
if (m_dataBuilt) return;
|
if (m_dataBuilt) return;
|
||||||
ensurePhysicsUpdated();
|
ensurePhysicsUpdated();
|
||||||
|
|
||||||
// Physics can request the next frame through markDirty(); the data is
|
|
||||||
// refreshed here anyway, so the flag is set after it runs.
|
|
||||||
m_dataBuilt = true;
|
m_dataBuilt = true;
|
||||||
m_data.rects.clear();
|
m_data.rects.clear();
|
||||||
m_bounds = QRectF();
|
m_bounds = QRectF();
|
||||||
|
m_data.hasInverted = false;
|
||||||
|
m_data.invertedRadius = 0;
|
||||||
|
memset(m_data.invertedOuter, 0, sizeof(m_data.invertedOuter));
|
||||||
|
memset(m_data.invertedInner, 0, sizeof(m_data.invertedInner));
|
||||||
|
memset(m_data.invertedOuterRadii, 0, sizeof(m_data.invertedOuterRadii));
|
||||||
|
|
||||||
const float smoothFactor = static_cast<float>(m_smoothing);
|
const float smoothFactor = static_cast<float>(m_smoothing);
|
||||||
// The same reach the shader uses to decide whether a rect can contribute, plus
|
|
||||||
// the shadow, so every pixel the shader can give a value to is inside the node.
|
|
||||||
const double extent = static_cast<double>(smoothFactor) * 1.5 +
|
const double extent = static_cast<double>(smoothFactor) * 1.5 +
|
||||||
static_cast<double>(m_shadowRadius);
|
static_cast<double>(m_shadowRadius);
|
||||||
|
|
||||||
// Group order, no pruning by proximity: the shader sees this list for every pixel. A
|
const bool dropAtRest = m_invertedRect != nullptr;
|
||||||
// shape that is not pushing is not in the list at all: shapes here are data for the
|
|
||||||
// group draw, so leaving a panel at rest out means it cannot define any part of the
|
|
||||||
// edge. A closing panel is still in the list while its curve runs out, even with no
|
|
||||||
// size left, which is what keeps the bounce.
|
|
||||||
// entryShapes maps a shared entry back to the shape it came from.
|
|
||||||
QVector<BlobShape2*> entryShapes;
|
QVector<BlobShape2*> entryShapes;
|
||||||
|
|
||||||
for (auto* shape : std::as_const(m_shapes)) {
|
for (auto* shape : std::as_const(m_shapes)) {
|
||||||
if (!shape->isMoving()) continue;
|
if (dropAtRest && !shape->isMoving()) continue;
|
||||||
if (shape->width() <= 0 || shape->height() <= 0) continue;
|
if (shape->width() <= 0 || shape->height() <= 0) continue;
|
||||||
if (static_cast<int>(m_data.rects.size()) >= BlobGroupData::kMaxRects)
|
if (static_cast<int>(m_data.rects.size()) >= BlobGroupData::kMaxRects)
|
||||||
continue;
|
continue;
|
||||||
@@ -194,7 +217,6 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
|
|
||||||
const int count = static_cast<int>(m_data.rects.size());
|
const int count = static_cast<int>(m_data.rects.size());
|
||||||
|
|
||||||
// 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;
|
||||||
BlobShape2* const si = entryShapes[i];
|
BlobShape2* const si = entryShapes[i];
|
||||||
@@ -214,10 +236,11 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
BlobShape2* const si = entryShapes[i];
|
BlobShape2* const si = entryShapes[i];
|
||||||
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
||||||
|
|
||||||
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
|
const float dcx = ri.cx + ri.offsetX, dcy = ri.cy + ri.offsetY;
|
||||||
const float cBrX = ri.cx + ri.hw, cBrY = ri.cy + ri.hh;
|
const float cTrX = dcx + ri.hw, cTrY = dcy - ri.hh;
|
||||||
const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh;
|
const float cBrX = dcx + ri.hw, cBrY = dcy + ri.hh;
|
||||||
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
const float cBlX = dcx - ri.hw, cBlY = dcy + ri.hh;
|
||||||
|
const float cTlX = dcx - ri.hw, cTlY = dcy - ri.hh;
|
||||||
|
|
||||||
if (cornerFill) {
|
if (cornerFill) {
|
||||||
for (int j = 0; j < count; ++j) {
|
for (int j = 0; j < count; ++j) {
|
||||||
@@ -227,37 +250,41 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
|
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si))
|
||||||
continue;
|
continue;
|
||||||
const auto& rj = m_data.rects[j];
|
const auto& rj = m_data.rects[j];
|
||||||
|
const float jcx = rj.cx + rj.offsetX;
|
||||||
|
const float jcy = rj.cy + rj.offsetY;
|
||||||
fTr = std::min(
|
fTr = std::min(
|
||||||
fTr,
|
fTr,
|
||||||
cornerFillFactor(
|
cornerFillFactor(
|
||||||
cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh),
|
cpuSdBox(cTrX, cTrY, jcx, jcy, rj.hw, rj.hh),
|
||||||
smoothFactor));
|
smoothFactor));
|
||||||
fBr = std::min(
|
fBr = std::min(
|
||||||
fBr,
|
fBr,
|
||||||
cornerFillFactor(
|
cornerFillFactor(
|
||||||
cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh),
|
cpuSdBox(cBrX, cBrY, jcx, jcy, rj.hw, rj.hh),
|
||||||
smoothFactor));
|
smoothFactor));
|
||||||
fBl = std::min(
|
fBl = std::min(
|
||||||
fBl,
|
fBl,
|
||||||
cornerFillFactor(
|
cornerFillFactor(
|
||||||
cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh),
|
cpuSdBox(cBlX, cBlY, jcx, jcy, rj.hw, rj.hh),
|
||||||
smoothFactor));
|
smoothFactor));
|
||||||
fTl = std::min(
|
fTl = std::min(
|
||||||
fTl,
|
fTl,
|
||||||
cornerFillFactor(
|
cornerFillFactor(
|
||||||
cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh),
|
cpuSdBox(cTlX, cTlY, jcx, jcy, rj.hw, rj.hh),
|
||||||
smoothFactor));
|
smoothFactor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
|
ri.radius[0] = ri.radius[0] > 0.0f ? std::max(ri.radius[0] * fTr, minR)
|
||||||
ri.radius[1] = std::max(ri.radius[1] * fBr, minR);
|
: 0.0f;
|
||||||
ri.radius[2] = std::max(ri.radius[2] * fBl, minR);
|
ri.radius[1] = ri.radius[1] > 0.0f ? std::max(ri.radius[1] * fBr, minR)
|
||||||
ri.radius[3] = std::max(ri.radius[3] * fTl, minR);
|
: 0.0f;
|
||||||
|
ri.radius[2] = ri.radius[2] > 0.0f ? std::max(ri.radius[2] * fBl, minR)
|
||||||
|
: 0.0f;
|
||||||
|
ri.radius[3] = ri.radius[3] > 0.0f ? std::max(ri.radius[3] * fTl, minR)
|
||||||
|
: 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverted rect. The frame term is evaluated for every pixel in the bounds,
|
|
||||||
// so the 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);
|
||||||
@@ -297,6 +324,8 @@ void BlobGroup2::ensureDataBuilt() {
|
|||||||
m_data.invertedInner[2] = innerHW;
|
m_data.invertedInner[2] = innerHW;
|
||||||
m_data.invertedInner[3] = innerHH;
|
m_data.invertedInner[3] = innerHH;
|
||||||
|
|
||||||
|
inv->outerCornerRadii(m_data.invertedOuterRadii);
|
||||||
|
|
||||||
m_bounds = m_bounds.united(QRectF(
|
m_bounds = m_bounds.united(QRectF(
|
||||||
static_cast<double>(outerCX) - static_cast<double>(outerHW) - extent,
|
static_cast<double>(outerCX) - static_cast<double>(outerHW) - extent,
|
||||||
static_cast<double>(outerCY) - static_cast<double>(outerHH) - extent,
|
static_cast<double>(outerCY) - static_cast<double>(outerHH) - extent,
|
||||||
@@ -311,7 +340,6 @@ void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
|||||||
material->m_paddedH = static_cast<float>(m_bounds.height());
|
material->m_paddedH = static_cast<float>(m_bounds.height());
|
||||||
|
|
||||||
material->m_smoothFactor = static_cast<float>(m_smoothing);
|
material->m_smoothFactor = static_cast<float>(m_smoothing);
|
||||||
// The single group draw has no owner, so the shader skips the ownership test.
|
|
||||||
material->m_myIndex = -2;
|
material->m_myIndex = -2;
|
||||||
material->m_color = m_color;
|
material->m_color = m_color;
|
||||||
material->m_hasInverted = m_data.hasInverted ? 1 : 0;
|
material->m_hasInverted = m_data.hasInverted ? 1 : 0;
|
||||||
@@ -324,6 +352,10 @@ void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
|||||||
material->m_invertedInner,
|
material->m_invertedInner,
|
||||||
data().invertedInner,
|
data().invertedInner,
|
||||||
sizeof(data().invertedInner));
|
sizeof(data().invertedInner));
|
||||||
|
memcpy(
|
||||||
|
material->m_invertedOuterRadii,
|
||||||
|
data().invertedOuterRadii,
|
||||||
|
sizeof(data().invertedOuterRadii));
|
||||||
|
|
||||||
material->m_shadowRadius = static_cast<float>(m_shadowRadius);
|
material->m_shadowRadius = static_cast<float>(m_shadowRadius);
|
||||||
material->m_shadowColor[0] = static_cast<float>(m_shadowColor.redF());
|
material->m_shadowColor[0] = static_cast<float>(m_shadowColor.redF());
|
||||||
@@ -339,8 +371,6 @@ void BlobGroup2::fillMaterial(BlobMaterial* material) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QSGNode* BlobGroup2::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
QSGNode* BlobGroup2::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||||
// Data is built during polish. This only covers a render pass that happens
|
|
||||||
// without one.
|
|
||||||
ensureDataBuilt();
|
ensureDataBuilt();
|
||||||
|
|
||||||
if (m_data.rects.isEmpty() && !m_data.hasInverted) {
|
if (m_data.rects.isEmpty() && !m_data.hasInverted) {
|
||||||
@@ -364,10 +394,6 @@ QSGNode* BlobGroup2::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
|||||||
node->setFlag(QSGNode::OwnsMaterial);
|
node->setFlag(QSGNode::OwnsMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The shader evaluates the field at the scene position a vertex maps to, so
|
|
||||||
// the group item itself must not be transformed. The vertex positions are the
|
|
||||||
// bounds in this item's frame; the material carries the same rect in scene
|
|
||||||
// coordinates.
|
|
||||||
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
|
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
|
||||||
const QPointF p0 = mapFromScene(m_bounds.topLeft());
|
const QPointF p0 = mapFromScene(m_bounds.topLeft());
|
||||||
const QPointF p1 = mapFromScene(m_bounds.bottomRight());
|
const QPointF p1 = mapFromScene(m_bounds.bottomRight());
|
||||||
|
|||||||
@@ -9,22 +9,13 @@
|
|||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qrect.h>
|
#include <qrect.h>
|
||||||
#include <qsgnode.h>
|
#include <qsgnode.h>
|
||||||
|
#include <qtimer.h>
|
||||||
#include <qvector.h>
|
#include <qvector.h>
|
||||||
|
|
||||||
class BlobShape2;
|
class BlobShape2;
|
||||||
class BlobInvertedRect2;
|
class BlobInvertedRect2;
|
||||||
|
|
||||||
// One shared valuation for a whole group, drawn by one node.
|
|
||||||
//
|
|
||||||
// The shapes here are data only: they say where the material should be. The group
|
|
||||||
// draws a single quad over everything the shader can evaluate, so a pixel cannot
|
|
||||||
// be evaluated by one shape and missed by another, and no second draw can
|
|
||||||
// disagree with the first. That is what makes overlapping shapes read as one
|
|
||||||
// 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
|
|
||||||
// mask. The shared list is capped at 16, in group order, so the cap is the
|
|
||||||
// same list for every pixel.
|
|
||||||
static constexpr int kMaxRects = 16;
|
static constexpr int kMaxRects = 16;
|
||||||
|
|
||||||
QVector<BlobRectData> rects;
|
QVector<BlobRectData> rects;
|
||||||
@@ -32,6 +23,7 @@ struct BlobGroupData {
|
|||||||
float invertedRadius = 0;
|
float invertedRadius = 0;
|
||||||
float invertedOuter[4] = {};
|
float invertedOuter[4] = {};
|
||||||
float invertedInner[4] = {};
|
float invertedInner[4] = {};
|
||||||
|
float invertedOuterRadii[4] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlobGroup2 : public QQuickItem {
|
class BlobGroup2 : public QQuickItem {
|
||||||
@@ -91,10 +83,10 @@ class BlobGroup2 : public QQuickItem {
|
|||||||
|
|
||||||
[[nodiscard]] const BlobGroupData& data() const { return m_data; }
|
[[nodiscard]] const BlobGroupData& data() const { return m_data; }
|
||||||
|
|
||||||
// One draw per group, so a change to any shape changes the value for every
|
|
||||||
// pixel. Refresh the whole group.
|
|
||||||
void markDirty();
|
void markDirty();
|
||||||
|
|
||||||
|
void requestPhysics();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void smoothingChanged();
|
void smoothingChanged();
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
@@ -110,6 +102,7 @@ class BlobGroup2 : public QQuickItem {
|
|||||||
void ensurePhysicsUpdated();
|
void ensurePhysicsUpdated();
|
||||||
void ensureDataBuilt();
|
void ensureDataBuilt();
|
||||||
void fillMaterial(BlobMaterial* material);
|
void fillMaterial(BlobMaterial* material);
|
||||||
|
void pumpPhysics();
|
||||||
|
|
||||||
qreal m_smoothing = 32.0;
|
qreal m_smoothing = 32.0;
|
||||||
QColor m_color{0x44, 0x88, 0xff};
|
QColor m_color{0x44, 0x88, 0xff};
|
||||||
@@ -120,9 +113,8 @@ class BlobGroup2 : public QQuickItem {
|
|||||||
BlobInvertedRect2* m_invertedRect = nullptr;
|
BlobInvertedRect2* m_invertedRect = nullptr;
|
||||||
bool m_physicsUpdated = false;
|
bool m_physicsUpdated = false;
|
||||||
bool m_dataBuilt = false;
|
bool m_dataBuilt = false;
|
||||||
|
QTimer m_physicsPump;
|
||||||
BlobGroupData m_data;
|
BlobGroupData m_data;
|
||||||
|
|
||||||
// Scene-space union of what the shader can evaluate for any shape. The node
|
|
||||||
// covers exactly this, so gaps are not possible.
|
|
||||||
QRectF m_bounds;
|
QRectF m_bounds;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,28 +11,24 @@
|
|||||||
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {}
|
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {}
|
||||||
|
|
||||||
static void setFrameIndices(quint16* idx) {
|
static void setFrameIndices(quint16* idx) {
|
||||||
// Top strip: 0-1-4, 1-5-4
|
|
||||||
idx[0] = 0;
|
idx[0] = 0;
|
||||||
idx[1] = 1;
|
idx[1] = 1;
|
||||||
idx[2] = 4;
|
idx[2] = 4;
|
||||||
idx[3] = 1;
|
idx[3] = 1;
|
||||||
idx[4] = 5;
|
idx[4] = 5;
|
||||||
idx[5] = 4;
|
idx[5] = 4;
|
||||||
// Right strip: 1-2-5, 2-6-5
|
|
||||||
idx[6] = 1;
|
idx[6] = 1;
|
||||||
idx[7] = 2;
|
idx[7] = 2;
|
||||||
idx[8] = 5;
|
idx[8] = 5;
|
||||||
idx[9] = 2;
|
idx[9] = 2;
|
||||||
idx[10] = 6;
|
idx[10] = 6;
|
||||||
idx[11] = 5;
|
idx[11] = 5;
|
||||||
// Bottom strip: 2-3-6, 3-7-6
|
|
||||||
idx[12] = 2;
|
idx[12] = 2;
|
||||||
idx[13] = 3;
|
idx[13] = 3;
|
||||||
idx[14] = 6;
|
idx[14] = 6;
|
||||||
idx[15] = 3;
|
idx[15] = 3;
|
||||||
idx[16] = 7;
|
idx[16] = 7;
|
||||||
idx[17] = 6;
|
idx[17] = 6;
|
||||||
// Left strip: 3-0-7, 0-4-7
|
|
||||||
idx[18] = 3;
|
idx[18] = 3;
|
||||||
idx[19] = 0;
|
idx[19] = 0;
|
||||||
idx[20] = 7;
|
idx[20] = 7;
|
||||||
@@ -50,17 +46,20 @@ QSGNode* BlobInvertedRect::updatePaintNode(
|
|||||||
|
|
||||||
const float pad = static_cast<float>(m_group->smoothing());
|
const float pad = static_cast<float>(m_group->smoothing());
|
||||||
|
|
||||||
// Compute inner hole boundary in local coords
|
|
||||||
// Inset past the inner border edge by 2x smoothing to cover the blend zone
|
|
||||||
const float inset = pad * 2.0f;
|
const float inset = pad * 2.0f;
|
||||||
const float holeLeft = static_cast<float>(m_borderLeft) + inset;
|
const float holeLeft = static_cast<float>(m_borderLeft) + inset;
|
||||||
const float holeTop = static_cast<float>(m_borderTop) + inset;
|
const float holeTop = static_cast<float>(m_borderTop) + inset;
|
||||||
const float holeRight = static_cast<float>(width() - m_borderRight) - inset;
|
const float holeRight = static_cast<float>(width() - m_borderRight) - inset;
|
||||||
const float holeBot = static_cast<float>(height() - m_borderBottom) - inset;
|
const float holeBot = static_cast<float>(height() - m_borderBottom) - inset;
|
||||||
|
|
||||||
// If the hole is too small or invalid, fall back to full quad
|
if (holeLeft >= holeRight || holeTop >= holeBot) {
|
||||||
if (holeLeft >= holeRight || holeTop >= holeBot)
|
auto* oldGeoNode = static_cast<QSGGeometryNode*>(oldNode);
|
||||||
|
if (oldNode && oldGeoNode->geometry()->vertexCount() != 4) {
|
||||||
|
delete oldNode;
|
||||||
|
oldNode = nullptr;
|
||||||
|
}
|
||||||
return BlobShape::updatePaintNode(oldNode, nullptr);
|
return BlobShape::updatePaintNode(oldNode, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
auto* node = static_cast<QSGGeometryNode*>(oldNode);
|
auto* node = static_cast<QSGGeometryNode*>(oldNode);
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ QSGNode* BlobInvertedRect::updatePaintNode(
|
|||||||
node->setFlag(QSGNode::OwnsMaterial);
|
node->setFlag(QSGNode::OwnsMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outer bounds (local coords)
|
|
||||||
const float x0 = static_cast<float>(m_localPaddedRect.x());
|
const float x0 = static_cast<float>(m_localPaddedRect.x());
|
||||||
const float y0 = static_cast<float>(m_localPaddedRect.y());
|
const float y0 = static_cast<float>(m_localPaddedRect.y());
|
||||||
const float x1 = x0 + static_cast<float>(m_localPaddedRect.width());
|
const float x1 = x0 + static_cast<float>(m_localPaddedRect.width());
|
||||||
@@ -95,15 +93,12 @@ QSGNode* BlobInvertedRect::updatePaintNode(
|
|||||||
const float w = x1 - x0;
|
const float w = x1 - x0;
|
||||||
const float h = y1 - y0;
|
const float h = y1 - y0;
|
||||||
|
|
||||||
// Update vertex positions and texture coordinates
|
|
||||||
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
|
auto* v = node->geometry()->vertexDataAsTexturedPoint2D();
|
||||||
|
|
||||||
// Outer corners
|
|
||||||
v[0].set(x0, y0, 0.0f, 0.0f);
|
v[0].set(x0, y0, 0.0f, 0.0f);
|
||||||
v[1].set(x1, y0, 1.0f, 0.0f);
|
v[1].set(x1, y0, 1.0f, 0.0f);
|
||||||
v[2].set(x1, y1, 1.0f, 1.0f);
|
v[2].set(x1, y1, 1.0f, 1.0f);
|
||||||
v[3].set(x0, y1, 0.0f, 1.0f);
|
v[3].set(x0, y1, 0.0f, 1.0f);
|
||||||
// Inner corners (hole)
|
|
||||||
v[4].set(holeLeft, holeTop, (holeLeft - x0) / w, (holeTop - y0) / h);
|
v[4].set(holeLeft, holeTop, (holeLeft - x0) / w, (holeTop - y0) / h);
|
||||||
v[5].set(holeRight, holeTop, (holeRight - x0) / w, (holeTop - y0) / h);
|
v[5].set(holeRight, holeTop, (holeRight - x0) / w, (holeTop - y0) / h);
|
||||||
v[6].set(holeRight, holeBot, (holeRight - x0) / w, (holeBot - y0) / h);
|
v[6].set(holeRight, holeBot, (holeRight - x0) / w, (holeBot - y0) / h);
|
||||||
@@ -111,7 +106,6 @@ QSGNode* BlobInvertedRect::updatePaintNode(
|
|||||||
|
|
||||||
node->markDirty(QSGNode::DirtyGeometry);
|
node->markDirty(QSGNode::DirtyGeometry);
|
||||||
|
|
||||||
// Update material uniforms
|
|
||||||
auto* material = static_cast<BlobMaterial*>(node->material());
|
auto* material = static_cast<BlobMaterial*>(node->material());
|
||||||
material->m_paddedX = m_cachedPaddedX;
|
material->m_paddedX = m_cachedPaddedX;
|
||||||
material->m_paddedY = m_cachedPaddedY;
|
material->m_paddedY = m_cachedPaddedY;
|
||||||
@@ -218,7 +212,7 @@ void BlobInvertedRect::outerCornerRadii(float out[4]) const {
|
|||||||
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
const auto base = std::min(static_cast<float>(m_outerRadius), maxR);
|
||||||
out[0] = std::min(
|
out[0] = std::min(
|
||||||
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
m_outerTopRightRadius >= 0 ? static_cast<float>(m_outerTopRightRadius)
|
||||||
: base,
|
: base,
|
||||||
maxR);
|
maxR);
|
||||||
out[1] = std::min(
|
out[1] = std::min(
|
||||||
m_outerBottomRightRadius >= 0
|
m_outerBottomRightRadius >= 0
|
||||||
|
|||||||
@@ -59,12 +59,15 @@ class BlobInvertedRect : public BlobShape {
|
|||||||
|
|
||||||
void setOuterRadius(qreal v);
|
void setOuterRadius(qreal v);
|
||||||
|
|
||||||
// Per-corner outer radii. -1 falls back to outerRadius (mirrors BlobRect).
|
[[nodiscard]] qreal outerTopLeftRadius() const {
|
||||||
[[nodiscard]] qreal outerTopLeftRadius() const { return m_outerTopLeftRadius; }
|
return m_outerTopLeftRadius;
|
||||||
|
}
|
||||||
|
|
||||||
void setOuterTopLeftRadius(qreal v);
|
void setOuterTopLeftRadius(qreal v);
|
||||||
|
|
||||||
[[nodiscard]] qreal outerTopRightRadius() const { return m_outerTopRightRadius; }
|
[[nodiscard]] qreal outerTopRightRadius() const {
|
||||||
|
return m_outerTopRightRadius;
|
||||||
|
}
|
||||||
|
|
||||||
void setOuterTopRightRadius(qreal v);
|
void setOuterTopRightRadius(qreal v);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "blobinvertedrect2.hpp"
|
#include "blobinvertedrect2.hpp"
|
||||||
#include "blobgroup2.hpp"
|
#include "blobgroup2.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
BlobInvertedRect2::BlobInvertedRect2(QQuickItem* parent) : BlobShape2(parent) {}
|
BlobInvertedRect2::BlobInvertedRect2(QQuickItem* parent) : BlobShape2(parent) {}
|
||||||
|
|
||||||
BlobInvertedRect2::~BlobInvertedRect2() {
|
BlobInvertedRect2::~BlobInvertedRect2() {
|
||||||
@@ -35,6 +37,64 @@ void BlobInvertedRect2::setBorderBottom(qreal v) {
|
|||||||
if (m_group) m_group->markDirty();
|
if (m_group) m_group->markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlobInvertedRect2::setOuterRadius(qreal v) {
|
||||||
|
if (qFuzzyCompare(m_outerRadius, v)) return;
|
||||||
|
m_outerRadius = v;
|
||||||
|
emit outerRadiusChanged();
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobInvertedRect2::setOuterTopLeftRadius(qreal v) {
|
||||||
|
if (qFuzzyCompare(m_outerTopLeftRadius, v)) return;
|
||||||
|
m_outerTopLeftRadius = v;
|
||||||
|
emit outerTopLeftRadiusChanged();
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobInvertedRect2::setOuterTopRightRadius(qreal v) {
|
||||||
|
if (qFuzzyCompare(m_outerTopRightRadius, v)) return;
|
||||||
|
m_outerTopRightRadius = v;
|
||||||
|
emit outerTopRightRadiusChanged();
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobInvertedRect2::setOuterBottomLeftRadius(qreal v) {
|
||||||
|
if (qFuzzyCompare(m_outerBottomLeftRadius, v)) return;
|
||||||
|
m_outerBottomLeftRadius = v;
|
||||||
|
emit outerBottomLeftRadiusChanged();
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobInvertedRect2::setOuterBottomRightRadius(qreal v) {
|
||||||
|
if (qFuzzyCompare(m_outerBottomRightRadius, v)) return;
|
||||||
|
m_outerBottomRightRadius = v;
|
||||||
|
emit outerBottomRightRadiusChanged();
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobInvertedRect2::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 BlobInvertedRect2::registerWithGroup() {
|
void BlobInvertedRect2::registerWithGroup() {
|
||||||
if (m_group) m_group->setInvertedRect(this);
|
if (m_group) m_group->setInvertedRect(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ class BlobInvertedRect2 : public BlobShape2 {
|
|||||||
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 BlobInvertedRect2(QQuickItem* parent = nullptr);
|
explicit BlobInvertedRect2(QQuickItem* parent = nullptr);
|
||||||
@@ -40,13 +55,48 @@ class BlobInvertedRect2 : public BlobShape2 {
|
|||||||
|
|
||||||
void setBorderBottom(qreal v);
|
void setBorderBottom(qreal v);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal outerRadius() const { return m_outerRadius; }
|
||||||
|
|
||||||
|
void setOuterRadius(qreal v);
|
||||||
|
|
||||||
|
[[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:
|
||||||
|
void outerCornerRadii(float out[4]) const override;
|
||||||
|
|
||||||
void registerWithGroup() override;
|
void registerWithGroup() override;
|
||||||
void unregisterFromGroup() override;
|
void unregisterFromGroup() override;
|
||||||
|
|
||||||
@@ -55,4 +105,10 @@ class BlobInvertedRect2 : public BlobShape2 {
|
|||||||
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,22 +44,17 @@ bool BlobMaterialShader::updateUniformData(
|
|||||||
memcpy(buf->data() + 64, &opacity, 4);
|
memcpy(buf->data() + 64, &opacity, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Padded rect (offset 68)
|
|
||||||
memcpy(buf->data() + 68, &mat->m_paddedX, 4);
|
memcpy(buf->data() + 68, &mat->m_paddedX, 4);
|
||||||
memcpy(buf->data() + 72, &mat->m_paddedY, 4);
|
memcpy(buf->data() + 72, &mat->m_paddedY, 4);
|
||||||
memcpy(buf->data() + 76, &mat->m_paddedW, 4);
|
memcpy(buf->data() + 76, &mat->m_paddedW, 4);
|
||||||
memcpy(buf->data() + 80, &mat->m_paddedH, 4);
|
memcpy(buf->data() + 80, &mat->m_paddedH, 4);
|
||||||
|
|
||||||
// Smooth factor (offset 84)
|
|
||||||
memcpy(buf->data() + 84, &mat->m_smoothFactor, 4);
|
memcpy(buf->data() + 84, &mat->m_smoothFactor, 4);
|
||||||
|
|
||||||
// Rect count (offset 88)
|
|
||||||
memcpy(buf->data() + 88, &mat->m_rectCount, 4);
|
memcpy(buf->data() + 88, &mat->m_rectCount, 4);
|
||||||
|
|
||||||
// My index (offset 92). -2 skips the ownership test in the shader.
|
|
||||||
memcpy(buf->data() + 92, &mat->m_myIndex, 4);
|
memcpy(buf->data() + 92, &mat->m_myIndex, 4);
|
||||||
|
|
||||||
// Color as vec4 (offset 96, 16 bytes)
|
|
||||||
const float color[4] = {
|
const float color[4] = {
|
||||||
mat->m_color.redF(),
|
mat->m_color.redF(),
|
||||||
mat->m_color.greenF(),
|
mat->m_color.greenF(),
|
||||||
@@ -68,26 +63,19 @@ bool BlobMaterialShader::updateUniformData(
|
|||||||
};
|
};
|
||||||
memcpy(buf->data() + 96, color, 16);
|
memcpy(buf->data() + 96, color, 16);
|
||||||
|
|
||||||
// Has inverted (offset 112)
|
|
||||||
memcpy(buf->data() + 112, &mat->m_hasInverted, 4);
|
memcpy(buf->data() + 112, &mat->m_hasInverted, 4);
|
||||||
|
|
||||||
// Inverted radius (offset 116)
|
|
||||||
memcpy(buf->data() + 116, &mat->m_invertedRadius, 4);
|
memcpy(buf->data() + 116, &mat->m_invertedRadius, 4);
|
||||||
|
|
||||||
// Padding at 120-127 (skip)
|
|
||||||
|
|
||||||
// Inverted outer (offset 128, 16 bytes)
|
|
||||||
memcpy(buf->data() + 128, mat->m_invertedOuter, 16);
|
memcpy(buf->data() + 128, mat->m_invertedOuter, 16);
|
||||||
|
|
||||||
// Inverted inner (offset 144, 16 bytes)
|
|
||||||
memcpy(buf->data() + 144, mat->m_invertedInner, 16);
|
memcpy(buf->data() + 144, mat->m_invertedInner, 16);
|
||||||
|
|
||||||
// Rect data (offset 160, each rect = 5 vec4s = 80 bytes)
|
|
||||||
const int count = qMin(mat->m_rectCount, 16);
|
const int count = qMin(mat->m_rectCount, 16);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
const auto& r = mat->m_rects[i];
|
const auto& r = mat->m_rects[i];
|
||||||
const int base = 160 + i * 80;
|
const int base = 160 + i * 80;
|
||||||
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
|
|
||||||
float maskAsFloat = NAN;
|
float maskAsFloat = NAN;
|
||||||
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};
|
||||||
@@ -100,12 +88,9 @@ bool BlobMaterialShader::updateUniformData(
|
|||||||
memcpy(buf->data() + base + 64, r.radius, 16);
|
memcpy(buf->data() + base + 64, r.radius, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow: after rectData, which ends at 1440. shadowColor is 16-byte aligned,
|
|
||||||
// so it lands at 1456.
|
|
||||||
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);
|
memcpy(buf->data() + 1472, mat->m_invertedOuterRadii, 16);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -8,14 +8,9 @@ struct BlobRectData {
|
|||||||
float cx = 0, cy = 0, hw = 0, hh = 0;
|
float cx = 0, cy = 0, hw = 0, hh = 0;
|
||||||
float offsetX = 0, offsetY = 0;
|
float offsetX = 0, offsetY = 0;
|
||||||
float minEig = 1.0f;
|
float minEig = 1.0f;
|
||||||
// Inverse of 2x2 deformation matrix, column-major for GLSL
|
|
||||||
float invDeform[4] = {1, 0, 0, 1};
|
float invDeform[4] = {1, 0, 0, 1};
|
||||||
// Screen-space AABB half-extents of the deformed rect
|
|
||||||
float screenHalfX = 0, screenHalfY = 0;
|
float screenHalfX = 0, screenHalfY = 0;
|
||||||
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
|
|
||||||
float radius[4] = {0, 0, 0, 0};
|
float radius[4] = {0, 0, 0, 0};
|
||||||
// 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.
|
|
||||||
int excludeMask = 0;
|
int excludeMask = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,19 +27,14 @@ class BlobMaterial : public QSGMaterial {
|
|||||||
float m_paddedH = 0;
|
float m_paddedH = 0;
|
||||||
float m_smoothFactor = 32.0f;
|
float m_smoothFactor = 32.0f;
|
||||||
int m_rectCount = 0;
|
int m_rectCount = 0;
|
||||||
// -2 means no owner test: the single group draw writes every pixel it covers.
|
|
||||||
int m_myIndex = -2;
|
int m_myIndex = -2;
|
||||||
QColor m_color{0x44, 0x88, 0xff};
|
QColor m_color{0x44, 0x88, 0xff};
|
||||||
int m_hasInverted = 0;
|
int m_hasInverted = 0;
|
||||||
float m_invertedRadius = 0;
|
float m_invertedRadius = 0;
|
||||||
float m_invertedOuter[4] = {};
|
float m_invertedOuter[4] = {};
|
||||||
float m_invertedInner[4] = {};
|
float m_invertedInner[4] = {};
|
||||||
// Phase 2: shadow around the material edges. Matches the uniform block, which
|
|
||||||
// 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] = {};
|
float m_invertedOuterRadii[4] = {};
|
||||||
BlobRectData m_rects[16] = {};
|
BlobRectData m_rects[16] = {};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,19 +7,94 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr float k_retireMs = 120.0f;
|
constexpr float k_retireMs = 120.0f;
|
||||||
}
|
constexpr qint64 k_minEvalMs = 10;
|
||||||
|
constexpr float k_staleSampleSec = 0.1f;
|
||||||
|
constexpr float k_staleMovePx = 2.0f;
|
||||||
|
constexpr float k_moveSpeed = 5.0f;
|
||||||
|
constexpr float k_settleDelta = 0.004f;
|
||||||
|
constexpr float k_settleVel = 0.05f;
|
||||||
|
constexpr float k_identityEps = 0.0005f;
|
||||||
|
constexpr float k_stillPx = 0.5f;
|
||||||
|
constexpr int k_settleStillSamples = 3;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {}
|
BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {
|
||||||
|
m_stillSamples = k_settleStillSamples;
|
||||||
|
}
|
||||||
|
|
||||||
BlobRect::~BlobRect() {
|
BlobRect::~BlobRect() {
|
||||||
if (m_group) m_group->removeShape(this);
|
if (m_group) m_group->removeShape(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BlobRect::needsFrame() const {
|
||||||
|
return m_physicsActive || m_retiring ||
|
||||||
|
m_stillSamples < k_settleStillSamples ||
|
||||||
|
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||||
|
std::abs(m_dm11 - 1.0f) >
|
||||||
|
k_identityEps;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect::resetDeformMatrix() {
|
||||||
|
m_dm00 = 1.0f;
|
||||||
|
m_dm01 = 0.0f;
|
||||||
|
m_dm11 = 1.0f;
|
||||||
|
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
||||||
|
m_retiring = false;
|
||||||
|
m_physicsActive = false;
|
||||||
|
BlobShape::resetDeformMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect::requestPhysics() {
|
||||||
|
if (m_group) m_group->requestPhysics();
|
||||||
|
}
|
||||||
|
|
||||||
void BlobRect::updatePolish() {
|
void BlobRect::updatePolish() {
|
||||||
BlobShape::updatePolish();
|
BlobShape::updatePolish();
|
||||||
|
|
||||||
|
if (m_physicsActive || m_retiring) requestPhysics();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect::finishAtIdentity() {
|
||||||
|
m_dm00 = 1.0f;
|
||||||
|
m_dm01 = 0.0f;
|
||||||
|
m_dm11 = 1.0f;
|
||||||
|
m_deformMatrix = QMatrix4x4();
|
||||||
|
m_retiring = false;
|
||||||
|
m_physicsActive = false;
|
||||||
|
emit rawDeformMatrixChanged();
|
||||||
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect::beginRetire() {
|
||||||
|
if (m_retiring) return;
|
||||||
|
|
||||||
|
m_retire00 = m_dm00;
|
||||||
|
m_retire01 = m_dm01;
|
||||||
|
m_retire11 = m_dm11;
|
||||||
|
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
||||||
|
m_retireElapsed.start();
|
||||||
|
m_retiring = true;
|
||||||
|
requestPhysics();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect::trySettle(float speed) {
|
||||||
|
if (m_retiring) return;
|
||||||
if (!m_physicsActive) return;
|
if (!m_physicsActive) return;
|
||||||
|
|
||||||
|
const float totalDelta =
|
||||||
|
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
|
||||||
|
const float totalVel =
|
||||||
|
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
|
||||||
|
|
||||||
|
if (speed < k_moveSpeed && totalDelta < k_settleDelta &&
|
||||||
|
totalVel < k_settleVel && m_stillSamples >= k_settleStillSamples) {
|
||||||
|
beginRetire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect::updatePhysics() {
|
||||||
if (m_retiring) {
|
if (m_retiring) {
|
||||||
const float t = std::clamp(
|
const float t = std::clamp(
|
||||||
static_cast<float>(m_retireElapsed.elapsed()) / k_retireMs,
|
static_cast<float>(m_retireElapsed.elapsed()) / k_retireMs,
|
||||||
@@ -28,100 +103,80 @@ void BlobRect::updatePolish() {
|
|||||||
const float s = t * t * (3.0f - 2.0f * t);
|
const float s = t * t * (3.0f - 2.0f * t);
|
||||||
|
|
||||||
if (t >= 1.0f) {
|
if (t >= 1.0f) {
|
||||||
m_dm00 = 1.0f;
|
finishAtIdentity();
|
||||||
m_dm01 = 0.0f;
|
return;
|
||||||
m_dm11 = 1.0f;
|
|
||||||
m_deformMatrix = QMatrix4x4();
|
|
||||||
m_retiring = false;
|
|
||||||
m_physicsActive = false;
|
|
||||||
} else {
|
|
||||||
m_dm00 = m_retire00 + (1.0f - m_retire00) * s;
|
|
||||||
m_dm01 = m_retire01 - m_retire01 * s;
|
|
||||||
m_dm11 = m_retire11 + (1.0f - m_retire11) * s;
|
|
||||||
m_deformMatrix = QMatrix4x4(
|
|
||||||
m_dm00,
|
|
||||||
m_dm01,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
m_dm01,
|
|
||||||
m_dm11,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_dm00 = m_retire00 + (1.0f - m_retire00) * s;
|
||||||
|
m_dm01 = m_retire01 - m_retire01 * s;
|
||||||
|
m_dm11 = m_retire11 + (1.0f - m_retire11) * s;
|
||||||
|
m_deformMatrix = QMatrix4x4(
|
||||||
|
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||||
emit rawDeformMatrixChanged();
|
emit rawDeformMatrixChanged();
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
if (!m_physicsActive) return;
|
|
||||||
} else {
|
|
||||||
const float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
|
||||||
std::abs(m_dm11 - 1.0f);
|
|
||||||
const float totalVel =
|
|
||||||
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
|
|
||||||
|
|
||||||
if (totalDelta < 0.004f && totalVel < 0.05f) beginRetire();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QMetaObject::invokeMethod(
|
|
||||||
this,
|
|
||||||
[this]() {
|
|
||||||
if (m_physicsActive && m_group) m_group->markDirty();
|
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlobRect::beginRetire() {
|
|
||||||
m_retire00 = m_dm00;
|
|
||||||
m_retire01 = m_dm01;
|
|
||||||
m_retire11 = m_dm11;
|
|
||||||
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
|
||||||
m_retireElapsed.start();
|
|
||||||
m_retiring = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlobRect::updatePhysics() {
|
|
||||||
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
|
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
|
||||||
|
|
||||||
|
if (!m_physicsActive && !m_retiring) {
|
||||||
|
const float stuck = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||||
|
std::abs(m_dm11 - 1.0f);
|
||||||
|
if (stuck > k_identityEps) {
|
||||||
|
beginRetire();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_hasPrevPos) {
|
if (!m_hasPrevPos) {
|
||||||
m_prevScenePos = scenePos;
|
m_prevScenePos = scenePos;
|
||||||
m_elapsed.start();
|
m_elapsed.start();
|
||||||
m_hasPrevPos = true;
|
m_hasPrevPos = true;
|
||||||
|
m_stillSamples = k_settleStillSamples;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
|
const qint64 evalMs = m_elapsed.elapsed();
|
||||||
if (dt > 0.1f || dt < 0.001f) {
|
if (evalMs < k_minEvalMs) {
|
||||||
m_prevScenePos = scenePos;
|
|
||||||
if (m_physicsActive) checkAtRest(0.0f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_elapsed.restart();
|
||||||
|
|
||||||
const float velX =
|
const float dx = static_cast<float>(scenePos.x() - m_prevScenePos.x());
|
||||||
static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
|
const float dy = static_cast<float>(scenePos.y() - m_prevScenePos.y());
|
||||||
const float velY =
|
|
||||||
static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
|
|
||||||
m_prevScenePos = scenePos;
|
m_prevScenePos = scenePos;
|
||||||
|
|
||||||
|
if (std::abs(dx) + std::abs(dy) > k_stillPx)
|
||||||
|
m_stillSamples = 0;
|
||||||
|
else
|
||||||
|
m_stillSamples += 1;
|
||||||
|
|
||||||
|
const float dt = static_cast<float>(evalMs) / 1000.0f;
|
||||||
|
if (dt > k_staleSampleSec) {
|
||||||
|
if (std::abs(dx) + std::abs(dy) > k_staleMovePx) {
|
||||||
|
m_physicsActive = true;
|
||||||
|
requestPhysics();
|
||||||
|
} else {
|
||||||
|
trySettle(0.0f);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float velX = dx / dt;
|
||||||
|
const float velY = dy / dt;
|
||||||
|
|
||||||
const float speed = std::sqrt(velX * velX + velY * velY);
|
const float speed = std::sqrt(velX * velX + velY * velY);
|
||||||
|
|
||||||
if (m_retiring) {
|
if (m_retiring) {
|
||||||
if (speed > 5.0f)
|
if (speed > k_moveSpeed)
|
||||||
m_retiring = false;
|
m_retiring = false;
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_physicsActive) {
|
if (!m_physicsActive) {
|
||||||
if (speed < 5.0f) return;
|
if (speed < k_moveSpeed) return;
|
||||||
m_physicsActive = true;
|
m_physicsActive = true;
|
||||||
|
requestPhysics();
|
||||||
}
|
}
|
||||||
|
|
||||||
const float kStretchFactor = static_cast<float>(m_deformScale);
|
const float kStretchFactor = static_cast<float>(m_deformScale);
|
||||||
@@ -131,7 +186,7 @@ void BlobRect::updatePhysics() {
|
|||||||
float target01 = 0.0f;
|
float target01 = 0.0f;
|
||||||
float target11 = 1.0f;
|
float target11 = 1.0f;
|
||||||
|
|
||||||
if (speed > 5.0f) {
|
if (speed > k_moveSpeed) {
|
||||||
const float targetStretch =
|
const float targetStretch =
|
||||||
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
||||||
const float targetCompress = 1.0f / targetStretch;
|
const float targetCompress = 1.0f / targetStretch;
|
||||||
@@ -165,7 +220,7 @@ void BlobRect::updatePhysics() {
|
|||||||
emit rawDeformMatrixChanged();
|
emit rawDeformMatrixChanged();
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
checkAtRest(speed);
|
trySettle(speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::setTopLeftRadius(qreal r) {
|
void BlobRect::setTopLeftRadius(qreal r) {
|
||||||
@@ -340,16 +395,3 @@ void BlobRect::excludeCornersRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
|||||||
if (self->m_group) self->m_group->markDirty();
|
if (self->m_group) self->m_group->markDirty();
|
||||||
emit self->excludeCornersChanged();
|
emit self->excludeCornersChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::checkAtRest(float speed) {
|
|
||||||
if (m_retiring) return;
|
|
||||||
|
|
||||||
constexpr float kEpsilon = 0.002f;
|
|
||||||
const bool atRest =
|
|
||||||
std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
|
|
||||||
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
|
|
||||||
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon &&
|
|
||||||
speed < 5.0f;
|
|
||||||
|
|
||||||
if (atRest) beginRetire();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -105,15 +105,20 @@ class BlobRect : public BlobShape {
|
|||||||
protected:
|
protected:
|
||||||
void updatePolish() override;
|
void updatePolish() override;
|
||||||
void updatePhysics() override;
|
void updatePhysics() override;
|
||||||
|
bool needsFrame() const override;
|
||||||
|
void resetDeformMatrix() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkAtRest(float speed);
|
void trySettle(float speed);
|
||||||
void beginRetire();
|
void beginRetire();
|
||||||
|
void finishAtIdentity();
|
||||||
|
void requestPhysics();
|
||||||
|
|
||||||
QPointF m_prevScenePos;
|
QPointF m_prevScenePos;
|
||||||
QElapsedTimer m_elapsed;
|
QElapsedTimer m_elapsed;
|
||||||
bool m_physicsActive = false;
|
bool m_physicsActive = false;
|
||||||
bool m_hasPrevPos = false;
|
bool m_hasPrevPos = false;
|
||||||
|
int m_stillSamples = 0;
|
||||||
|
|
||||||
bool m_retiring = false;
|
bool m_retiring = false;
|
||||||
QElapsedTimer m_retireElapsed;
|
QElapsedTimer m_retireElapsed;
|
||||||
|
|||||||
@@ -6,14 +6,59 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr float k_retireMs = 120.0f;
|
constexpr float k_retireMs = 120.0f;
|
||||||
|
constexpr qint64 k_minEvalMs = 10;
|
||||||
|
constexpr float k_staleSampleSec = 0.1f;
|
||||||
|
constexpr float k_staleMovePx = 2.0f;
|
||||||
|
constexpr float k_moveSpeed = 5.0f;
|
||||||
|
constexpr float k_settleDelta = 0.004f;
|
||||||
|
constexpr float k_settleVel = 0.05f;
|
||||||
|
constexpr float k_identityEps = 0.0005f;
|
||||||
|
constexpr float k_stillPx = 0.5f;
|
||||||
|
constexpr int k_settleStillSamples = 3;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
BlobRect2::BlobRect2(QQuickItem* parent) : BlobShape2(parent) {}
|
BlobRect2::BlobRect2(QQuickItem* parent) : BlobShape2(parent) {
|
||||||
|
m_stillSamples = k_settleStillSamples;
|
||||||
|
}
|
||||||
|
|
||||||
BlobRect2::~BlobRect2() {
|
BlobRect2::~BlobRect2() {
|
||||||
if (m_group) m_group->removeShape(this);
|
if (m_group) m_group->removeShape(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BlobRect2::needsFrame() const {
|
||||||
|
return m_physicsActive || m_retiring ||
|
||||||
|
m_stillSamples < k_settleStillSamples ||
|
||||||
|
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||||
|
std::abs(m_dm11 - 1.0f) >
|
||||||
|
k_identityEps;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlobRect2::isMoving() const {
|
||||||
|
return m_atRestOverride || m_physicsActive || m_retiring ||
|
||||||
|
m_stillSamples < k_settleStillSamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect2::updateMoving() {
|
||||||
|
const bool moving = isMoving();
|
||||||
|
if (moving == m_movingEmitted) return;
|
||||||
|
m_movingEmitted = moving;
|
||||||
|
emit movingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect2::resetDeformMatrix() {
|
||||||
|
m_dm00 = 1.0f;
|
||||||
|
m_dm01 = 0.0f;
|
||||||
|
m_dm11 = 1.0f;
|
||||||
|
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
||||||
|
m_retiring = false;
|
||||||
|
m_physicsActive = false;
|
||||||
|
BlobShape2::resetDeformMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect2::requestPhysics() {
|
||||||
|
if (m_group) m_group->requestPhysics();
|
||||||
|
}
|
||||||
|
|
||||||
void BlobRect2::finishAtIdentity() {
|
void BlobRect2::finishAtIdentity() {
|
||||||
m_dm00 = 1.0f;
|
m_dm00 = 1.0f;
|
||||||
m_dm01 = 0.0f;
|
m_dm01 = 0.0f;
|
||||||
@@ -23,11 +68,12 @@ void BlobRect2::finishAtIdentity() {
|
|||||||
m_physicsActive = false;
|
m_physicsActive = false;
|
||||||
emit rawDeformMatrixChanged();
|
emit rawDeformMatrixChanged();
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
|
if (m_group) m_group->markDirty();
|
||||||
|
updateMoving();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect2::beginRetire() {
|
void BlobRect2::beginRetire() {
|
||||||
// The settle check would otherwise re-enter this every frame and restart the
|
|
||||||
// curve timer, so the curve never reaches t = 1.
|
|
||||||
if (m_retiring) return;
|
if (m_retiring) return;
|
||||||
|
|
||||||
m_retire00 = m_dm00;
|
m_retire00 = m_dm00;
|
||||||
@@ -36,10 +82,26 @@ void BlobRect2::beginRetire() {
|
|||||||
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
m_dmVel00 = m_dmVel01 = m_dmVel11 = 0.0f;
|
||||||
m_retireElapsed.start();
|
m_retireElapsed.start();
|
||||||
m_retiring = true;
|
m_retiring = true;
|
||||||
|
requestPhysics();
|
||||||
|
updateMoving();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlobRect2::trySettle(float speed) {
|
||||||
|
if (m_retiring) return;
|
||||||
|
if (!m_physicsActive) return;
|
||||||
|
|
||||||
|
const float totalDelta =
|
||||||
|
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
|
||||||
|
const float totalVel =
|
||||||
|
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
|
||||||
|
|
||||||
|
if (speed < k_moveSpeed && totalDelta < k_settleDelta &&
|
||||||
|
totalVel < k_settleVel && m_stillSamples >= k_settleStillSamples) {
|
||||||
|
beginRetire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect2::updatePhysics() {
|
void BlobRect2::updatePhysics() {
|
||||||
// Shapes are data only, so the group drives physics here.
|
|
||||||
if (m_retiring) {
|
if (m_retiring) {
|
||||||
const float t = std::clamp(
|
const float t = std::clamp(
|
||||||
static_cast<float>(m_retireElapsed.elapsed()) / k_retireMs,
|
static_cast<float>(m_retireElapsed.elapsed()) / k_retireMs,
|
||||||
@@ -63,12 +125,16 @@ void BlobRect2::updatePhysics() {
|
|||||||
if (!m_physicsActive) return;
|
if (!m_physicsActive) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No "not active" guard here: an inactive shape still has to be evaluated so it
|
if (!m_physicsActive && !m_retiring) {
|
||||||
// can see the panel start moving and become active. The frame loop is only kept
|
const float stuck = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||||
// alive while active, and it is requested before anything can return early —
|
std::abs(m_dm11 - 1.0f);
|
||||||
// requesting it at the end lets a shape stop being evaluated with the matrix
|
if (stuck > k_identityEps) {
|
||||||
// still off identity, which is what left the panel contents blurry.
|
beginRetire();
|
||||||
if (m_physicsActive && m_group) m_group->markDirty();
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_physicsActive) requestPhysics();
|
||||||
|
|
||||||
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
|
const QPointF scenePos = mapToScene(QPointF(width() / 2.0, height() / 2.0));
|
||||||
|
|
||||||
@@ -76,34 +142,54 @@ void BlobRect2::updatePhysics() {
|
|||||||
m_prevScenePos = scenePos;
|
m_prevScenePos = scenePos;
|
||||||
m_elapsed.start();
|
m_elapsed.start();
|
||||||
m_hasPrevPos = true;
|
m_hasPrevPos = true;
|
||||||
|
m_stillSamples = k_settleStillSamples;
|
||||||
|
updateMoving();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
|
const qint64 evalMs = m_elapsed.elapsed();
|
||||||
if (dt > 0.1f || dt < 0.001f) {
|
if (evalMs < k_minEvalMs) {
|
||||||
m_prevScenePos = scenePos;
|
|
||||||
if (m_physicsActive) checkAtRest(0.0f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_elapsed.restart();
|
||||||
|
|
||||||
const float velX =
|
const float dx = static_cast<float>(scenePos.x() - m_prevScenePos.x());
|
||||||
static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
|
const float dy = static_cast<float>(scenePos.y() - m_prevScenePos.y());
|
||||||
const float velY =
|
|
||||||
static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
|
|
||||||
m_prevScenePos = scenePos;
|
m_prevScenePos = scenePos;
|
||||||
|
|
||||||
|
if (std::abs(dx) + std::abs(dy) > k_stillPx)
|
||||||
|
m_stillSamples = 0;
|
||||||
|
else
|
||||||
|
m_stillSamples += 1;
|
||||||
|
|
||||||
|
const float dt = static_cast<float>(evalMs) / 1000.0f;
|
||||||
|
if (dt > k_staleSampleSec) {
|
||||||
|
if (std::abs(dx) + std::abs(dy) > k_staleMovePx) {
|
||||||
|
m_physicsActive = true;
|
||||||
|
requestPhysics();
|
||||||
|
updateMoving();
|
||||||
|
} else {
|
||||||
|
trySettle(0.0f);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float velX = dx / dt;
|
||||||
|
const float velY = dy / dt;
|
||||||
|
|
||||||
const float speed = std::sqrt(velX * velX + velY * velY);
|
const float speed = std::sqrt(velX * velX + velY * velY);
|
||||||
|
|
||||||
if (m_retiring) {
|
if (m_retiring) {
|
||||||
if (speed > 5.0f)
|
if (speed > k_moveSpeed)
|
||||||
m_retiring = false;
|
m_retiring = false;
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_physicsActive) {
|
if (!m_physicsActive) {
|
||||||
if (speed < 5.0f) return;
|
if (speed < k_moveSpeed) return;
|
||||||
m_physicsActive = true;
|
m_physicsActive = true;
|
||||||
|
requestPhysics();
|
||||||
}
|
}
|
||||||
|
|
||||||
const float kStretchFactor = static_cast<float>(m_deformScale);
|
const float kStretchFactor = static_cast<float>(m_deformScale);
|
||||||
@@ -113,7 +199,7 @@ void BlobRect2::updatePhysics() {
|
|||||||
float target01 = 0.0f;
|
float target01 = 0.0f;
|
||||||
float target11 = 1.0f;
|
float target11 = 1.0f;
|
||||||
|
|
||||||
if (speed > 5.0f) {
|
if (speed > k_moveSpeed) {
|
||||||
const float targetStretch =
|
const float targetStretch =
|
||||||
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
||||||
const float targetCompress = 1.0f / targetStretch;
|
const float targetCompress = 1.0f / targetStretch;
|
||||||
@@ -147,22 +233,8 @@ void BlobRect2::updatePhysics() {
|
|||||||
emit rawDeformMatrixChanged();
|
emit rawDeformMatrixChanged();
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
// Start the retire as soon as the matrix is close to identity, as in v1. Waiting
|
trySettle(speed);
|
||||||
// for every term to fall below kEpsilon keeps the matrix just off identity for
|
updateMoving();
|
||||||
// longer, and the panel content is transformed by it, which reads as blur on
|
|
||||||
// text.
|
|
||||||
const float totalDelta =
|
|
||||||
std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
|
|
||||||
const float totalVel =
|
|
||||||
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
|
|
||||||
// The residual is already inside the rest epsilon, so the eased curve would only
|
|
||||||
// be aborted or restarted every frame by the panel creeping. Snap instead.
|
|
||||||
if (totalDelta < 0.004f && totalVel < 0.05f) {
|
|
||||||
finishAtIdentity();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAtRest(speed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect2::setTopLeftRadius(qreal r) {
|
void BlobRect2::setTopLeftRadius(qreal r) {
|
||||||
@@ -338,16 +410,3 @@ void BlobRect2::excludeCornersRemoveLast(QQmlListProperty<BlobRect2>* prop) {
|
|||||||
if (self->m_group) self->m_group->markDirty();
|
if (self->m_group) self->m_group->markDirty();
|
||||||
emit self->excludeCornersChanged();
|
emit self->excludeCornersChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect2::checkAtRest(float speed) {
|
|
||||||
if (m_retiring) return;
|
|
||||||
|
|
||||||
constexpr float kEpsilon = 0.002f;
|
|
||||||
const bool atRest =
|
|
||||||
std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
|
|
||||||
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
|
|
||||||
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon &&
|
|
||||||
speed < 5.0f;
|
|
||||||
|
|
||||||
if (atRest) beginRetire();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class BlobRect2 : public BlobShape2 {
|
|||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool atRestOverride READ atRestOverride WRITE setAtRestOverride NOTIFY
|
bool atRestOverride READ atRestOverride WRITE setAtRestOverride NOTIFY
|
||||||
atRestOverrideChanged)
|
atRestOverrideChanged)
|
||||||
|
Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
|
qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY
|
||||||
topLeftRadiusChanged)
|
topLeftRadiusChanged)
|
||||||
@@ -78,18 +79,13 @@ class BlobRect2 : public BlobShape2 {
|
|||||||
if (m_atRestOverride == o) return;
|
if (m_atRestOverride == o) return;
|
||||||
m_atRestOverride = o;
|
m_atRestOverride = o;
|
||||||
emit atRestOverrideChanged();
|
emit atRestOverrideChanged();
|
||||||
|
updateMoving();
|
||||||
if (m_group) m_group->markDirty();
|
if (m_group) m_group->markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
bool isMoving() const override;
|
||||||
// retiring still counts, because the retire curve is still moving the surface.
|
|
||||||
// atRestOverride keeps the shape in the group's data even at rest; when it is false
|
|
||||||
// the actual spring state decides, so it is not an override of the state itself.
|
|
||||||
bool isMoving() const override {
|
|
||||||
return m_atRestOverride || 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; }
|
||||||
@@ -117,6 +113,7 @@ class BlobRect2 : public BlobShape2 {
|
|||||||
void excludeChanged();
|
void excludeChanged();
|
||||||
void excludeCornersChanged();
|
void excludeCornersChanged();
|
||||||
void atRestOverrideChanged();
|
void atRestOverrideChanged();
|
||||||
|
void movingChanged();
|
||||||
void topLeftRadiusChanged();
|
void topLeftRadiusChanged();
|
||||||
void topRightRadiusChanged();
|
void topRightRadiusChanged();
|
||||||
void bottomLeftRadiusChanged();
|
void bottomLeftRadiusChanged();
|
||||||
@@ -124,20 +121,24 @@ class BlobRect2 : public BlobShape2 {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void updatePhysics() override;
|
void updatePhysics() override;
|
||||||
|
bool needsFrame() const override;
|
||||||
|
void resetDeformMatrix() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkAtRest(float speed);
|
void trySettle(float speed);
|
||||||
void beginRetire();
|
void beginRetire();
|
||||||
void finishAtIdentity();
|
void finishAtIdentity();
|
||||||
|
void requestPhysics();
|
||||||
|
void updateMoving();
|
||||||
|
|
||||||
// Physics state
|
|
||||||
QPointF m_prevScenePos;
|
QPointF m_prevScenePos;
|
||||||
QElapsedTimer m_elapsed;
|
QElapsedTimer m_elapsed;
|
||||||
bool m_physicsActive = false;
|
bool m_physicsActive = false;
|
||||||
bool m_hasPrevPos = false;
|
bool m_hasPrevPos = false;
|
||||||
|
|
||||||
// Set from QML. Keeps the shape in the group's data while it is at rest; when it is
|
int m_stillSamples = 0;
|
||||||
// false the spring state decides on its own.
|
bool m_movingEmitted = false;
|
||||||
|
|
||||||
bool m_atRestOverride = false;
|
bool m_atRestOverride = false;
|
||||||
|
|
||||||
bool m_retiring = false;
|
bool m_retiring = false;
|
||||||
@@ -146,13 +147,10 @@ class BlobRect2 : public BlobShape2 {
|
|||||||
float m_retire01 = 0.0f;
|
float m_retire01 = 0.0f;
|
||||||
float m_retire11 = 1.0f;
|
float m_retire11 = 1.0f;
|
||||||
|
|
||||||
// Symmetric 2x2 deformation matrix components (3 independent: m00, m01,
|
|
||||||
// m11) Rest state is identity: m00=1, m01=0, m11=1
|
|
||||||
float m_dm00 = 1.0f;
|
float m_dm00 = 1.0f;
|
||||||
float m_dm01 = 0.0f;
|
float m_dm01 = 0.0f;
|
||||||
float m_dm11 = 1.0f;
|
float m_dm11 = 1.0f;
|
||||||
|
|
||||||
// Spring velocities for each component
|
|
||||||
float m_dmVel00 = 0.0f;
|
float m_dmVel00 = 0.0f;
|
||||||
float m_dmVel01 = 0.0f;
|
float m_dmVel01 = 0.0f;
|
||||||
float m_dmVel11 = 0.0f;
|
float m_dmVel11 = 0.0f;
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int kMaxRects = 16;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
|
static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
|
||||||
const float dm00 = dm(0, 0), dm01 = dm(0, 1);
|
const float dm00 = dm(0, 0), dm01 = dm(0, 1);
|
||||||
const float dm10 = dm(1, 0), dm11 = dm(1, 1);
|
const float dm10 = dm(1, 0), dm11 = dm(1, 1);
|
||||||
@@ -45,6 +49,7 @@ BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) {
|
|||||||
void BlobShape::setGroup(BlobGroup* g) {
|
void BlobShape::setGroup(BlobGroup* g) {
|
||||||
if (m_group == g) return;
|
if (m_group == g) return;
|
||||||
if (m_group && isComponentComplete()) unregisterFromGroup();
|
if (m_group && isComponentComplete()) unregisterFromGroup();
|
||||||
|
if (m_group) resetDeformMatrix();
|
||||||
m_group = g;
|
m_group = g;
|
||||||
if (m_group && isComponentComplete()) registerWithGroup();
|
if (m_group && isComponentComplete()) registerWithGroup();
|
||||||
emit groupChanged();
|
emit groupChanged();
|
||||||
@@ -87,6 +92,8 @@ void BlobShape::geometryChange(
|
|||||||
std::abs(m_pendingDh) > syncThreshold) {
|
std::abs(m_pendingDh) > syncThreshold) {
|
||||||
m_pendingDx = 0;
|
m_pendingDx = 0;
|
||||||
m_pendingDy = 0;
|
m_pendingDy = 0;
|
||||||
|
m_pendingDw = 0;
|
||||||
|
m_pendingDh = 0;
|
||||||
m_group->markShapeDirty(this);
|
m_group->markShapeDirty(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,6 +112,12 @@ void BlobShape::updateCenteredDeformMatrix() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlobShape::resetDeformMatrix() {
|
||||||
|
m_deformMatrix = QMatrix4x4();
|
||||||
|
emit rawDeformMatrixChanged();
|
||||||
|
updateCenteredDeformMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
void BlobShape::cornerRadii(float out[4]) const {
|
void BlobShape::cornerRadii(float out[4]) const {
|
||||||
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
||||||
const auto r = std::min(static_cast<float>(m_radius), maxR);
|
const auto r = std::min(static_cast<float>(m_radius), maxR);
|
||||||
@@ -187,6 +200,12 @@ void BlobShape::updatePolish() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (include) {
|
if (include) {
|
||||||
|
if (other == this && m_cachedRects.size() >= kMaxRects) {
|
||||||
|
m_cachedRects.removeLast();
|
||||||
|
rectShapes.removeLast();
|
||||||
|
}
|
||||||
|
if (m_cachedRects.size() >= kMaxRects) continue;
|
||||||
|
|
||||||
if (other == this)
|
if (other == this)
|
||||||
m_cachedMyIndex = static_cast<int>(m_cachedRects.size());
|
m_cachedMyIndex = static_cast<int>(m_cachedRects.size());
|
||||||
|
|
||||||
@@ -240,8 +259,7 @@ 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(
|
memset(m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
||||||
m_cachedInvertedOuterRadii, 0, sizeof(m_cachedInvertedOuterRadii));
|
|
||||||
|
|
||||||
auto* inv = m_group->invertedRect();
|
auto* inv = m_group->invertedRect();
|
||||||
if (inv) {
|
if (inv) {
|
||||||
@@ -308,10 +326,11 @@ void BlobShape::updatePolish() {
|
|||||||
BlobShape* const si = rectShapes[i];
|
BlobShape* const si = rectShapes[i];
|
||||||
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
||||||
|
|
||||||
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
|
const float dcx = ri.cx + ri.offsetX, dcy = ri.cy + ri.offsetY;
|
||||||
const float cBrX = ri.cx + ri.hw, cBrY = ri.cy + ri.hh;
|
const float cTrX = dcx + ri.hw, cTrY = dcy - ri.hh;
|
||||||
const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh;
|
const float cBrX = dcx + ri.hw, cBrY = dcy + ri.hh;
|
||||||
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
const float cBlX = dcx - ri.hw, cBlY = dcy + ri.hh;
|
||||||
|
const float cTlX = dcx - ri.hw, cTlY = dcy - ri.hh;
|
||||||
|
|
||||||
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
|
for (qsizetype j = 0; cornerFill && j < rectCount; ++j) {
|
||||||
if (j == i) continue;
|
if (j == i) continue;
|
||||||
@@ -319,10 +338,12 @@ void BlobShape::updatePolish() {
|
|||||||
BlobShape* const sj = rectShapes[j];
|
BlobShape* const sj = rectShapes[j];
|
||||||
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) continue;
|
if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) continue;
|
||||||
const auto& rj = m_cachedRects[j];
|
const auto& rj = m_cachedRects[j];
|
||||||
const float sdTr = cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh);
|
const float jcx = rj.cx + rj.offsetX;
|
||||||
const float sdBr = cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh);
|
const float jcy = rj.cy + rj.offsetY;
|
||||||
const float sdBl = cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh);
|
const float sdTr = cpuSdBox(cTrX, cTrY, jcx, jcy, rj.hw, rj.hh);
|
||||||
const float sdTl = cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh);
|
const float sdBr = cpuSdBox(cBrX, cBrY, jcx, jcy, rj.hw, rj.hh);
|
||||||
|
const float sdBl = cpuSdBox(cBlX, cBlY, jcx, jcy, rj.hw, rj.hh);
|
||||||
|
const float sdTl = cpuSdBox(cTlX, cTlY, jcx, jcy, rj.hw, rj.hh);
|
||||||
fTr = std::min(fTr, cornerFillFactor(sdTr, smoothFactor));
|
fTr = std::min(fTr, cornerFillFactor(sdTr, smoothFactor));
|
||||||
fBr = std::min(fBr, cornerFillFactor(sdBr, smoothFactor));
|
fBr = std::min(fBr, cornerFillFactor(sdBr, smoothFactor));
|
||||||
fBl = std::min(fBl, cornerFillFactor(sdBl, smoothFactor));
|
fBl = std::min(fBl, cornerFillFactor(sdBl, smoothFactor));
|
||||||
@@ -360,10 +381,14 @@ void BlobShape::updatePolish() {
|
|||||||
-cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
|
-cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
|
ri.radius[0] = ri.radius[0] > 0.0f ? std::max(ri.radius[0] * fTr, minR)
|
||||||
ri.radius[1] = std::max(ri.radius[1] * fBr, minR);
|
: 0.0f;
|
||||||
ri.radius[2] = std::max(ri.radius[2] * fBl, minR);
|
ri.radius[1] = ri.radius[1] > 0.0f ? std::max(ri.radius[1] * fBr, minR)
|
||||||
ri.radius[3] = std::max(ri.radius[3] * fTl, minR);
|
: 0.0f;
|
||||||
|
ri.radius[2] = ri.radius[2] > 0.0f ? std::max(ri.radius[2] * fBl, minR)
|
||||||
|
: 0.0f;
|
||||||
|
ri.radius[3] = ri.radius[3] > 0.0f ? std::max(ri.radius[3] * fTl, minR)
|
||||||
|
: 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +453,7 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
|||||||
sizeof(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(kMaxRects)));
|
||||||
material->m_rectCount = count;
|
material->m_rectCount = count;
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < count; ++i)
|
||||||
material->m_rects[i] = m_cachedRects[i];
|
material->m_rects[i] = m_cachedRects[i];
|
||||||
|
|||||||
@@ -61,9 +61,6 @@ 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 {
|
virtual void outerCornerRadii(float out[4]) const {
|
||||||
out[0] = 0;
|
out[0] = 0;
|
||||||
out[1] = 0;
|
out[1] = 0;
|
||||||
@@ -73,16 +70,19 @@ class BlobShape : public QQuickItem {
|
|||||||
|
|
||||||
virtual void updatePhysics() {}
|
virtual void updatePhysics() {}
|
||||||
|
|
||||||
|
virtual bool needsFrame() const { return false; }
|
||||||
|
|
||||||
virtual void registerWithGroup();
|
virtual void registerWithGroup();
|
||||||
virtual void unregisterFromGroup();
|
virtual void unregisterFromGroup();
|
||||||
void updateCenteredDeformMatrix();
|
void updateCenteredDeformMatrix();
|
||||||
|
|
||||||
|
virtual void resetDeformMatrix();
|
||||||
|
|
||||||
BlobGroup* m_group = nullptr;
|
BlobGroup* m_group = nullptr;
|
||||||
qreal m_radius = 0;
|
qreal m_radius = 0;
|
||||||
QMatrix4x4 m_deformMatrix; // identity by default
|
QMatrix4x4 m_deformMatrix; // identity by default
|
||||||
QMatrix4x4 m_centeredDeformMatrix;
|
QMatrix4x4 m_centeredDeformMatrix;
|
||||||
|
|
||||||
// Cached data from updatePolish
|
|
||||||
float m_cachedPaddedX = 0;
|
float m_cachedPaddedX = 0;
|
||||||
float m_cachedPaddedY = 0;
|
float m_cachedPaddedY = 0;
|
||||||
float m_cachedPaddedW = 0;
|
float m_cachedPaddedW = 0;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ BlobShape2::BlobShape2(QQuickItem* parent) : QQuickItem(parent) {}
|
|||||||
void BlobShape2::setGroup(BlobGroup2* g) {
|
void BlobShape2::setGroup(BlobGroup2* g) {
|
||||||
if (m_group == g) return;
|
if (m_group == g) return;
|
||||||
if (m_group && isComponentComplete()) unregisterFromGroup();
|
if (m_group && isComponentComplete()) unregisterFromGroup();
|
||||||
|
if (m_group) resetDeformMatrix();
|
||||||
m_group = g;
|
m_group = g;
|
||||||
if (m_group && isComponentComplete()) registerWithGroup();
|
if (m_group && isComponentComplete()) registerWithGroup();
|
||||||
emit groupChanged();
|
emit groupChanged();
|
||||||
@@ -32,7 +33,6 @@ void BlobShape2::geometryChange(
|
|||||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
// One draw covers the whole group, so any shape change is a group change.
|
|
||||||
if (m_group) m_group->markDirty();
|
if (m_group) m_group->markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +49,12 @@ void BlobShape2::updateCenteredDeformMatrix() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlobShape2::resetDeformMatrix() {
|
||||||
|
m_deformMatrix = QMatrix4x4();
|
||||||
|
emit rawDeformMatrixChanged();
|
||||||
|
updateCenteredDeformMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
void BlobShape2::cornerRadii(float out[4]) const {
|
void BlobShape2::cornerRadii(float out[4]) const {
|
||||||
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
||||||
const auto r = std::min(static_cast<float>(m_radius), maxR);
|
const auto r = std::min(static_cast<float>(m_radius), maxR);
|
||||||
|
|||||||
@@ -7,12 +7,6 @@
|
|||||||
|
|
||||||
class BlobGroup2;
|
class BlobGroup2;
|
||||||
|
|
||||||
// Data-only shape.
|
|
||||||
//
|
|
||||||
// The group draws a single node for the whole group, so a shape here contributes
|
|
||||||
// only what the shader needs: position, radii, exclusion, physics. A shape does
|
|
||||||
// not create a render node, so no pixel can be evaluated by one shape and missed
|
|
||||||
// by another.
|
|
||||||
class BlobShape2 : public QQuickItem {
|
class BlobShape2 : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(BlobGroup2* group READ group WRITE setGroup NOTIFY groupChanged)
|
Q_PROPERTY(BlobGroup2* group READ group WRITE setGroup NOTIFY groupChanged)
|
||||||
@@ -56,8 +50,6 @@ class BlobShape2 : public QQuickItem {
|
|||||||
|
|
||||||
virtual bool isExcluded(const BlobShape2*) const { return false; }
|
virtual bool isExcluded(const BlobShape2*) const { return false; }
|
||||||
|
|
||||||
// Whether this shape is part of the group's data. Only a pushing shape is, so a shape
|
|
||||||
// at rest has no part in the draw. BlobRect2 also honours the QML atRestOverride.
|
|
||||||
virtual bool isMoving() const { return false; }
|
virtual bool isMoving() const { return false; }
|
||||||
|
|
||||||
virtual bool isCornerExcluded(const BlobShape2* /*other*/) const {
|
virtual bool isCornerExcluded(const BlobShape2* /*other*/) const {
|
||||||
@@ -66,13 +58,23 @@ class BlobShape2 : public QQuickItem {
|
|||||||
|
|
||||||
virtual void cornerRadii(float out[4]) const;
|
virtual void cornerRadii(float out[4]) const;
|
||||||
|
|
||||||
// Called by the group when it rebuilds the shared data, once per frame.
|
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 bool needsFrame() const { return false; }
|
||||||
|
|
||||||
virtual void registerWithGroup();
|
virtual void registerWithGroup();
|
||||||
virtual void unregisterFromGroup();
|
virtual void unregisterFromGroup();
|
||||||
void updateCenteredDeformMatrix();
|
void updateCenteredDeformMatrix();
|
||||||
|
|
||||||
|
virtual void resetDeformMatrix();
|
||||||
|
|
||||||
BlobGroup2* m_group = nullptr;
|
BlobGroup2* m_group = nullptr;
|
||||||
qreal m_radius = 0;
|
qreal m_radius = 0;
|
||||||
QMatrix4x4 m_deformMatrix; // identity by default
|
QMatrix4x4 m_deformMatrix; // identity by default
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ float sdRoundedBox(vec2 p, vec2 center, vec2 halfSize, float radius) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float sdRoundedBox4(vec2 p, vec2 center, vec2 halfSize, vec4 r) {
|
float sdRoundedBox4(vec2 p, vec2 center, vec2 halfSize, vec4 r) {
|
||||||
// r = (topRight, bottomRight, bottomLeft, topLeft)
|
|
||||||
p -= center;
|
p -= center;
|
||||||
r.xy = (p.x > 0.0) ? r.xy : r.wz;
|
r.xy = (p.x > 0.0) ? r.xy : r.wz;
|
||||||
r.x = (p.y > 0.0) ? r.y : r.x;
|
r.x = (p.y > 0.0) ? r.y : r.x;
|
||||||
@@ -44,7 +43,6 @@ float sdBox(vec2 p, vec2 center, vec2 halfSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float smin(float a, float b, float k) {
|
float smin(float a, float b, float k) {
|
||||||
// Cubic smooth min (C2 continuous — no curvature kinks at blend boundary)
|
|
||||||
float h = max(k - abs(a - b), 0.0) / k;
|
float h = max(k - abs(a - b), 0.0) / k;
|
||||||
return min(a, b) - h * h * h * k * (1.0 / 6.0);
|
return min(a, b) - h * h * h * k * (1.0 / 6.0);
|
||||||
}
|
}
|
||||||
@@ -55,9 +53,6 @@ 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).
|
|
||||||
// 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);
|
||||||
@@ -67,10 +62,6 @@ 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);
|
||||||
|
|
||||||
// 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:
|
|
||||||
// myIndex stays -2, so the ownership test is skipped and this pass writes every
|
|
||||||
// pixel the quad covers. Excluded pairs still need the pairwise pass below.
|
|
||||||
float dArr[16];
|
float dArr[16];
|
||||||
int owner = -2;
|
int owner = -2;
|
||||||
float minDist = 1e10;
|
float minDist = 1e10;
|
||||||
@@ -84,12 +75,8 @@ void main() {
|
|||||||
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)
|
||||||
|
|
||||||
// Offset center for asymmetric deformation
|
|
||||||
vec2 center = rect.xy + props.yz;
|
vec2 center = rect.xy + props.yz;
|
||||||
|
|
||||||
// AABB early-out: skip rects far from this pixel. The reach has to include the
|
|
||||||
// shadow, otherwise a rect that is only inside the shadow range would be
|
|
||||||
// skipped and its distance could not be used.
|
|
||||||
vec2 extent = sh.xy + vec2(smoothFactor * 1.5 + shadowRadius);
|
vec2 extent = sh.xy + vec2(smoothFactor * 1.5 + shadowRadius);
|
||||||
if (abs(pixel.x - center.x) > extent.x ||
|
if (abs(pixel.x - center.x) > extent.x ||
|
||||||
abs(pixel.y - center.y) > extent.y) {
|
abs(pixel.y - center.y) > extent.y) {
|
||||||
@@ -97,19 +84,13 @@ void main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply pre-computed inverse deformation to the evaluation point
|
|
||||||
mat2 invDeform = mat2(invDm.xy, invDm.zw);
|
mat2 invDeform = mat2(invDm.xy, invDm.zw);
|
||||||
vec2 transformedPixel = center + invDeform * (pixel - center);
|
vec2 transformedPixel = center + invDeform * (pixel - center);
|
||||||
|
|
||||||
// Use pre-computed effective per-corner radii
|
|
||||||
float d = sdRoundedBox4(transformedPixel, center, rect.zw, radii);
|
float d = sdRoundedBox4(transformedPixel, center, rect.zw, radii);
|
||||||
|
|
||||||
// Use pre-computed minimum eigenvalue for SDF correction
|
|
||||||
d *= max(props.w, 0.01);
|
d *= max(props.w, 0.01);
|
||||||
|
|
||||||
// Scale SDF on the axis facing a nearby border to narrow the smin blend
|
|
||||||
// zone in that direction only, without reducing k (which would cause sharp
|
|
||||||
// edges).
|
|
||||||
if (hasInverted != 0) {
|
if (hasInverted != 0) {
|
||||||
vec2 screenHalf = sh.xy;
|
vec2 screenHalf = sh.xy;
|
||||||
|
|
||||||
@@ -122,26 +103,21 @@ void main() {
|
|||||||
float distX1 =
|
float distX1 =
|
||||||
(invertedInner.x + invertedInner.z) - (center.x - screenHalf.x);
|
(invertedInner.x + invertedInner.z) - (center.x - screenHalf.x);
|
||||||
|
|
||||||
// 0 = far from border, 1 = at border (max compression)
|
|
||||||
float yProx = 1.0 - min(smoothstep(0.0, smoothFactor, distY0),
|
float yProx = 1.0 - min(smoothstep(0.0, smoothFactor, distY0),
|
||||||
smoothstep(0.0, smoothFactor, distY1));
|
smoothstep(0.0, smoothFactor, distY1));
|
||||||
float xProx = 1.0 - min(smoothstep(0.0, smoothFactor, distX0),
|
float xProx = 1.0 - min(smoothstep(0.0, smoothFactor, distX0),
|
||||||
smoothstep(0.0, smoothFactor, distX1));
|
smoothstep(0.0, smoothFactor, distX1));
|
||||||
|
|
||||||
// Smooth axis weights: gradient-based at corners, face-based inside.
|
|
||||||
vec2 q = abs(pixel - center) - screenHalf;
|
vec2 q = abs(pixel - center) - screenHalf;
|
||||||
vec2 qp = max(q, vec2(0.0));
|
vec2 qp = max(q, vec2(0.0));
|
||||||
float cornerLen = length(qp);
|
float cornerLen = length(qp);
|
||||||
|
|
||||||
// Gradient direction in corner region (smooth 90-degree rotation)
|
|
||||||
float gradX = qp.x / max(cornerLen, 0.001);
|
float gradX = qp.x / max(cornerLen, 0.001);
|
||||||
float gradY = qp.y / max(cornerLen, 0.001);
|
float gradY = qp.y / max(cornerLen, 0.001);
|
||||||
|
|
||||||
// Smooth face weights for inside/edge (no hard branch)
|
|
||||||
float faceY = smoothstep(-4.0, 4.0, q.y - q.x);
|
float faceY = smoothstep(-4.0, 4.0, q.y - q.x);
|
||||||
float faceX = 1.0 - faceY;
|
float faceX = 1.0 - faceY;
|
||||||
|
|
||||||
// Blend: gradient in corner region, face-based inside
|
|
||||||
float t = smoothstep(0.0, 2.0, cornerLen);
|
float t = smoothstep(0.0, 2.0, cornerLen);
|
||||||
float xWeight = mix(faceX, gradX, t);
|
float xWeight = mix(faceX, gradX, t);
|
||||||
float yWeight = mix(faceY, gradY, t);
|
float yWeight = mix(faceY, gradY, t);
|
||||||
@@ -158,15 +134,11 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: hard-min baseline over all rects.
|
|
||||||
float mergedSdf = 1e10;
|
float mergedSdf = 1e10;
|
||||||
for (int i = 0; i < rectCount; i++) {
|
for (int i = 0; i < rectCount; i++) {
|
||||||
mergedSdf = min(mergedSdf, dArr[i]);
|
mergedSdf = min(mergedSdf, dArr[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3: pair-wise smin contributions, skipping excluded pairs. Pair smin
|
|
||||||
// <= min, so taking the min over all non-excluded pair smins gives the
|
|
||||||
// smoothly-merged SDF.
|
|
||||||
for (int i = 0; i < rectCount; i++) {
|
for (int i = 0; i < rectCount; i++) {
|
||||||
if (dArr[i] >= 1e9)
|
if (dArr[i] >= 1e9)
|
||||||
continue;
|
continue;
|
||||||
@@ -176,7 +148,6 @@ void main() {
|
|||||||
continue;
|
continue;
|
||||||
if ((excludeMask & (1 << j)) != 0)
|
if ((excludeMask & (1 << j)) != 0)
|
||||||
continue;
|
continue;
|
||||||
// smin only deviates from min within smoothFactor
|
|
||||||
if (abs(dArr[i] - dArr[j]) >= smoothFactor)
|
if (abs(dArr[i] - dArr[j]) >= smoothFactor)
|
||||||
continue;
|
continue;
|
||||||
mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor));
|
mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor));
|
||||||
@@ -184,16 +155,12 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasInverted != 0) {
|
if (hasInverted != 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 =
|
float dOuter =
|
||||||
sdRoundedBox4(pixel, invertedOuter.xy, invertedOuter.zw,
|
sdRoundedBox4(pixel, invertedOuter.xy, invertedOuter.zw,
|
||||||
invertedOuterRadii) - 1.0;
|
invertedOuterRadii) - 1.0;
|
||||||
float dInner =
|
float dInner =
|
||||||
sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius);
|
sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius);
|
||||||
|
|
||||||
// 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;
|
||||||
float innerLeft = invertedInner.x - invertedInner.z;
|
float innerLeft = invertedInner.x - invertedInner.z;
|
||||||
@@ -203,62 +170,51 @@ void main() {
|
|||||||
float outerLeft = invertedOuter.x - invertedOuter.z;
|
float outerLeft = invertedOuter.x - invertedOuter.z;
|
||||||
float outerRight = invertedOuter.x + invertedOuter.z;
|
float outerRight = invertedOuter.x + invertedOuter.z;
|
||||||
|
|
||||||
|
float topZone =
|
||||||
|
1.0 - smoothstep(innerTop, innerTop + smoothFactor, pixel.y);
|
||||||
|
float botZone = smoothstep(innerBot - smoothFactor, innerBot, pixel.y);
|
||||||
|
float leftZone =
|
||||||
|
1.0 - smoothstep(innerLeft, innerLeft + smoothFactor, pixel.x);
|
||||||
|
float rightZone =
|
||||||
|
smoothstep(innerRight - smoothFactor, innerRight, pixel.x);
|
||||||
|
|
||||||
float sinkValue = 0.0;
|
float sinkValue = 0.0;
|
||||||
for (int i = 0; i < rectCount; i++) {
|
float preOff = smoothFactor * (1.0 / 6.0);
|
||||||
vec4 rect = rectData[i * 5];
|
float s = smoothFactor * 2.0;
|
||||||
vec4 sinkProps = rectData[i * 5 + 1];
|
|
||||||
vec2 sinkSh = rectData[i * 5 + 3].xy;
|
|
||||||
|
|
||||||
// Screen-space center (with offset) and pre-computed AABB half-extents
|
if (topZone > 0.0 || botZone > 0.0 || leftZone > 0.0 || rightZone > 0.0) {
|
||||||
vec2 ctr = rect.xy + sinkProps.yz;
|
for (int i = 0; i < rectCount; i++) {
|
||||||
|
vec4 rect = rectData[i * 5];
|
||||||
|
vec4 sinkProps = rectData[i * 5 + 1];
|
||||||
|
vec2 sinkSh = rectData[i * 5 + 3].xy;
|
||||||
|
|
||||||
// Delay sink to absorb smin blend depth (cubic smin max = k/6)
|
vec2 ctr = rect.xy + sinkProps.yz;
|
||||||
float preOff = smoothFactor * (1.0 / 6.0);
|
|
||||||
|
|
||||||
// Top border: track rect's BOTTOM edge, only within 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);
|
|
||||||
|
|
||||||
// Bottom border: track rect's TOP edge
|
float botPen = clamp((ctr.y - sinkSh.y) - innerBot - preOff, 0.0,
|
||||||
float botPen = clamp((ctr.y - sinkSh.y) - innerBot - preOff, 0.0,
|
outerBot - innerBot);
|
||||||
outerBot - innerBot);
|
|
||||||
|
|
||||||
// Left border: track rect's RIGHT edge
|
float leftPen = clamp(innerLeft - (ctr.x + sinkSh.x) - preOff, 0.0,
|
||||||
float leftPen = clamp(innerLeft - (ctr.x + sinkSh.x) - preOff, 0.0,
|
innerLeft - outerLeft);
|
||||||
innerLeft - outerLeft);
|
|
||||||
|
|
||||||
// Right border: track rect's LEFT edge
|
float rightPen = clamp((ctr.x - sinkSh.x) - innerRight - preOff, 0.0,
|
||||||
float rightPen = clamp((ctr.x - sinkSh.x) - innerRight - preOff, 0.0,
|
outerRight - innerRight);
|
||||||
outerRight - innerRight);
|
|
||||||
|
|
||||||
// Lateral distance from pixel to rect's extent along each edge
|
float hLat = max(abs(pixel.x - ctr.x) - sinkSh.x, 0.0);
|
||||||
float hLat = max(abs(pixel.x - ctr.x) - sinkSh.x, 0.0);
|
float vLat = max(abs(pixel.y - ctr.y) - sinkSh.y, 0.0);
|
||||||
float vLat = max(abs(pixel.y - ctr.y) - sinkSh.y, 0.0);
|
|
||||||
|
|
||||||
// Perpendicular proximity: full strength in border, fade inside inner
|
float sink = max(max(topPen * smoothstep(s, 0.0, hLat) * topZone,
|
||||||
// area
|
botPen * smoothstep(s, 0.0, hLat) * botZone),
|
||||||
float topZone =
|
max(leftPen * smoothstep(s, 0.0, vLat) * leftZone,
|
||||||
1.0 - smoothstep(innerTop, innerTop + smoothFactor, pixel.y);
|
rightPen * smoothstep(s, 0.0, vLat) * rightZone));
|
||||||
float botZone = smoothstep(innerBot - smoothFactor, innerBot, pixel.y);
|
sinkValue = max(sinkValue, sink);
|
||||||
float leftZone =
|
}
|
||||||
1.0 - smoothstep(innerLeft, innerLeft + smoothFactor, pixel.x);
|
|
||||||
float rightZone =
|
|
||||||
smoothstep(innerRight - smoothFactor, innerRight, pixel.x);
|
|
||||||
|
|
||||||
float s = smoothFactor * 2.0;
|
|
||||||
float sink = max(max(topPen * smoothstep(s, 0.0, hLat) * topZone,
|
|
||||||
botPen * smoothstep(s, 0.0, hLat) * botZone),
|
|
||||||
max(leftPen * smoothstep(s, 0.0, vLat) * leftZone,
|
|
||||||
rightPen * smoothstep(s, 0.0, vLat) * rightZone));
|
|
||||||
sinkValue = max(sinkValue, sink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dInner -= sinkValue;
|
dInner -= sinkValue;
|
||||||
|
|
||||||
// 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 negInner = -dInner;
|
||||||
float dFrame = dOuter >= negInner ? dOuter
|
float dFrame = dOuter >= negInner ? dOuter
|
||||||
: smaxSharpA(dOuter, negInner, smoothFactor);
|
: smaxSharpA(dOuter, negInner, smoothFactor);
|
||||||
@@ -269,37 +225,22 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A pixel this far from every rect cannot have a value, so it is not written.
|
|
||||||
// The one-unit margin keeps the outermost pixels of the quad out of the AA: their
|
|
||||||
// derivatives are undefined (the quad neighbour is outside the primitive), which
|
|
||||||
// is what drew a line along the coverage edge.
|
|
||||||
float reach = max(smoothFactor, shadowRadius);
|
float reach = max(smoothFactor, shadowRadius);
|
||||||
if (mergedSdf > reach - 1.0)
|
float pxWidth = max(length(vec2(dFdx(mergedSdf), dFdy(mergedSdf))), 1e-5);
|
||||||
|
if (mergedSdf > reach - max(2.0 * pxWidth, 1.0))
|
||||||
discard;
|
discard;
|
||||||
|
|
||||||
// Only the owner writes a pixel, which is what makes the per-shape path look
|
|
||||||
// like one surface: qt_Opacity is applied once per pixel instead of once per
|
|
||||||
// overlapping draw. The single group draw has no owner (myIndex == -2), so the
|
|
||||||
// test is skipped and the value below is the only value a pixel can have.
|
|
||||||
if (myIndex >= -1 && owner != myIndex)
|
if (myIndex >= -1 && owner != myIndex)
|
||||||
discard;
|
discard;
|
||||||
|
|
||||||
// Fixed-width AA: fwidth is capped at one unit, so a bad derivative near the quad
|
float materialAlpha = clamp(0.5 - mergedSdf / pxWidth, 0.0, 1.0);
|
||||||
// boundary cannot make a wide edge.
|
|
||||||
float fw = min(fwidth(mergedSdf), 1.0);
|
|
||||||
float materialAlpha = 1.0 - smoothstep(-fw, fw, mergedSdf);
|
|
||||||
|
|
||||||
// Phase 2: the shadow, as a distance falloff from the merged surface. The
|
|
||||||
// quadratic reaches zero with zero slope at its outer edge, so that edge needs no
|
|
||||||
// AA of its own. Only the single group draw writes a shadow: in the per-shape
|
|
||||||
// path overlapping draws would darken the same area twice.
|
|
||||||
float shadowAlpha = 0.0;
|
float shadowAlpha = 0.0;
|
||||||
if (shadowRadius > 0.0 && myIndex == -2) {
|
if (shadowRadius > 0.0 && myIndex == -2) {
|
||||||
float t = clamp(mergedSdf / shadowRadius, 0.0, 1.0);
|
float t = clamp(mergedSdf / shadowRadius, 0.0, 1.0);
|
||||||
shadowAlpha = (1.0 - t) * (1.0 - t) * shadowColor.a;
|
shadowAlpha = (1.0 - t) * (1.0 - t) * shadowColor.a;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Material over shadow, premultiplied.
|
|
||||||
float totalAlpha = materialAlpha + shadowAlpha * (1.0 - materialAlpha);
|
float totalAlpha = materialAlpha + shadowAlpha * (1.0 - materialAlpha);
|
||||||
vec3 rgb = color.rgb * materialAlpha
|
vec3 rgb = color.rgb * materialAlpha
|
||||||
+ shadowColor.rgb * shadowAlpha * (1.0 - materialAlpha);
|
+ shadowColor.rgb * shadowAlpha * (1.0 - materialAlpha);
|
||||||
|
|||||||
Binary file not shown.
@@ -4,6 +4,7 @@
|
|||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
#include <QtGui/private/qfont_p.h>
|
#include <QtGui/private/qfont_p.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcQuickLayouts, "qt.quick.layouts")
|
Q_LOGGING_CATEGORY(lcQuickLayouts, "qt.quick.layouts")
|
||||||
@@ -54,49 +55,13 @@ SnapGridLayoutItem::SnapGridLayoutItem(
|
|||||||
int column,
|
int column,
|
||||||
int rowSpan,
|
int rowSpan,
|
||||||
int columnSpan,
|
int columnSpan,
|
||||||
Qt::Alignment alignment,
|
Qt::Alignment alignment)
|
||||||
SnapLayout* layout)
|
: QQuickGridLayoutItem(item, row, column, rowSpan, columnSpan, alignment) {}
|
||||||
: QQuickGridLayoutItem(item, row, column, rowSpan, columnSpan, alignment)
|
|
||||||
, m_layout(layout) {}
|
|
||||||
|
|
||||||
void SnapGridLayoutItem::setGeometry(const QRectF& rect) {
|
void SnapGridLayoutItem::setGeometry(const QRectF& rect) {
|
||||||
QQuickLayoutAttached* info = attachedLayoutObject(m_item, false);
|
QQuickLayoutAttached* info = attachedLayoutObject(m_item, false);
|
||||||
QRectF placed = info ? rect.marginsRemoved(info->effectiveQMargins())
|
engineRect = info ? rect.marginsRemoved(info->effectiveQMargins()) : rect;
|
||||||
: rect;
|
placed = true;
|
||||||
|
|
||||||
const QQuickWindow* win = m_item->window();
|
|
||||||
const qreal dpr = win ? win->effectiveDevicePixelRatio() : 1.0;
|
|
||||||
|
|
||||||
if (dpr > 0.0) {
|
|
||||||
const QPointF origin = absoluteOrigin(m_layout);
|
|
||||||
const qreal left = (origin.x() + placed.x()) * dpr;
|
|
||||||
const qreal top = (origin.y() + placed.y()) * dpr;
|
|
||||||
const qreal right = (origin.x() + placed.right()) * dpr;
|
|
||||||
const qreal bottom = (origin.y() + placed.bottom()) * dpr;
|
|
||||||
|
|
||||||
const qint64 nearX = std::llround(left);
|
|
||||||
const qint64 nearY = std::llround(top);
|
|
||||||
const qint64 farX = std::llround(right);
|
|
||||||
const qint64 farY = std::llround(bottom);
|
|
||||||
|
|
||||||
placed = QRectF(
|
|
||||||
qreal(nearX) / dpr - origin.x(),
|
|
||||||
qreal(nearY) / dpr - origin.y(),
|
|
||||||
qreal(farX - nearX) / dpr,
|
|
||||||
qreal(farY - nearY) / dpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
const QSizeF oldSize(m_item->width(), m_item->height());
|
|
||||||
const QSizeF newSize = placed.size();
|
|
||||||
|
|
||||||
m_item->setPosition(placed.topLeft());
|
|
||||||
if (newSize == oldSize) {
|
|
||||||
if (QQuickLayout* lay = qobject_cast<QQuickLayout*>(m_item)) {
|
|
||||||
if (lay->invalidatedArrangement()) lay->rearrange(newSize);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_item->setSize(newSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SnapLayoutPrivate::mirrorChange() {
|
void SnapLayoutPrivate::mirrorChange() {
|
||||||
@@ -119,6 +84,7 @@ SnapLayout::~SnapLayout() {
|
|||||||
void SnapLayout::componentComplete() {
|
void SnapLayout::componentComplete() {
|
||||||
QQuickLayout::componentComplete();
|
QQuickLayout::componentComplete();
|
||||||
|
|
||||||
|
applySpacing();
|
||||||
invalidate();
|
invalidate();
|
||||||
ensureLayoutItemsUpdated(QQuickLayout::ApplySizeHints);
|
ensureLayoutItemsUpdated(QQuickLayout::ApplySizeHints);
|
||||||
|
|
||||||
@@ -128,8 +94,12 @@ void SnapLayout::componentComplete() {
|
|||||||
|
|
||||||
void SnapLayout::itemChange(ItemChange change, const ItemChangeData& value) {
|
void SnapLayout::itemChange(ItemChange change, const ItemChangeData& value) {
|
||||||
QQuickLayout::itemChange(change, value);
|
QQuickLayout::itemChange(change, value);
|
||||||
if (change == ItemDevicePixelRatioHasChanged || change == ItemSceneChange)
|
if (change == ItemChildRemovedChange && value.item)
|
||||||
|
d_func()->childSizes.remove(value.item);
|
||||||
|
if (change == ItemDevicePixelRatioHasChanged || change == ItemSceneChange) {
|
||||||
|
applySpacing();
|
||||||
invalidate();
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSizeF SnapLayout::sizeHint(Qt::SizeHint whichSizeHint) const {
|
QSizeF SnapLayout::sizeHint(Qt::SizeHint whichSizeHint) const {
|
||||||
@@ -169,16 +139,33 @@ Qt::LayoutDirection SnapLayout::effectiveLayoutDirection() const {
|
|||||||
|
|
||||||
qreal SnapLayout::spacing() const {
|
qreal SnapLayout::spacing() const {
|
||||||
Q_D(const SnapLayout);
|
Q_D(const SnapLayout);
|
||||||
return d->engine.spacing(d->orientation, d->styleInfo);
|
if (!std::isnan(d->spacingRequest)) return d->spacingRequest;
|
||||||
|
return d->styleInfo->spacing(d->orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapLayout::applySpacing() {
|
||||||
|
Q_D(SnapLayout);
|
||||||
|
if (!isReady()) return;
|
||||||
|
|
||||||
|
const QQuickWindow* win = window();
|
||||||
|
const qreal dpr = win ? win->effectiveDevicePixelRatio() : 1.0;
|
||||||
|
|
||||||
|
qreal space = d->spacingRequest;
|
||||||
|
if (std::isnan(space)) space = d->styleInfo->spacing(d->orientation);
|
||||||
|
|
||||||
|
if (dpr > 0.0) space = qreal(std::llround(space * dpr)) / dpr;
|
||||||
|
|
||||||
|
d->engine.setSpacing(space, Qt::Horizontal | Qt::Vertical);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SnapLayout::setSpacing(qreal space) {
|
void SnapLayout::setSpacing(qreal space) {
|
||||||
Q_D(SnapLayout);
|
Q_D(SnapLayout);
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
||||||
if (std::isnan(space) || spacing() == space) return;
|
if (std::isnan(space) || d->spacingRequest == space) return;
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
d->engine.setSpacing(space, Qt::Horizontal | Qt::Vertical);
|
d->spacingRequest = space;
|
||||||
|
applySpacing();
|
||||||
invalidate();
|
invalidate();
|
||||||
emit spacingChanged();
|
emit spacingChanged();
|
||||||
}
|
}
|
||||||
@@ -242,6 +229,14 @@ void SnapLayout::updateLayoutItems() {
|
|||||||
Q_D(SnapLayout);
|
Q_D(SnapLayout);
|
||||||
if (!isReady()) return;
|
if (!isReady()) return;
|
||||||
|
|
||||||
|
const auto items = childItems();
|
||||||
|
for (auto entry = d->childSizes.begin(); entry != d->childSizes.end();) {
|
||||||
|
if (items.contains(entry.key()))
|
||||||
|
++entry;
|
||||||
|
else
|
||||||
|
entry = d->childSizes.erase(entry);
|
||||||
|
}
|
||||||
|
|
||||||
d->engine.deleteItems();
|
d->engine.deleteItems();
|
||||||
insertLayoutItems();
|
insertLayoutItems();
|
||||||
}
|
}
|
||||||
@@ -286,8 +281,8 @@ void SnapLayout::insertLayoutItems() {
|
|||||||
int gridRow = 0;
|
int gridRow = 0;
|
||||||
int gridColumn = index;
|
int gridColumn = index;
|
||||||
if (d->orientation == Qt::Vertical) qSwap(gridRow, gridColumn);
|
if (d->orientation == Qt::Vertical) qSwap(gridRow, gridColumn);
|
||||||
QGridLayoutItem* layoutItem = new SnapGridLayoutItem(
|
QGridLayoutItem* layoutItem =
|
||||||
child, gridRow, gridColumn, 1, 1, alignment, this);
|
new SnapGridLayoutItem(child, gridRow, gridColumn, 1, 1, alignment);
|
||||||
|
|
||||||
if (hStretch >= 0) {
|
if (hStretch >= 0) {
|
||||||
if (!fillWidth)
|
if (!fillWidth)
|
||||||
@@ -315,6 +310,7 @@ void SnapLayout::removeGridItem(QGridLayoutItem* gridItem) {
|
|||||||
void SnapLayout::itemDestroyed(QQuickItem* item) {
|
void SnapLayout::itemDestroyed(QQuickItem* item) {
|
||||||
if (!isReady()) return;
|
if (!isReady()) return;
|
||||||
Q_D(SnapLayout);
|
Q_D(SnapLayout);
|
||||||
|
d->childSizes.remove(item);
|
||||||
if (QQuickGridLayoutItem* gridItem = d->engine.findLayoutItem(item)) {
|
if (QQuickGridLayoutItem* gridItem = d->engine.findLayoutItem(item)) {
|
||||||
removeGridItem(gridItem);
|
removeGridItem(gridItem);
|
||||||
delete gridItem;
|
delete gridItem;
|
||||||
@@ -381,6 +377,98 @@ void SnapLayout::clearAncestorListeners() {
|
|||||||
d->ancestorListeners.clear();
|
d->ancestorListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SnapLayout::arrangeChildren() {
|
||||||
|
Q_D(SnapLayout);
|
||||||
|
const QQuickWindow* win = window();
|
||||||
|
const qreal dpr = win ? win->effectiveDevicePixelRatio() : 1.0;
|
||||||
|
if (dpr <= 0.0) return;
|
||||||
|
|
||||||
|
const bool horiz = d->orientation == Qt::Horizontal;
|
||||||
|
const QPointF origin = absoluteOrigin(this);
|
||||||
|
const qint64 originXDev = std::llround(origin.x() * dpr);
|
||||||
|
const qint64 originYDev = std::llround(origin.y() * dpr);
|
||||||
|
|
||||||
|
qreal prevEndExact = 0.0;
|
||||||
|
qint64 prevNearDev = 0;
|
||||||
|
qint64 prevSizeDev = 0;
|
||||||
|
bool started = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < d->engine.itemCount(); ++i) {
|
||||||
|
SnapGridLayoutItem* gridItem =
|
||||||
|
static_cast<SnapGridLayoutItem*>(d->engine.itemAt(i));
|
||||||
|
if (!gridItem->placed) continue;
|
||||||
|
QQuickItem* child = gridItem->layoutItem();
|
||||||
|
const QRectF& cell = gridItem->engineRect;
|
||||||
|
|
||||||
|
const qreal mainExact = (horiz ? cell.x() : cell.y()) * dpr;
|
||||||
|
const qreal mainSizeExact =
|
||||||
|
(horiz ? cell.width() : cell.height()) * dpr;
|
||||||
|
const qreal crossExact = (horiz ? cell.y() : cell.x()) * dpr;
|
||||||
|
const qint64 mainSizeDev = std::llround(mainSizeExact);
|
||||||
|
const qint64 crossSizeDev =
|
||||||
|
std::llround((horiz ? cell.height() : cell.width()) * dpr);
|
||||||
|
const qint64 engineWidth = horiz ? mainSizeDev : crossSizeDev;
|
||||||
|
const qint64 engineHeight = horiz ? crossSizeDev : mainSizeDev;
|
||||||
|
|
||||||
|
qint64 mainNear = 0;
|
||||||
|
if (!started) {
|
||||||
|
mainNear = std::llround(mainExact);
|
||||||
|
started = true;
|
||||||
|
} else {
|
||||||
|
mainNear = prevNearDev + prevSizeDev +
|
||||||
|
std::llround(mainExact - prevEndExact);
|
||||||
|
}
|
||||||
|
|
||||||
|
const qint64 mainFarDev = std::llround(mainExact + mainSizeExact);
|
||||||
|
|
||||||
|
const auto stored = d->childSizes.constFind(child);
|
||||||
|
qint64 width = engineWidth;
|
||||||
|
qint64 height = engineHeight;
|
||||||
|
if (horiz)
|
||||||
|
width = std::max(mainFarDev - mainNear, qint64(0));
|
||||||
|
else
|
||||||
|
height = std::max(mainFarDev - mainNear, qint64(0));
|
||||||
|
|
||||||
|
if (stored != d->childSizes.constEnd() &&
|
||||||
|
stored.value().engineWidth == engineWidth &&
|
||||||
|
stored.value().engineHeight == engineHeight &&
|
||||||
|
std::llround(child->width() * dpr) == stored.value().width &&
|
||||||
|
std::llround(child->height() * dpr) == stored.value().height) {
|
||||||
|
width = stored.value().width;
|
||||||
|
height = stored.value().height;
|
||||||
|
} else {
|
||||||
|
d->childSizes[child] = SnapLayoutPrivate::ChildSize{
|
||||||
|
engineWidth, engineHeight, width, height};
|
||||||
|
}
|
||||||
|
|
||||||
|
prevEndExact = mainExact + mainSizeExact;
|
||||||
|
prevNearDev = mainNear;
|
||||||
|
prevSizeDev = horiz ? width : height;
|
||||||
|
|
||||||
|
const qint64 crossNear = std::llround(crossExact);
|
||||||
|
const qint64 nearX = originXDev + (horiz ? mainNear : crossNear);
|
||||||
|
const qint64 nearY = originYDev + (horiz ? crossNear : mainNear);
|
||||||
|
|
||||||
|
const QRectF snapped(
|
||||||
|
qreal(nearX) / dpr - origin.x(),
|
||||||
|
qreal(nearY) / dpr - origin.y(),
|
||||||
|
qreal(width) / dpr,
|
||||||
|
qreal(height) / dpr);
|
||||||
|
|
||||||
|
const QSizeF oldSize(child->width(), child->height());
|
||||||
|
const QSizeF newSize = snapped.size();
|
||||||
|
|
||||||
|
child->setPosition(snapped.topLeft());
|
||||||
|
if (newSize == oldSize) {
|
||||||
|
if (QQuickLayout* lay = qobject_cast<QQuickLayout*>(child)) {
|
||||||
|
if (lay->invalidatedArrangement()) lay->rearrange(newSize);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
child->setSize(newSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SnapLayout::rearrange(const QSizeF& size) {
|
void SnapLayout::rearrange(const QSizeF& size) {
|
||||||
Q_D(SnapLayout);
|
Q_D(SnapLayout);
|
||||||
if (!isReady() || !size.isValid()) return;
|
if (!isReady() || !size.isValid()) return;
|
||||||
@@ -408,7 +496,11 @@ void SnapLayout::rearrange(const QSizeF& size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QQuickLayout::rearrange(size);
|
QQuickLayout::rearrange(size);
|
||||||
|
|
||||||
|
for (int i = 0; i < d->engine.itemCount(); ++i)
|
||||||
|
static_cast<SnapGridLayoutItem*>(d->engine.itemAt(i))->placed = false;
|
||||||
d->engine.setGeometries(QRectF(QPointF(0, 0), size), d->styleInfo);
|
d->engine.setGeometries(QRectF(QPointF(0, 0), size), d->styleInfo);
|
||||||
|
arrangeChildren();
|
||||||
d->rearranging = false;
|
d->rearranging = false;
|
||||||
|
|
||||||
for (auto childItem : std::as_const(d->invalidateAfterRearrange))
|
for (auto childItem : std::as_const(d->invalidateAfterRearrange))
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QtMath>
|
||||||
#include <QtGui/private/qabstractlayoutstyleinfo_p.h>
|
#include <QtGui/private/qabstractlayoutstyleinfo_p.h>
|
||||||
#include <QtGui/private/qgridlayoutengine_p.h>
|
#include <QtGui/private/qgridlayoutengine_p.h>
|
||||||
#include <QtQuickLayouts/private/qquickgridlayoutengine_p.h>
|
#include <QtQuickLayouts/private/qquickgridlayoutengine_p.h>
|
||||||
@@ -20,6 +22,20 @@ class SnapLayoutStyleInfo : public QAbstractLayoutStyleInfo {
|
|||||||
bool hasChangedCore() const override;
|
bool hasChangedCore() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SnapGridLayoutEngine : public QGridLayoutEngine {
|
||||||
|
public:
|
||||||
|
SnapGridLayoutEngine() : QGridLayoutEngine(Qt::AlignVCenter, false) {}
|
||||||
|
|
||||||
|
QQuickGridLayoutItem* findLayoutItem(QQuickItem* item) const {
|
||||||
|
for (int i = int(q_items.size()) - 1; i >= 0; --i) {
|
||||||
|
QQuickGridLayoutItem* layoutItem =
|
||||||
|
static_cast<QQuickGridLayoutItem*>(q_items.at(i));
|
||||||
|
if (layoutItem->layoutItem() == item) return layoutItem;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class SnapGridLayoutItem : public QQuickGridLayoutItem {
|
class SnapGridLayoutItem : public QQuickGridLayoutItem {
|
||||||
public:
|
public:
|
||||||
SnapGridLayoutItem(
|
SnapGridLayoutItem(
|
||||||
@@ -28,13 +44,12 @@ class SnapGridLayoutItem : public QQuickGridLayoutItem {
|
|||||||
int column,
|
int column,
|
||||||
int rowSpan,
|
int rowSpan,
|
||||||
int columnSpan,
|
int columnSpan,
|
||||||
Qt::Alignment alignment,
|
Qt::Alignment alignment);
|
||||||
SnapLayout* layout);
|
|
||||||
|
|
||||||
void setGeometry(const QRectF& rect) override;
|
void setGeometry(const QRectF& rect) override;
|
||||||
|
|
||||||
private:
|
QRectF engineRect;
|
||||||
SnapLayout* m_layout;
|
bool placed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SnapLayout : public QQuickLayout {
|
class SnapLayout : public QQuickLayout {
|
||||||
@@ -101,6 +116,10 @@ class SnapLayout : public QQuickLayout {
|
|||||||
void insertLayoutItems();
|
void insertLayoutItems();
|
||||||
void removeGridItem(QGridLayoutItem* gridItem);
|
void removeGridItem(QGridLayoutItem* gridItem);
|
||||||
|
|
||||||
|
void arrangeChildren();
|
||||||
|
|
||||||
|
void applySpacing();
|
||||||
|
|
||||||
void updateAncestorListeners();
|
void updateAncestorListeners();
|
||||||
void clearAncestorListeners();
|
void clearAncestorListeners();
|
||||||
};
|
};
|
||||||
@@ -113,10 +132,11 @@ class SnapLayoutPrivate : public QQuickLayoutPrivate {
|
|||||||
|
|
||||||
void mirrorChange() override;
|
void mirrorChange() override;
|
||||||
|
|
||||||
QQuickGridLayoutEngine engine;
|
SnapGridLayoutEngine engine;
|
||||||
SnapLayoutStyleInfo* styleInfo = nullptr;
|
SnapLayoutStyleInfo* styleInfo = nullptr;
|
||||||
Qt::Orientation orientation = Qt::Horizontal;
|
Qt::Orientation orientation = Qt::Horizontal;
|
||||||
Qt::LayoutDirection layoutDirection = Qt::LeftToRight;
|
Qt::LayoutDirection layoutDirection = Qt::LeftToRight;
|
||||||
|
qreal spacingRequest = qQNaN();
|
||||||
bool rearranging = false;
|
bool rearranging = false;
|
||||||
int recurRearrangeCounter = 0;
|
int recurRearrangeCounter = 0;
|
||||||
QList<QQuickItem*> invalidateAfterRearrange;
|
QList<QQuickItem*> invalidateAfterRearrange;
|
||||||
@@ -124,6 +144,14 @@ class SnapLayoutPrivate : public QQuickLayoutPrivate {
|
|||||||
QVector<QPointer<QQuickItem>> ancestorListeners;
|
QVector<QPointer<QQuickItem>> ancestorListeners;
|
||||||
qint64 originResidualX = -1;
|
qint64 originResidualX = -1;
|
||||||
qint64 originResidualY = -1;
|
qint64 originResidualY = -1;
|
||||||
|
|
||||||
|
struct ChildSize {
|
||||||
|
qint64 engineWidth = 0;
|
||||||
|
qint64 engineHeight = 0;
|
||||||
|
qint64 width = 0;
|
||||||
|
qint64 height = 0;
|
||||||
|
};
|
||||||
|
QHash<QQuickItem*, ChildSize> childSizes;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::components
|
} // namespace ZShell::components
|
||||||
|
|||||||
@@ -29,3 +29,9 @@ qml_module(ZShell-internal
|
|||||||
PkgConfig::GL
|
PkgConfig::GL
|
||||||
ZShell-config
|
ZShell-config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
qt_add_custom_brush_shaders(ZShell-internal "internal_brush_shaders"
|
||||||
|
PREFIX "/"
|
||||||
|
FILES
|
||||||
|
shaders/strokeholemask.frag
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#version 440
|
||||||
|
|
||||||
|
QC_INCLUDE "customfrag.glsl"
|
||||||
|
|
||||||
|
|
||||||
|
float sdRoundRect(vec2 p, vec2 b, float r) {
|
||||||
|
vec2 q = abs(p) - b + vec2(r);
|
||||||
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float keep = 1.0;
|
||||||
|
|
||||||
|
if (data4.x > 0.5) {
|
||||||
|
vec2 p = fragCoord - data1.xy;
|
||||||
|
float feather = max(data3.y, 0.0001);
|
||||||
|
keep = smoothstep(-feather, feather, sdRoundRect(p, data1.zw, data3.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
float a = keep * data2.w * globalAlpha * antialiasingAlpha();
|
||||||
|
fragColor = vec4(data2.rgb * a, a);
|
||||||
|
}
|
||||||
@@ -71,11 +71,23 @@ static QCanvasPath buildStrokePath(
|
|||||||
|
|
||||||
void StrokeCanvasItem::undoLastGroup() {
|
void StrokeCanvasItem::undoLastGroup() {
|
||||||
if (m_strokes.isEmpty()) return;
|
if (m_strokes.isEmpty()) return;
|
||||||
|
|
||||||
const int lastGroup = m_strokes.last().groupId;
|
const int lastGroup = m_strokes.last().groupId;
|
||||||
|
|
||||||
|
QVector<Stroke> group;
|
||||||
for (int i = static_cast<int>(m_strokes.size()) - 1;
|
for (int i = static_cast<int>(m_strokes.size()) - 1;
|
||||||
i >= 0 && m_strokes[i].groupId == lastGroup;
|
i >= 0 && m_strokes[i].groupId == lastGroup;
|
||||||
--i)
|
--i)
|
||||||
m_strokes.remove(i);
|
group.prepend(m_strokes.takeAt(i));
|
||||||
|
|
||||||
|
m_redoStack.append(group);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StrokeCanvasItem::redo() {
|
||||||
|
if (m_redoStack.isEmpty()) return;
|
||||||
|
|
||||||
|
m_strokes.append(m_redoStack.takeLast());
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,19 +118,14 @@ void StrokeCanvasItem::setHoverPoint(const QPointF& point) {
|
|||||||
emit hoverPointChanged();
|
emit hoverPointChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::showHover(qreal x, qreal y) {
|
void StrokeCanvasItem::showHover() {
|
||||||
const QPointF newPoint{x, y};
|
if (m_hoverVisible) return;
|
||||||
const bool pointChanged = (m_hoverPoint != newPoint);
|
|
||||||
const bool visibleChanged = !m_hoverVisible;
|
|
||||||
|
|
||||||
|
|
||||||
if (pointChanged || visibleChanged) {
|
m_hoverVisible = true;
|
||||||
m_hoverVisible = true;
|
update();
|
||||||
m_hoverPoint = newPoint;
|
|
||||||
if (pointChanged) emit hoverPointChanged();
|
emit hoverVisibleChanged();
|
||||||
if (visibleChanged) emit hoverVisibleChanged();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::hideHover() {
|
void StrokeCanvasItem::hideHover() {
|
||||||
@@ -139,6 +146,24 @@ void StrokeCanvasItem::setPenWidth(qreal width) {
|
|||||||
emit penWidthChanged();
|
emit penWidthChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StrokeCanvasItem::setHole(const QRectF& hole) {
|
||||||
|
if (m_hole == hole) return;
|
||||||
|
|
||||||
|
m_hole = hole;
|
||||||
|
update();
|
||||||
|
|
||||||
|
emit holeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StrokeCanvasItem::setHoleRadius(qreal radius) {
|
||||||
|
if (qFuzzyCompare(m_holeRadius, radius)) return;
|
||||||
|
|
||||||
|
m_holeRadius = radius;
|
||||||
|
update();
|
||||||
|
|
||||||
|
emit holeRadiusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void StrokeCanvasItem::beginStroke(qreal x, qreal y, bool polyline) {
|
void StrokeCanvasItem::beginStroke(qreal x, qreal y, bool polyline) {
|
||||||
m_isDrawing = true;
|
m_isDrawing = true;
|
||||||
m_currentStroke.points.clear();
|
m_currentStroke.points.clear();
|
||||||
@@ -199,6 +224,7 @@ void StrokeCanvasItem::endStroke() {
|
|||||||
m_currentStroke.points.clear();
|
m_currentStroke.points.clear();
|
||||||
m_strokes.append(m_currentStroke);
|
m_strokes.append(m_currentStroke);
|
||||||
m_currentStroke = {};
|
m_currentStroke = {};
|
||||||
|
m_redoStack.clear();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
@@ -206,6 +232,7 @@ void StrokeCanvasItem::endStroke() {
|
|||||||
void StrokeCanvasItem::clear() {
|
void StrokeCanvasItem::clear() {
|
||||||
m_strokes.clear();
|
m_strokes.clear();
|
||||||
m_currentStroke = {};
|
m_currentStroke = {};
|
||||||
|
m_redoStack.clear();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QCanvasPainterItem>
|
#include <QCanvasPainterItem>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
|
#include <QRectF>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <qcolor.h>
|
#include <qcolor.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
@@ -27,6 +28,10 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
|||||||
hoverPointChanged)
|
hoverPointChanged)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged)
|
qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged)
|
||||||
|
Q_PROPERTY(QRectF hole READ hole WRITE setHole NOTIFY holeChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal holeRadius READ holeRadius WRITE setHoleRadius NOTIFY
|
||||||
|
holeRadiusChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit StrokeCanvasItem(QQuickItem* parent = nullptr);
|
explicit StrokeCanvasItem(QQuickItem* parent = nullptr);
|
||||||
@@ -37,15 +42,20 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
|||||||
void setHoverVisible(bool visible);
|
void setHoverVisible(bool visible);
|
||||||
void setHoverPoint(const QPointF& point);
|
void setHoverPoint(const QPointF& point);
|
||||||
|
|
||||||
Q_INVOKABLE void showHover(qreal x, qreal y);
|
Q_INVOKABLE void showHover();
|
||||||
Q_INVOKABLE void hideHover();
|
Q_INVOKABLE void hideHover();
|
||||||
Q_INVOKABLE void undoLastGroup();
|
Q_INVOKABLE void undoLastGroup();
|
||||||
|
Q_INVOKABLE void redo();
|
||||||
|
|
||||||
[[nodiscard]] QColor penColor() const { return m_penColor; }
|
[[nodiscard]] QColor penColor() const { return m_penColor; }
|
||||||
[[nodiscard]] qreal penWidth() const { return m_penWidth; }
|
[[nodiscard]] qreal penWidth() const { return m_penWidth; }
|
||||||
|
[[nodiscard]] QRectF hole() const { return m_hole; }
|
||||||
|
[[nodiscard]] qreal holeRadius() const { return m_holeRadius; }
|
||||||
|
|
||||||
void setPenColor(const QColor& color);
|
void setPenColor(const QColor& color);
|
||||||
void setPenWidth(qreal width);
|
void setPenWidth(qreal width);
|
||||||
|
void setHole(const QRectF& hole);
|
||||||
|
void setHoleRadius(qreal radius);
|
||||||
|
|
||||||
Q_INVOKABLE void clear();
|
Q_INVOKABLE void clear();
|
||||||
|
|
||||||
@@ -56,6 +66,8 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
|||||||
signals:
|
signals:
|
||||||
void penColorChanged();
|
void penColorChanged();
|
||||||
void penWidthChanged();
|
void penWidthChanged();
|
||||||
|
void holeChanged();
|
||||||
|
void holeRadiusChanged();
|
||||||
void hoverVisibleChanged();
|
void hoverVisibleChanged();
|
||||||
void hoverPointChanged();
|
void hoverPointChanged();
|
||||||
|
|
||||||
@@ -72,9 +84,12 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
|||||||
qreal m_penWidth = 4.0;
|
qreal m_penWidth = 4.0;
|
||||||
bool m_isDrawing = false;
|
bool m_isDrawing = false;
|
||||||
|
|
||||||
|
QRectF m_hole;
|
||||||
|
qreal m_holeRadius = 0.0;
|
||||||
int m_nextGroupId = 0;
|
int m_nextGroupId = 0;
|
||||||
QVector<Stroke> m_strokes;
|
QVector<Stroke> m_strokes;
|
||||||
Stroke m_currentStroke;
|
Stroke m_currentStroke;
|
||||||
|
QVector<QVector<Stroke>> m_redoStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // namespace ZShell::internal
|
}; // namespace ZShell::internal
|
||||||
|
|||||||
@@ -1,11 +1,98 @@
|
|||||||
#include "strokecanvasrenderer.hpp"
|
#include "strokecanvasrenderer.hpp"
|
||||||
#include "strokecanvasitem.hpp"
|
#include "strokecanvasitem.hpp"
|
||||||
|
#include <QCanvasPainter>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QVector4D>
|
||||||
#include <qcolor.h>
|
#include <qcolor.h>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
static void drawStroke(
|
using Qt::StringLiterals::operator""_s;
|
||||||
|
|
||||||
|
static const QString kStrokeHoleShader = u":/shaders/strokeholemask.frag.qsb"_s;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct HoleUniforms {
|
||||||
|
QVector4D data1; // center.xy, halfSize.xy
|
||||||
|
QVector4D data3; // x: radius, y: feather in logical px
|
||||||
|
QVector4D data4; // x: hole enabled
|
||||||
|
};
|
||||||
|
|
||||||
|
static HoleUniforms holeUniforms(
|
||||||
|
const QRectF& hole, qreal radius, qreal devicePixelRatio) {
|
||||||
|
const bool active = hole.isValid() && hole.width() > 0 && hole.height() > 0;
|
||||||
|
|
||||||
|
if (!active) return {QVector4D(), QVector4D(), QVector4D()};
|
||||||
|
|
||||||
|
const QPointF center = hole.center();
|
||||||
|
const QPointF half(hole.width() * 0.5, hole.height() * 0.5);
|
||||||
|
const qreal maxRadius = qMin(half.x(), half.y());
|
||||||
|
|
||||||
|
return {
|
||||||
|
QVector4D(
|
||||||
|
static_cast<float>(center.x()),
|
||||||
|
static_cast<float>(center.y()),
|
||||||
|
static_cast<float>(half.x()),
|
||||||
|
static_cast<float>(half.y())),
|
||||||
|
QVector4D(
|
||||||
|
static_cast<float>(qMin(radius, maxRadius)),
|
||||||
|
static_cast<float>(1.0 / devicePixelRatio),
|
||||||
|
0.0f,
|
||||||
|
0.0f),
|
||||||
|
QVector4D(1.0, 0.0, 0.0, 0.0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void applyHole(QCanvasCustomBrush& brush, const HoleUniforms& u) {
|
||||||
|
brush.setData1(u.data1);
|
||||||
|
brush.setData3(u.data3);
|
||||||
|
brush.setData4(u.data4);
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
void StrokeCanvasRenderer::updateBrushHole() {
|
||||||
|
const HoleUniforms uniforms =
|
||||||
|
holeUniforms(m_hole, m_holeRadius, m_devicePixelRatio);
|
||||||
|
|
||||||
|
if (uniforms.data1 == m_lastHoleData1 &&
|
||||||
|
uniforms.data3 == m_lastHoleData3 && uniforms.data4 == m_lastHoleData4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_lastHoleData1 = uniforms.data1;
|
||||||
|
m_lastHoleData3 = uniforms.data3;
|
||||||
|
m_lastHoleData4 = uniforms.data4;
|
||||||
|
++m_holeToken;
|
||||||
|
|
||||||
|
for (auto& entry : m_brushes) {
|
||||||
|
if (entry.holeToken == m_holeToken) continue;
|
||||||
|
|
||||||
|
applyHole(entry.brush, uniforms);
|
||||||
|
entry.holeToken = m_holeToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QCanvasCustomBrush& StrokeCanvasRenderer::brushFor(const QColor& color) {
|
||||||
|
const QRgb key = color.rgba();
|
||||||
|
|
||||||
|
if (auto it = m_brushes.find(key); it != m_brushes.end()) return it->brush;
|
||||||
|
|
||||||
|
const HoleUniforms uniforms =
|
||||||
|
holeUniforms(m_hole, m_holeRadius, m_devicePixelRatio);
|
||||||
|
|
||||||
|
BrushEntry entry;
|
||||||
|
entry.brush.setFragmentShader(kStrokeHoleShader);
|
||||||
|
applyHole(entry.brush, uniforms);
|
||||||
|
entry.brush.setData2(
|
||||||
|
QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF()));
|
||||||
|
entry.holeToken = m_holeToken;
|
||||||
|
|
||||||
|
return m_brushes.insert(key, entry)->brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StrokeCanvasRenderer::drawStroke(
|
||||||
QCanvasPainter* painter,
|
QCanvasPainter* painter,
|
||||||
const QVector<QPointF>& points,
|
const QVector<QPointF>& points,
|
||||||
const QColor& color,
|
const QColor& color,
|
||||||
@@ -13,8 +100,10 @@ static void drawStroke(
|
|||||||
bool polyline) {
|
bool polyline) {
|
||||||
if (points.isEmpty()) return;
|
if (points.isEmpty()) return;
|
||||||
|
|
||||||
painter->setStrokeStyle(color);
|
QCanvasCustomBrush& brush = brushFor(color);
|
||||||
painter->setFillStyle(color);
|
|
||||||
|
painter->setStrokeStyle(brush);
|
||||||
|
painter->setFillStyle(brush);
|
||||||
painter->setLineWidth(width);
|
painter->setLineWidth(width);
|
||||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||||
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
||||||
@@ -65,7 +154,7 @@ static void drawStroke(
|
|||||||
painter->stroke();
|
painter->stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawHoverCursor(
|
void StrokeCanvasRenderer::drawHoverCursor(
|
||||||
QCanvasPainter* painter,
|
QCanvasPainter* painter,
|
||||||
const QPointF& point,
|
const QPointF& point,
|
||||||
qreal penWidth,
|
qreal penWidth,
|
||||||
@@ -74,7 +163,7 @@ static void drawHoverCursor(
|
|||||||
const qreal radius = penWidth * 0.5;
|
const qreal radius = penWidth * 0.5;
|
||||||
|
|
||||||
if (isDrawing) {
|
if (isDrawing) {
|
||||||
painter->setFillStyle(penColor);
|
painter->setFillStyle(brushFor(penColor));
|
||||||
painter->beginPath();
|
painter->beginPath();
|
||||||
painter->circle(point, radius);
|
painter->circle(point, radius);
|
||||||
painter->fill();
|
painter->fill();
|
||||||
@@ -85,7 +174,7 @@ static void drawHoverCursor(
|
|||||||
const bool useDashes = penWidth > 10.0;
|
const bool useDashes = penWidth > 10.0;
|
||||||
|
|
||||||
auto drawOutline = [&](const QColor& color, qreal width) {
|
auto drawOutline = [&](const QColor& color, qreal width) {
|
||||||
painter->setStrokeStyle(color);
|
painter->setStrokeStyle(brushFor(color));
|
||||||
painter->setLineWidth(width);
|
painter->setLineWidth(width);
|
||||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||||
|
|
||||||
@@ -117,7 +206,7 @@ static void drawHoverCursor(
|
|||||||
};
|
};
|
||||||
|
|
||||||
auto drawCrosshair = [&](const QColor& color, qreal width) {
|
auto drawCrosshair = [&](const QColor& color, qreal width) {
|
||||||
painter->setStrokeStyle(color);
|
painter->setStrokeStyle(brushFor(color));
|
||||||
painter->setLineWidth(width);
|
painter->setLineWidth(width);
|
||||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||||
|
|
||||||
@@ -148,8 +237,11 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
|
|||||||
|
|
||||||
m_penColor = canvas->m_penColor;
|
m_penColor = canvas->m_penColor;
|
||||||
m_penWidth = canvas->m_penWidth;
|
m_penWidth = canvas->m_penWidth;
|
||||||
|
m_hole = canvas->m_hole;
|
||||||
|
m_holeRadius = canvas->m_holeRadius;
|
||||||
|
m_devicePixelRatio =
|
||||||
|
canvas->window() ? canvas->window()->effectiveDevicePixelRatio() : 1.0;
|
||||||
|
|
||||||
// Detect removed strokes: present in renderer but no longer in canvas
|
|
||||||
QSet<int> canvasGroupIds;
|
QSet<int> canvasGroupIds;
|
||||||
for (const auto& stroke : canvas->m_strokes)
|
for (const auto& stroke : canvas->m_strokes)
|
||||||
canvasGroupIds.insert(stroke.groupId);
|
canvasGroupIds.insert(stroke.groupId);
|
||||||
@@ -162,7 +254,6 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect added strokes: present in canvas but not yet in renderer
|
|
||||||
QSet<int> rendererGroupIds;
|
QSet<int> rendererGroupIds;
|
||||||
for (const auto& stroke : m_strokes)
|
for (const auto& stroke : m_strokes)
|
||||||
rendererGroupIds.insert(stroke.groupId);
|
rendererGroupIds.insert(stroke.groupId);
|
||||||
@@ -180,6 +271,8 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StrokeCanvasRenderer::paint(QCanvasPainter* painter) {
|
void StrokeCanvasRenderer::paint(QCanvasPainter* painter) {
|
||||||
|
updateBrushHole();
|
||||||
|
|
||||||
for (int id : m_pendingGroupRemovals)
|
for (int id : m_pendingGroupRemovals)
|
||||||
painter->removePathGroup(id);
|
painter->removePathGroup(id);
|
||||||
m_pendingGroupRemovals.clear();
|
m_pendingGroupRemovals.clear();
|
||||||
@@ -187,8 +280,8 @@ void StrokeCanvasRenderer::paint(QCanvasPainter* painter) {
|
|||||||
painter->clearRect(0, 0, width(), height());
|
painter->clearRect(0, 0, width(), height());
|
||||||
|
|
||||||
for (const auto& stroke : m_strokes) {
|
for (const auto& stroke : m_strokes) {
|
||||||
painter->setStrokeStyle(stroke.color);
|
painter->setStrokeStyle(brushFor(stroke.color));
|
||||||
painter->setFillStyle(stroke.color);
|
painter->setFillStyle(brushFor(stroke.color));
|
||||||
painter->setLineWidth(stroke.width);
|
painter->setLineWidth(stroke.width);
|
||||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||||
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
||||||
|
|||||||
@@ -1,26 +1,58 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCanvasCustomBrush>
|
||||||
#include <QCanvasPainterItemRenderer>
|
#include <QCanvasPainterItemRenderer>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QRectF>
|
||||||
|
#include <QVector4D>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include "stroke.hpp"
|
#include "stroke.hpp"
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
|
struct BrushEntry {
|
||||||
|
QCanvasCustomBrush brush;
|
||||||
|
qint64 holeToken = -1; // hole generation this brush was built with
|
||||||
|
};
|
||||||
|
|
||||||
class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer {
|
class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer {
|
||||||
public:
|
public:
|
||||||
void synchronizeData(QCanvasPainterItem* item) override;
|
void synchronizeData(QCanvasPainterItem* item) override;
|
||||||
void paint(QCanvasPainter* painter) override;
|
void paint(QCanvasPainter* painter) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QCanvasCustomBrush& brushFor(const QColor& color);
|
||||||
|
void updateBrushHole();
|
||||||
|
void drawStroke(
|
||||||
|
QCanvasPainter* painter,
|
||||||
|
const QVector<QPointF>& points,
|
||||||
|
const QColor& color,
|
||||||
|
qreal width,
|
||||||
|
bool polyline);
|
||||||
|
void drawHoverCursor(
|
||||||
|
QCanvasPainter* painter,
|
||||||
|
const QPointF& point,
|
||||||
|
qreal penWidth,
|
||||||
|
QColor penColor,
|
||||||
|
bool isDrawing);
|
||||||
|
|
||||||
QColor m_penColor;
|
QColor m_penColor;
|
||||||
qreal m_penWidth = 4.0;
|
qreal m_penWidth = 4.0;
|
||||||
bool m_hoverVisible = false;
|
bool m_hoverVisible = false;
|
||||||
QPointF m_hoverPoint;
|
QPointF m_hoverPoint;
|
||||||
bool m_isDrawing = false;
|
bool m_isDrawing = false;
|
||||||
|
QRectF m_hole;
|
||||||
|
qreal m_holeRadius = 0.0;
|
||||||
|
qreal m_devicePixelRatio = 1.0;
|
||||||
|
|
||||||
|
QVector4D m_lastHoleData1;
|
||||||
|
QVector4D m_lastHoleData3;
|
||||||
|
QVector4D m_lastHoleData4;
|
||||||
|
qint64 m_holeToken = 0;
|
||||||
QVector<Stroke> m_strokes;
|
QVector<Stroke> m_strokes;
|
||||||
Stroke m_currentStroke;
|
Stroke m_currentStroke;
|
||||||
QVector<int> m_pendingGroupRemovals;
|
QVector<int> m_pendingGroupRemovals;
|
||||||
|
QHash<QRgb, BrushEntry> m_brushes;
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // namespace ZShell::internal
|
}; // namespace ZShell::internal
|
||||||
|
|||||||
@@ -545,7 +545,7 @@ def generate(
|
|||||||
except (PermissionError, OSError, BlockingIOError):
|
except (PermissionError, OSError, BlockingIOError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def smart_mode(image_path: Path) -> str:
|
def smart_mode(image_path: Path | io.BytesIO) -> str:
|
||||||
is_dark = ""
|
is_dark = ""
|
||||||
|
|
||||||
with Image.open(image_path) as img:
|
with Image.open(image_path) as img:
|
||||||
@@ -739,7 +739,7 @@ def generate(
|
|||||||
|
|
||||||
return rendered_outputs
|
return rendered_outputs
|
||||||
|
|
||||||
def seed_from_image(image_path: Path) -> Hct:
|
def seed_from_image(image_path: Path | io.BytesIO) -> Hct:
|
||||||
image = Image.open(image_path)
|
image = Image.open(image_path)
|
||||||
pixel_len = image.width * image.height
|
pixel_len = image.width * image.height
|
||||||
image_data = image.getdata()
|
image_data = image.getdata()
|
||||||
@@ -837,11 +837,13 @@ def generate(
|
|||||||
image_path = image_path or Path(WALL_PATH)
|
image_path = image_path or Path(WALL_PATH)
|
||||||
|
|
||||||
blob = scheme_thumb_from_db(str(DB_PATH), str(image_path))
|
blob = scheme_thumb_from_db(str(DB_PATH), str(image_path))
|
||||||
if blob is not None:
|
|
||||||
open_thumb = lambda: io.BytesIO(blob)
|
def open_thumb():
|
||||||
else:
|
if blob is not None:
|
||||||
thumb_path = generate_thumbnail(image_path, THUMB_DIR)
|
return io.BytesIO(blob)
|
||||||
open_thumb = lambda: thumb_path
|
else:
|
||||||
|
thumb_path = generate_thumbnail(image_path, THUMB_DIR)
|
||||||
|
return thumb_path
|
||||||
|
|
||||||
seed = seed_from_image(open_thumb())
|
seed = seed_from_image(open_thumb())
|
||||||
name = "dynamic"
|
name = "dynamic"
|
||||||
|
|||||||
Reference in New Issue
Block a user