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
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:
@@ -4,12 +4,16 @@ import Quickshell
|
||||
import QtQuick
|
||||
import ZShell.Internal
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property alias content: contentLoader.item
|
||||
|
||||
required property ScreenState screenState
|
||||
property rect holeRect
|
||||
property real holeRadius: 0
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
@@ -18,6 +22,9 @@ Item {
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: StrokeCanvas {
|
||||
anchors.fill: parent
|
||||
hole: root.holeRect
|
||||
holeRadius: root.holeRadius
|
||||
penColor: root.screenState.penColor
|
||||
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 QtQuick
|
||||
import ZShell.Config
|
||||
import ZShell.Internal
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Modules.Bar.Popouts
|
||||
|
||||
Item {
|
||||
CustomMouseArea {
|
||||
id: root
|
||||
|
||||
required property Drawing drawing
|
||||
required property DrawingInput input
|
||||
|
||||
required property Item bar
|
||||
required property real borderThickness
|
||||
property bool dashboardShortcutActive
|
||||
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 dashboardShortcutActive
|
||||
property point dragStart
|
||||
property bool osdShortcutActive
|
||||
property bool setInitialPoint: false
|
||||
property bool singleGestureTriggered: false
|
||||
property bool utilitiesShortcutActive
|
||||
required property ScreenState screenState
|
||||
|
||||
function inBottomPanel(panel: Item, x: real, y: real): bool {
|
||||
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
|
||||
@@ -56,124 +64,116 @@ Item {
|
||||
return x >= panelX && x <= panelX + panel.width;
|
||||
}
|
||||
|
||||
acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
DragHandler {
|
||||
id: singleHandler
|
||||
|
||||
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
|
||||
dragThreshold: 0
|
||||
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;
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
if (!containsMouse) {
|
||||
if (!osdShortcutActive) {
|
||||
screenState.osd = false;
|
||||
panels.osd.hovered = false;
|
||||
}
|
||||
|
||||
if (root.geometry.insetTop(root.borderThickness) > pressY && (Config.dashboard.showOnHover || !inDashboard)) {
|
||||
if (dragY > 20) {
|
||||
root.screenState.settings = true;
|
||||
root.singleGestureTriggered = true;
|
||||
} else if (dragY < -20) {
|
||||
root.screenState.settings = false;
|
||||
root.singleGestureTriggered = true;
|
||||
}
|
||||
if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
|
||||
if (pressY > root.screen.height - root.geometry.insetBottom(root.borderThickness) && pressX < root.screen.width / 5 && dragY < -50)
|
||||
root.screenState.clipboard = true;
|
||||
if (Config.bar.autoHide || bar.fullscreen)
|
||||
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 (dragY < -10) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
PointHandler {
|
||||
id: drawingHandler
|
||||
if (screenState.isDrawing && !inLeftPanel(panels.drawing, x, y)) {
|
||||
input.z = 2;
|
||||
panels.drawing.collapse();
|
||||
}
|
||||
|
||||
property bool setInitialPoint: false
|
||||
if (root.bar.fullscreen && !root.bar.isHovered)
|
||||
unhideTimer.start();
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
enabled: root.screenState.isDrawing && (!root.inLeftPanel(root.panels.drawing, hoverHandler.point.position.x, hoverHandler.point.position.y) || !root.panels.drawing.expanded)
|
||||
if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
|
||||
screenState.launcher = true;
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
setInitialPoint = false;
|
||||
root.drawing.content.endStroke();
|
||||
} else {
|
||||
root.panels.drawing.collapse();
|
||||
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;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
root.drawing.content.clear();
|
||||
return;
|
||||
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;
|
||||
}
|
||||
if (x === 0 && y === 0 && origX === 0 && origY === 0)
|
||||
return;
|
||||
} else {
|
||||
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
|
||||
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
|
||||
|
||||
if (!setInitialPoint) {
|
||||
setInitialPoint = true;
|
||||
root.drawing.content.beginStroke(origX, origY);
|
||||
return;
|
||||
if (!osdShortcutActive) {
|
||||
screenState.osd = showOsd;
|
||||
panels.osd.hovered = showOsd;
|
||||
} 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 {
|
||||
@@ -184,98 +184,20 @@ Item {
|
||||
running: false
|
||||
|
||||
onTriggered: {
|
||||
if (hoverHandler.hovered)
|
||||
if (root.containsMouse)
|
||||
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 {
|
||||
function onDashboardChanged() {
|
||||
function onDashboardChanged(): void {
|
||||
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) {
|
||||
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.sidebar = false;
|
||||
root.screenState.resources = false;
|
||||
@@ -285,12 +207,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function onIsDrawingChanged() {
|
||||
if (!root.screenState.isDrawing)
|
||||
root.drawing.clear();
|
||||
function onIsDrawingChanged(): void {
|
||||
if (root.screenState.isDrawing) {
|
||||
root.drawing.content.showHover();
|
||||
} else {
|
||||
root.drawing.content.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function onLauncherChanged() {
|
||||
function onLauncherChanged(): void {
|
||||
if (!root.screenState.launcher) {
|
||||
root.dashboardShortcutActive = false;
|
||||
root.osdShortcutActive = false;
|
||||
@@ -305,16 +230,13 @@ Item {
|
||||
}
|
||||
|
||||
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.settings = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onOsdChanged() {
|
||||
function onOsdChanged(): void {
|
||||
if (root.screenState.osd) {
|
||||
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
|
||||
if (!inOsdArea) {
|
||||
@@ -325,7 +247,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function onResourcesChanged() {
|
||||
function onResourcesChanged(): void {
|
||||
if (root.screenState.resources) {
|
||||
if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
|
||||
root.popouts.hasCurrent = false;
|
||||
@@ -335,7 +257,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function onSettingsChanged() {
|
||||
function onSettingsChanged(): void {
|
||||
if (root.screenState.settings) {
|
||||
root.screenState.resources = false;
|
||||
root.screenState.dashboard = false;
|
||||
@@ -345,9 +267,10 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function onSidebarChanged() {
|
||||
function onSidebarChanged(): void {
|
||||
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.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
|
||||
}
|
||||
}
|
||||
+24
-2
@@ -56,7 +56,7 @@ CustomWindow {
|
||||
property color surfaceColor: Colors.tPalette.m3surface
|
||||
|
||||
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
|
||||
color: "transparent"
|
||||
mask: screenState.isDrawing || screenState.screenshot ? null : (hasFullscreen && !bar.isHovered ? emptyRegion : regions)
|
||||
@@ -347,12 +347,31 @@ CustomWindow {
|
||||
Drawing {
|
||||
id: drawing
|
||||
|
||||
readonly property int extraWidth: drawingBg.radius * 2
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
anchors.fill: parent
|
||||
@@ -365,6 +384,8 @@ CustomWindow {
|
||||
popouts: panels.popouts
|
||||
screen: root.screen
|
||||
screenState: root.screenState
|
||||
input: lazyLoader.item
|
||||
z: 1
|
||||
|
||||
Panels {
|
||||
id: panels
|
||||
@@ -442,6 +463,7 @@ CustomWindow {
|
||||
parent: root.contentItem
|
||||
screen: root.screen
|
||||
screenState: root.screenState
|
||||
z: 4
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ find_package(Qt6 REQUIRED COMPONENTS
|
||||
GuiPrivate
|
||||
Quick
|
||||
CanvasPainter
|
||||
CanvasPainterTools
|
||||
QuickPrivate
|
||||
QuickLayouts
|
||||
QuickLayoutsPrivate
|
||||
|
||||
@@ -29,3 +29,9 @@ qml_module(ZShell-internal
|
||||
PkgConfig::GL
|
||||
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() {
|
||||
if (m_strokes.isEmpty()) return;
|
||||
|
||||
const int lastGroup = m_strokes.last().groupId;
|
||||
|
||||
QVector<Stroke> group;
|
||||
for (int i = static_cast<int>(m_strokes.size()) - 1;
|
||||
i >= 0 && m_strokes[i].groupId == lastGroup;
|
||||
--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();
|
||||
}
|
||||
|
||||
@@ -106,19 +118,14 @@ void StrokeCanvasItem::setHoverPoint(const QPointF& point) {
|
||||
emit hoverPointChanged();
|
||||
}
|
||||
|
||||
void StrokeCanvasItem::showHover(qreal x, qreal y) {
|
||||
const QPointF newPoint{x, y};
|
||||
const bool pointChanged = (m_hoverPoint != newPoint);
|
||||
const bool visibleChanged = !m_hoverVisible;
|
||||
void StrokeCanvasItem::showHover() {
|
||||
if (m_hoverVisible) return;
|
||||
|
||||
|
||||
if (pointChanged || visibleChanged) {
|
||||
m_hoverVisible = true;
|
||||
m_hoverPoint = newPoint;
|
||||
if (pointChanged) emit hoverPointChanged();
|
||||
if (visibleChanged) emit hoverVisibleChanged();
|
||||
update();
|
||||
}
|
||||
m_hoverVisible = true;
|
||||
update();
|
||||
|
||||
emit hoverVisibleChanged();
|
||||
}
|
||||
|
||||
void StrokeCanvasItem::hideHover() {
|
||||
@@ -139,6 +146,24 @@ void StrokeCanvasItem::setPenWidth(qreal width) {
|
||||
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) {
|
||||
m_isDrawing = true;
|
||||
m_currentStroke.points.clear();
|
||||
@@ -199,6 +224,7 @@ void StrokeCanvasItem::endStroke() {
|
||||
m_currentStroke.points.clear();
|
||||
m_strokes.append(m_currentStroke);
|
||||
m_currentStroke = {};
|
||||
m_redoStack.clear();
|
||||
|
||||
update();
|
||||
}
|
||||
@@ -206,6 +232,7 @@ void StrokeCanvasItem::endStroke() {
|
||||
void StrokeCanvasItem::clear() {
|
||||
m_strokes.clear();
|
||||
m_currentStroke = {};
|
||||
m_redoStack.clear();
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QCanvasPainterItem>
|
||||
#include <QColor>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QVector>
|
||||
#include <qcolor.h>
|
||||
#include <qcontainerfwd.h>
|
||||
@@ -27,6 +28,10 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
||||
hoverPointChanged)
|
||||
Q_PROPERTY(
|
||||
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:
|
||||
explicit StrokeCanvasItem(QQuickItem* parent = nullptr);
|
||||
@@ -37,15 +42,20 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
||||
void setHoverVisible(bool visible);
|
||||
void setHoverPoint(const QPointF& point);
|
||||
|
||||
Q_INVOKABLE void showHover(qreal x, qreal y);
|
||||
Q_INVOKABLE void showHover();
|
||||
Q_INVOKABLE void hideHover();
|
||||
Q_INVOKABLE void undoLastGroup();
|
||||
Q_INVOKABLE void redo();
|
||||
|
||||
[[nodiscard]] QColor penColor() const { return m_penColor; }
|
||||
[[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 setPenWidth(qreal width);
|
||||
void setHole(const QRectF& hole);
|
||||
void setHoleRadius(qreal radius);
|
||||
|
||||
Q_INVOKABLE void clear();
|
||||
|
||||
@@ -56,6 +66,8 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
||||
signals:
|
||||
void penColorChanged();
|
||||
void penWidthChanged();
|
||||
void holeChanged();
|
||||
void holeRadiusChanged();
|
||||
void hoverVisibleChanged();
|
||||
void hoverPointChanged();
|
||||
|
||||
@@ -72,9 +84,12 @@ class StrokeCanvasItem : public QCanvasPainterItem {
|
||||
qreal m_penWidth = 4.0;
|
||||
bool m_isDrawing = false;
|
||||
|
||||
QRectF m_hole;
|
||||
qreal m_holeRadius = 0.0;
|
||||
int m_nextGroupId = 0;
|
||||
QVector<Stroke> m_strokes;
|
||||
Stroke m_currentStroke;
|
||||
QVector<QVector<Stroke>> m_redoStack;
|
||||
};
|
||||
|
||||
}; // namespace ZShell::internal
|
||||
|
||||
@@ -1,11 +1,98 @@
|
||||
#include "strokecanvasrenderer.hpp"
|
||||
#include "strokecanvasitem.hpp"
|
||||
#include <QCanvasPainter>
|
||||
#include <QQuickWindow>
|
||||
#include <QVector4D>
|
||||
#include <qcolor.h>
|
||||
#include <QSet>
|
||||
|
||||
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,
|
||||
const QVector<QPointF>& points,
|
||||
const QColor& color,
|
||||
@@ -13,8 +100,10 @@ static void drawStroke(
|
||||
bool polyline) {
|
||||
if (points.isEmpty()) return;
|
||||
|
||||
painter->setStrokeStyle(color);
|
||||
painter->setFillStyle(color);
|
||||
QCanvasCustomBrush& brush = brushFor(color);
|
||||
|
||||
painter->setStrokeStyle(brush);
|
||||
painter->setFillStyle(brush);
|
||||
painter->setLineWidth(width);
|
||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
||||
@@ -65,7 +154,7 @@ static void drawStroke(
|
||||
painter->stroke();
|
||||
}
|
||||
|
||||
static void drawHoverCursor(
|
||||
void StrokeCanvasRenderer::drawHoverCursor(
|
||||
QCanvasPainter* painter,
|
||||
const QPointF& point,
|
||||
qreal penWidth,
|
||||
@@ -74,7 +163,7 @@ static void drawHoverCursor(
|
||||
const qreal radius = penWidth * 0.5;
|
||||
|
||||
if (isDrawing) {
|
||||
painter->setFillStyle(penColor);
|
||||
painter->setFillStyle(brushFor(penColor));
|
||||
painter->beginPath();
|
||||
painter->circle(point, radius);
|
||||
painter->fill();
|
||||
@@ -85,7 +174,7 @@ static void drawHoverCursor(
|
||||
const bool useDashes = penWidth > 10.0;
|
||||
|
||||
auto drawOutline = [&](const QColor& color, qreal width) {
|
||||
painter->setStrokeStyle(color);
|
||||
painter->setStrokeStyle(brushFor(color));
|
||||
painter->setLineWidth(width);
|
||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||
|
||||
@@ -117,7 +206,7 @@ static void drawHoverCursor(
|
||||
};
|
||||
|
||||
auto drawCrosshair = [&](const QColor& color, qreal width) {
|
||||
painter->setStrokeStyle(color);
|
||||
painter->setStrokeStyle(brushFor(color));
|
||||
painter->setLineWidth(width);
|
||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||
|
||||
@@ -148,8 +237,11 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
|
||||
|
||||
m_penColor = canvas->m_penColor;
|
||||
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;
|
||||
for (const auto& stroke : canvas->m_strokes)
|
||||
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;
|
||||
for (const auto& stroke : m_strokes)
|
||||
rendererGroupIds.insert(stroke.groupId);
|
||||
@@ -180,6 +271,8 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) {
|
||||
}
|
||||
|
||||
void StrokeCanvasRenderer::paint(QCanvasPainter* painter) {
|
||||
updateBrushHole();
|
||||
|
||||
for (int id : m_pendingGroupRemovals)
|
||||
painter->removePathGroup(id);
|
||||
m_pendingGroupRemovals.clear();
|
||||
@@ -187,8 +280,8 @@ void StrokeCanvasRenderer::paint(QCanvasPainter* painter) {
|
||||
painter->clearRect(0, 0, width(), height());
|
||||
|
||||
for (const auto& stroke : m_strokes) {
|
||||
painter->setStrokeStyle(stroke.color);
|
||||
painter->setFillStyle(stroke.color);
|
||||
painter->setStrokeStyle(brushFor(stroke.color));
|
||||
painter->setFillStyle(brushFor(stroke.color));
|
||||
painter->setLineWidth(stroke.width);
|
||||
painter->setLineCap(QCanvasPainter::LineCap::Round);
|
||||
painter->setLineJoin(QCanvasPainter::LineJoin::Round);
|
||||
|
||||
@@ -1,26 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <QCanvasCustomBrush>
|
||||
#include <QCanvasPainterItemRenderer>
|
||||
#include <QHash>
|
||||
#include <QRectF>
|
||||
#include <QVector4D>
|
||||
#include <qcontainerfwd.h>
|
||||
#include "stroke.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
struct BrushEntry {
|
||||
QCanvasCustomBrush brush;
|
||||
qint64 holeToken = -1; // hole generation this brush was built with
|
||||
};
|
||||
|
||||
class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer {
|
||||
public:
|
||||
void synchronizeData(QCanvasPainterItem* item) override;
|
||||
void paint(QCanvasPainter* painter) override;
|
||||
|
||||
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;
|
||||
qreal m_penWidth = 4.0;
|
||||
bool m_hoverVisible = false;
|
||||
QPointF m_hoverPoint;
|
||||
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;
|
||||
Stroke m_currentStroke;
|
||||
QVector<int> m_pendingGroupRemovals;
|
||||
QHash<QRgb, BrushEntry> m_brushes;
|
||||
};
|
||||
|
||||
}; // namespace ZShell::internal
|
||||
|
||||
Reference in New Issue
Block a user