diff --git a/Components/StateLayer.qml b/Components/StateLayer.qml index a3d29b8..efc985b 100644 --- a/Components/StateLayer.qml +++ b/Components/StateLayer.qml @@ -51,6 +51,7 @@ MouseArea { anchors.fill: parent cursorShape: !enabled ? undefined : Qt.PointingHandCursor + enabled: parent.enabled hoverEnabled: true Behavior on stateOpacity { diff --git a/Drawers/Drawing.qml b/Drawers/Drawing.qml index 399b11f..25447aa 100644 --- a/Drawers/Drawing.qml +++ b/Drawers/Drawing.qml @@ -1,36 +1,9 @@ import QtQuick +import ZShell.Internal -Canvas { +StrokeCanvas { id: root - property color penColor: "white" - property real penWidth: 4 - property var points: [] - - function clear(): void { - var ctx = getContext('2d'); - root.points = []; - ctx.reset(); - root.requestPaint(); - } - - renderStrategy: Canvas.Cooperative - - onPaint: { - if (points.length < 2) - return; - var ctx = root.getContext('2d'); - ctx.save(); - ctx.lineWidth = root.penWidth; - ctx.strokeStyle = root.penColor; - ctx.lineJoin = "round"; - ctx.lineCap = "round"; - ctx.beginPath(); - ctx.moveTo(points[0].x, points[0].y); - for (var i = 1; i < points.length; i++) - ctx.lineTo(points[i].x, points[i].y); - ctx.stroke(); - points = points.slice(points.length - 2); - ctx.restore(); - } + penColor: "white" + penWidth: 4 } diff --git a/Drawers/DrawingInput.qml b/Drawers/DrawingInput.qml deleted file mode 100644 index ffa37b2..0000000 --- a/Drawers/DrawingInput.qml +++ /dev/null @@ -1,60 +0,0 @@ -import Quickshell -import QtQuick -import qs.Components -import qs.Config - -CustomMouseArea { - id: root - - required property var bar - required property Drawing drawing - required property Panels panels - required property var popout - required property PersistentProperties visibilities - - function inLeftPanel(panel: Item, x: real, y: real): bool { - return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y); - } - - function withinPanelHeight(panel: Item, x: real, y: real): bool { - const panelY = panel.y + bar.implicitHeight; - return y >= panelY && y <= panelY + panel.height; - } - - acceptedButtons: Qt.LeftButton | Qt.RightButton - enabled: z > 0 - hoverEnabled: true - visible: root.visibilities.isDrawing - - onPositionChanged: event => { - const x = event.x; - const y = event.y; - if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { - root.drawing.points.push(Qt.point(x, y)); - root.drawing.requestPaint(); - return; - } - - if (!(event.buttons & Qt.LeftButton) && root.inLeftPanel(root.popout, x, y)) { - root.z = -2; - root.panels.drawing.expanded = true; - } - } - onPressed: event => { - const x = event.x; - const y = event.y; - - if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { - root.panels.drawing.expanded = false; - root.drawing.points.push(Qt.point(x, y)); - root.drawing.requestPaint(); - return; - } - - if (event.buttons & Qt.RightButton) - root.drawing.clear(); - } - onReleased: { - root.drawing.points = []; - } -} diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index 62edf62..b2efe92 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -12,7 +12,6 @@ Item { required property real borderThickness property bool dashboardShortcutActive required property Drawing drawing - required property DrawingInput input property bool osdShortcutActive required property Panels panels required property BarPopouts.Wrapper popouts @@ -60,6 +59,7 @@ Item { cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined dragThreshold: 0 + enabled: !root.visibilities.isDrawing grabPermissions: PointerHandler.CanTakeOverFromHandlersOfSameType | PointerHandler.ApprovesTakeOverByAnything maximumPointCount: 1 minimumPointCount: 1 @@ -117,6 +117,40 @@ Item { } } + PointHandler { + id: drawingHandler + + property bool setInitialPoint: false + + enabled: root.visibilities.isDrawing + + onActiveChanged: { + console.log(active); + if (!active) { + setInitialPoint = false; + root.drawing.endStroke(); + } else { + root.panels.drawing.expanded = false; + } + } + onPointChanged: { + const x = point.position.x; + const y = point.position.y; + const origX = point.pressPosition.x; + const origY = point.pressPosition.y; + if (x === 0 && y === 0 && origX === 0 && origY === 0) + return; + + if (!setInitialPoint) { + setInitialPoint = true; + root.drawing.beginStroke(origX, origY); + return; + } + + root.drawing.appendPoint(x, y); + } + } + HoverHandler { id: hoverHandler @@ -139,9 +173,10 @@ Item { const x = point.position.x; const y = point.position.y; - if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) { - root.input.z = 2; - root.panels.drawing.expanded = false; + if (root.visibilities.isDrawing) { + if (root.inLeftPanel(root.panels.drawing, x, y)) + root.panels.drawing.expanded = true; + return; } if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight) diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index 7c980e9..d76e533 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -26,7 +26,7 @@ Item { readonly property alias dashboardWrapper: dashboardWrapper readonly property alias dock: dock readonly property alias drawing: drawing - required property Canvas drawingItem + required property var drawingItem readonly property alias launcher: launcher readonly property alias notifications: notifications readonly property alias osd: osd diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index c7c76ee..4b28e86 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -195,6 +195,8 @@ CustomWindow { } Item { + id: surface + anchors.fill: parent layer.enabled: true opacity: root.surfaceColor.a @@ -364,30 +366,13 @@ CustomWindow { active: visibilities.isDrawing anchors.fill: parent + z: 2 sourceComponent: Drawing { id: drawing } } - Loader { - id: inputLoader - - active: visibilities.isDrawing - anchors.fill: parent - z: 2 - - sourceComponent: DrawingInput { - id: input - - bar: bar - drawing: drawingLoader.item - panels: panels - popout: panels.drawing - visibilities: visibilities - } - } - Interactions { id: interactions @@ -396,7 +381,6 @@ CustomWindow { borderThickness: root.borderLayoutThickness drawing: drawingLoader.item enabled: true - input: inputLoader.item panels: panels popouts: panels.popouts screen: root.screen @@ -451,6 +435,7 @@ CustomWindow { anchors.left: parent.left anchors.right: parent.right + enabled: !visibilities.isDrawing fullscreen: root.hasFullscreen popouts: panels.popouts popoutsWrapper: panels.popoutsWrapper diff --git a/Modules/Drawing/Content.qml b/Modules/Drawing/Content.qml index 98c4caf..71b9acd 100644 --- a/Modules/Drawing/Content.qml +++ b/Modules/Drawing/Content.qml @@ -9,7 +9,7 @@ Item { id: root readonly property var colors: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"] - required property Canvas drawing + required property var drawing required property var visibilities function syncFromPenColor() { diff --git a/Modules/Drawing/Wrapper.qml b/Modules/Drawing/Wrapper.qml index 6376d30..d8dcae9 100644 --- a/Modules/Drawing/Wrapper.qml +++ b/Modules/Drawing/Wrapper.qml @@ -10,7 +10,7 @@ import qs.Daemons Item { id: root - required property Canvas drawing + required property var drawing property bool expanded: true property real offsetScale: shouldBeActive ? 0 : 1 required property ShellScreen screen diff --git a/Plugins/ZShell/CMakeLists.txt b/Plugins/ZShell/CMakeLists.txt index 88c1263..2457613 100644 --- a/Plugins/ZShell/CMakeLists.txt +++ b/Plugins/ZShell/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(Qt6 REQUIRED COMPONENTS ShaderTools Core Qml Gui Quick Concurrent Sql Network DBus) +find_package(Qt6 REQUIRED COMPONENTS ShaderTools Core Qml Gui Quick Concurrent Sql Network DBus CanvasPainter) find_package(PkgConfig REQUIRED) find_library(SENSORS_LIBRARY NAMES sensors REQUIRED) find_path(SENSORS_INCLUDE_DIR NAMES sensors/sensors.h REQUIRED) diff --git a/Plugins/ZShell/Internal/CMakeLists.txt b/Plugins/ZShell/Internal/CMakeLists.txt index 94b1c64..0726068 100644 --- a/Plugins/ZShell/Internal/CMakeLists.txt +++ b/Plugins/ZShell/Internal/CMakeLists.txt @@ -12,6 +12,8 @@ qml_module(ZShell-internal lidwatcher.hpp lidwatcher.cpp visualizerbars.hpp visualizerbars.cpp linearindicatormanager.hpp linearindicatormanager.cpp + strokecanvasitem.hpp strokecanvasitem.cpp + strokecanvasrenderer.hpp strokecanvasrenderer.cpp LIBRARIES Qt::Gui Qt::Quick @@ -19,4 +21,5 @@ qml_module(ZShell-internal Qt::Core Qt::Network Qt::DBus + Qt::CanvasPainter ) diff --git a/Plugins/ZShell/Internal/stroke.hpp b/Plugins/ZShell/Internal/stroke.hpp new file mode 100644 index 0000000..baad605 --- /dev/null +++ b/Plugins/ZShell/Internal/stroke.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace ZShell::internal { + +struct Stroke { + QVector points; + QColor color; + float width; +}; + +}; diff --git a/Plugins/ZShell/Internal/strokecanvasitem.cpp b/Plugins/ZShell/Internal/strokecanvasitem.cpp new file mode 100644 index 0000000..a8854a4 --- /dev/null +++ b/Plugins/ZShell/Internal/strokecanvasitem.cpp @@ -0,0 +1,68 @@ +#include "strokecanvasitem.hpp" +#include "strokecanvasrenderer.hpp" +#include + +namespace ZShell::internal { + +StrokeCanvasItem::StrokeCanvasItem(QQuickItem *parent) : QCanvasPainterItem(parent) { + setFillColor(Qt::transparent); + setAlphaBlending(true); +} + +QCanvasPainterItemRenderer *StrokeCanvasItem::createItemRenderer() const { + return new StrokeCanvasRenderer; +} + +void StrokeCanvasItem::setPenColor(const QColor &color) { + if (m_penColor == color) + return; + + m_penColor = color; + update(); + + emit penColorChanged(); +} + +void StrokeCanvasItem::setPenWidth(float width) { + if (qFuzzyCompare(m_penWidth, width)) + return; + + m_penWidth = width; + update(); + + emit penWidthChanged(); +} + +void StrokeCanvasItem::beginStroke(qreal x, qreal y) { + m_currentStroke.points.clear(); + m_currentStroke.points.append({x, y}); + + m_currentStroke.color = m_penColor; + m_currentStroke.width = m_penWidth; + + update(); +} + +void StrokeCanvasItem::appendPoint(qreal x, qreal y) { + m_currentStroke.points.append({x, y}); + update(); +} + +void StrokeCanvasItem::endStroke() { + if (m_currentStroke.points.isEmpty()) + return; + + m_strokes.append(m_currentStroke); + m_currentStroke.points.clear(); + + update(); +} + +void StrokeCanvasItem::clear() { + m_strokes.clear(); + m_currentStroke.points.clear(); + + update(); +} + +}; diff --git a/Plugins/ZShell/Internal/strokecanvasitem.hpp b/Plugins/ZShell/Internal/strokecanvasitem.hpp new file mode 100644 index 0000000..3caeefa --- /dev/null +++ b/Plugins/ZShell/Internal/strokecanvasitem.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "stroke.hpp" + +namespace ZShell::internal { + +class StrokeCanvasRenderer; + +class StrokeCanvasItem : public QCanvasPainterItem { +Q_OBJECT + +QML_NAMED_ELEMENT(StrokeCanvas) + +Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor NOTIFY penColorChanged) +Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged) + +public: +explicit StrokeCanvasItem(QQuickItem *parent = nullptr); + +[[nodiscard]] QColor penColor() const { + return m_penColor; +} +[[nodiscard]] float penWidth() const { + return m_penWidth; +} + +void setPenColor(const QColor &color); +void setPenWidth(float width); + +Q_INVOKABLE void clear(); + +Q_INVOKABLE void beginStroke(qreal x, qreal y); +Q_INVOKABLE void appendPoint(qreal x, qreal y); +Q_INVOKABLE void endStroke(); + +signals: +void penColorChanged(); +void penWidthChanged(); + +protected: +[[nodiscard]] QCanvasPainterItemRenderer *createItemRenderer() const override; + +private: +friend class StrokeCanvasRenderer; + +QColor m_penColor = Qt::white; +float m_penWidth = 4.f; + +QVector m_strokes; +Stroke m_currentStroke; +}; + +}; diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp new file mode 100644 index 0000000..ae2e4a5 --- /dev/null +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp @@ -0,0 +1,61 @@ +#include "strokecanvasrenderer.hpp" +#include "strokecanvasitem.hpp" + +namespace ZShell::internal { + +static void drawStroke( + QCanvasPainter *painter, + const QVector &points, + const QColor &color, + float width) { + + if (points.isEmpty()) + return; + + painter->setStrokeStyle(color); + painter->setFillStyle(color); + painter->setLineWidth(width); + painter->setLineCap(QCanvasPainter::LineCap::Round); + painter->setLineJoin(QCanvasPainter::LineJoin::Round); + + if (points.size() == 1) { + painter->beginPath(); + painter->circle(points.front(), width * 0.5f); + painter->fill(); + return; + } + + painter->beginPath(); + painter->moveTo(points[0]); + + for (qsizetype i = 1; i < points.size(); ++i) + painter->lineTo(points[i]); + + painter->stroke(); +} + +void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { + auto *canvas = static_cast(item); + + m_penColor = canvas->m_penColor; + m_penWidth = canvas->m_penWidth; + + m_strokes = canvas->m_strokes; + m_currentStroke = canvas->m_currentStroke; +} + +void StrokeCanvasRenderer::paint(QCanvasPainter *painter) { + painter->clearRect(0, 0, width(), height()); + + for (const auto &stroke : m_strokes) + drawStroke(painter, stroke.points, stroke.color, stroke.width); + + drawStroke( + painter, + m_currentStroke.points, + m_currentStroke.color, + m_currentStroke.width + ); +} + +}; diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.hpp b/Plugins/ZShell/Internal/strokecanvasrenderer.hpp new file mode 100644 index 0000000..3a53139 --- /dev/null +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "stroke.hpp" + +namespace ZShell::internal { + +class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer { + +public: +void synchronizeData(QCanvasPainterItem *item) override; +void paint(QCanvasPainter *painter) override; + +private: +QColor m_penColor; +float m_penWidth = 4.f; + +QVector m_strokes; +Stroke m_currentStroke; + +}; + +};