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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user