feat: drawing popout now clips strokes again + add redo + add undo and redo keybinds
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 12s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Successful in 28s
Python / verify (pull_request) Failing after 1m41s
Rust / build (pull_request) Successful in 1m15s
Rust / fmt (pull_request) Successful in 42s
Rust / clippy (pull_request) Successful in 1m6s

This commit is contained in:
2026-10-30 14:16:19 +01:00
parent c5608dc9b9
commit 566b71d386
12 changed files with 467 additions and 539 deletions
+7
View File
@@ -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
} }
+95
View File
@@ -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
View File
@@ -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;
} }
-315
View File
@@ -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
}
}
+24 -2
View File
@@ -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
} }
} }
+1
View File
@@ -6,6 +6,7 @@ find_package(Qt6 REQUIRED COMPONENTS
GuiPrivate GuiPrivate
Quick Quick
CanvasPainter CanvasPainter
CanvasPainterTools
QuickPrivate QuickPrivate
QuickLayouts QuickLayouts
QuickLayoutsPrivate QuickLayoutsPrivate
+6
View File
@@ -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);
}
+39 -12
View File
@@ -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();
} }
+16 -1
View File
@@ -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
+104 -11
View File
@@ -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