diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro index a7248881c1..9e0e8df982 100644 --- a/core_lib/core_lib.pro +++ b/core_lib/core_lib.pro @@ -85,6 +85,7 @@ HEADERS += \ src/tool/penciltool.h \ src/tool/pentool.h \ src/tool/polylinetool.h \ + src/tool/radialoffsettool.h \ src/tool/selecttool.h \ src/tool/smudgetool.h \ src/tool/strokeinterpolator.h \ @@ -174,6 +175,7 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/tool/penciltool.cpp \ src/tool/pentool.cpp \ src/tool/polylinetool.cpp \ + src/tool/radialoffsettool.cpp \ src/tool/selecttool.cpp \ src/tool/smudgetool.cpp \ src/tool/strokeinterpolator.cpp \ diff --git a/core_lib/src/canvascursorpainter.cpp b/core_lib/src/canvascursorpainter.cpp index b3fa32bd5d..2a19ccb5d4 100644 --- a/core_lib/src/canvascursorpainter.cpp +++ b/core_lib/src/canvascursorpainter.cpp @@ -33,62 +33,35 @@ void CanvasCursorPainter::setupPen() void CanvasCursorPainter::paint(QPainter& painter, const QRect& blitRect) { - if (mOptions.isAdjusting || mOptions.showCursor) { - if (mOptions.useFeather) { - paintFeatherCursor(painter, blitRect, mOptions.widthRect, mOptions.featherRect); - } - paintWidthCursor(painter, blitRect, mOptions.widthRect); + if (mOptions.showCursor) { + paintWidthCursor(painter, blitRect, mOptions.circleRect); mIsDirty = true; } } -void CanvasCursorPainter::preparePainter(const CanvasCursorPainterOptions& painterOptions, const QTransform& viewTransform) +void CanvasCursorPainter::preparePainter(const CanvasCursorPainterOptions& painterOptions) { mOptions = painterOptions; - if (mOptions.isAdjusting || mOptions.showCursor) { - // Apply full transform to center of widthRect, but only apply scale to the rect as a whole - // Otherwise, view rotations will result in an incorrect rect size - QPointF newCenter = viewTransform.map(mOptions.widthRect.center()); - qreal scale = qSqrt(qPow(viewTransform.m11(), 2) + qPow(viewTransform.m21(), 2)); - mOptions.widthRect.setSize(mOptions.widthRect.size() * scale); - mOptions.widthRect.moveCenter(newCenter); - mOptions.featherRect.setSize(mOptions.featherRect.size() * scale); - mOptions.featherRect.moveCenter(newCenter); - } -} - -void CanvasCursorPainter::paintFeatherCursor(QPainter& painter, const QRect& blitRect, const QRectF& widthCircleBounds, const QRectF& featherCircleBounds) -{ - // When the circles are too close to each other, the rendering will appear dotted or almost - // invisible at certain zoom levels. - if (widthCircleBounds.width() - featherCircleBounds.width() <= 1) { - return; - } - - painter.save(); - - painter.setClipRect(blitRect); - painter.setPen(mCursorPen); - painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); - painter.drawEllipse(featherCircleBounds); - - painter.restore(); } void CanvasCursorPainter::paintWidthCursor(QPainter& painter, const QRect& blitRect, const QRectF& widthCircleBounds) { painter.save(); - painter.setClipRect(blitRect); + painter.setClipRect(painter.transform().inverted().mapRect(blitRect)); painter.setPen(mCursorPen); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); // Only draw the cross when the width is bigger than the cross itself - if (widthCircleBounds.width() > 8) { - const QPointF& pos = widthCircleBounds.center(); + if (widthCircleBounds.width() > 8 && mOptions.showCross) { + painter.save(); + + const QPointF& pos = painter.transform().mapRect(widthCircleBounds).center(); + painter.resetTransform(); painter.drawLine(QPointF(pos.x() - 2, pos.y()), QPointF(pos.x() + 2, pos.y())); painter.drawLine(QPointF(pos.x(), pos.y() - 2), QPointF(pos.x(), pos.y() + 2)); + painter.restore(); } painter.drawEllipse(widthCircleBounds); @@ -97,6 +70,7 @@ void CanvasCursorPainter::paintWidthCursor(QPainter& painter, const QRect& blitR mDirtyRect = widthCircleBounds.toAlignedRect(); } + void CanvasCursorPainter::clearDirty() { mDirtyRect = QRect(); diff --git a/core_lib/src/canvascursorpainter.h b/core_lib/src/canvascursorpainter.h index e0788a86d7..68181a5b33 100644 --- a/core_lib/src/canvascursorpainter.h +++ b/core_lib/src/canvascursorpainter.h @@ -23,11 +23,9 @@ class QPainter; struct CanvasCursorPainterOptions { - QRectF widthRect; - QRectF featherRect; - bool isAdjusting = false; - bool useFeather = false; + QRectF circleRect; bool showCursor = false; + bool showCross = false; }; class CanvasCursorPainter @@ -37,7 +35,7 @@ class CanvasCursorPainter CanvasCursorPainter(); void paint(QPainter& painter, const QRect& blitRect); - void preparePainter(const CanvasCursorPainterOptions& painterOptions, const QTransform& viewTransform); + void preparePainter(const CanvasCursorPainterOptions& painterOptions); const QRect dirtyRect() { return mDirtyRect; } bool isDirty() const { return mIsDirty; } @@ -49,7 +47,6 @@ class CanvasCursorPainter /// @brief precision circular cursor: used for drawing a cursor on the canvas. void paintWidthCursor(QPainter& painter, const QRect& blitRect, const QRectF& widthCircleBounds); - void paintFeatherCursor(QPainter& painter, const QRect& blitRect, const QRectF& widthCircleBounds, const QRectF& featherCircleBounds); CanvasCursorPainterOptions mOptions; QRect mDirtyRect; diff --git a/core_lib/src/tool/penciltool.cpp b/core_lib/src/tool/penciltool.cpp index 2588124fe7..db934acdaf 100644 --- a/core_lib/src/tool/penciltool.cpp +++ b/core_lib/src/tool/penciltool.cpp @@ -54,6 +54,7 @@ void PencilTool::loadSettings() properties.stabilizerLevel = settings.value("pencilLineStabilization", StabilizationLevel::STRONG).toInt(); properties.useAA = DISABLED; properties.useFillContour = false; + properties.useFeather = false; mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); } diff --git a/core_lib/src/tool/pentool.cpp b/core_lib/src/tool/pentool.cpp index dbb6a5f1ed..f508a94e4c 100644 --- a/core_lib/src/tool/pentool.cpp +++ b/core_lib/src/tool/pentool.cpp @@ -54,6 +54,7 @@ void PenTool::loadSettings() properties.preserveAlpha = OFF; properties.useAA = settings.value("penAA", true).toBool(); properties.stabilizerLevel = settings.value("penLineStabilization", StabilizationLevel::STRONG).toInt(); + properties.useFeather = false; mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); } diff --git a/core_lib/src/tool/polylinetool.cpp b/core_lib/src/tool/polylinetool.cpp index b18539d88a..63d3965304 100644 --- a/core_lib/src/tool/polylinetool.cpp +++ b/core_lib/src/tool/polylinetool.cpp @@ -53,6 +53,7 @@ void PolylineTool::loadSettings() properties.width = settings.value("polyLineWidth", 8.0).toDouble(); properties.feather = -1; + properties.useFeather = false; properties.pressure = false; properties.invisibility = OFF; properties.preserveAlpha = OFF; diff --git a/core_lib/src/tool/radialoffsettool.cpp b/core_lib/src/tool/radialoffsettool.cpp new file mode 100644 index 0000000000..abac216f72 --- /dev/null +++ b/core_lib/src/tool/radialoffsettool.cpp @@ -0,0 +1,79 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang +Copyright (C) 2025-2099 Oliver S. Larsen + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "radialoffsettool.h" + +#include "pointerevent.h" + +#include +#include +#include + +RadialOffsetTool::RadialOffsetTool(QObject* parent) : QObject(parent) +{ + +} + +RadialOffsetTool::~RadialOffsetTool() +{ +} + +void RadialOffsetTool::pointerEvent(PointerEvent* event) +{ + if (event->eventType() == PointerEvent::Press) { + startAdjusting(event); + } else if (event->eventType() == PointerEvent::Move) { + if (event->buttons() & Qt::LeftButton && mIsAdjusting) { + adjust(event); + } + } else if (event->eventType() == PointerEvent::Release && mIsAdjusting) { + stopAdjusting(); + } +} + +void RadialOffsetTool::adjust(PointerEvent* event) +{ + const qreal newValue = QLineF(mAdjustPoint, event->canvasPos()).length(); + + emit offsetChanged(newValue); +} + +bool RadialOffsetTool::startAdjusting(PointerEvent* event) +{ + const qreal rad = mOffset; + + QPointF directionDelta; + if (mAdjustPoint.isNull()) { + directionDelta = QPointF(-1, -1); // 45 deg back + } else { + directionDelta = -(event->canvasPos() - mAdjustPoint); + } + + QLineF line(event->canvasPos(), event->canvasPos() + directionDelta); + line.setLength(rad); + + mAdjustPoint = line.p2(); // Adjusted point on circle boundary + + mIsAdjusting = true; + return true; +} + +void RadialOffsetTool::stopAdjusting() +{ + mIsAdjusting = false; +} diff --git a/core_lib/src/tool/radialoffsettool.h b/core_lib/src/tool/radialoffsettool.h new file mode 100644 index 0000000000..e0c9f3477f --- /dev/null +++ b/core_lib/src/tool/radialoffsettool.h @@ -0,0 +1,67 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang +Copyright (C) 2025-2099 Oliver S. Larsen + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef RADIALOFFSETTOOL_H +#define RADIALOFFSETTOOL_H + +#include +#include + +class PointerEvent; + +/* + * A tool that can be used to make quick adjustments + * based on an offset vector from current pointer position + * + * Drag origin Cursor position + ● ● + | / + | / + | offset vector / + | / + ▼ ▼ + [Adjusted Point] —————————————> (Direction of drag) +*/ +class RadialOffsetTool : public QObject +{ + Q_OBJECT +public: + RadialOffsetTool(QObject* parent = nullptr); + ~RadialOffsetTool(); + + void setOffset(qreal offset) { mOffset = offset; } + void pointerEvent(PointerEvent* event); + + void stopAdjusting(); + bool isAdjusting() const { return mIsAdjusting; } + + const QPointF& offsetPoint() const { return mAdjustPoint; } + +signals: + void offsetChanged(qreal newSize); + +private: + + bool startAdjusting(PointerEvent* event); + void adjust(PointerEvent* event); + + bool mIsAdjusting = false; + qreal mOffset = 0.; + QPointF mAdjustPoint = QPointF(); +}; + +#endif // RADIALOFFSETTOOL_H diff --git a/core_lib/src/tool/smudgetool.cpp b/core_lib/src/tool/smudgetool.cpp index fbbb70b140..d1f2831aa9 100644 --- a/core_lib/src/tool/smudgetool.cpp +++ b/core_lib/src/tool/smudgetool.cpp @@ -198,7 +198,6 @@ void SmudgeTool::pointerMoveEvent(PointerEvent* event) return; } - if (event->inputType() != mCurrentInputType) return; Layer* layer = mEditor->layers()->currentLayer(); if (layer == nullptr) { return; } @@ -209,43 +208,45 @@ void SmudgeTool::pointerMoveEvent(PointerEvent* event) } auto selectMan = mEditor->select(); - if (event->buttons() & Qt::LeftButton) // the user is also pressing the mouse (dragging) { - { - if (layer->type() == Layer::BITMAP) - { - drawStroke(); - } - else //if (layer->type() == Layer::VECTOR) + if (event->inputType() == mCurrentInputType) { + if (event->buttons() & Qt::LeftButton) // the user is also pressing the mouse (dragging) { { - if (event->modifiers() != Qt::ShiftModifier) // (and the user doesn't press shift) + if (layer->type() == Layer::BITMAP) { - VectorImage* vectorImage = static_cast(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); - if (vectorImage == nullptr) { return; } - // transforms the selection + drawStroke(); + } + else //if (layer->type() == Layer::VECTOR) + { + if (event->modifiers() != Qt::ShiftModifier) // (and the user doesn't press shift) + { + VectorImage* vectorImage = static_cast(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); + if (vectorImage == nullptr) { return; } + // transforms the selection - BlitRect blit; + BlitRect blit; - // Use the previous dirty bound and extend it with the current dirty bound - // this ensures that we won't get painting artifacts - blit.extend(vectorImage->getBoundsOfTransformedCurves().toRect()); - selectMan->setSelectionTransform(QTransform().translate(offsetFromPressPos().x(), offsetFromPressPos().y())); - vectorImage->setSelectionTransformation(selectMan->selectionTransform()); - blit.extend(vectorImage->getBoundsOfTransformedCurves().toRect()); + // Use the previous dirty bound and extend it with the current dirty bound + // this ensures that we won't get painting artifacts + blit.extend(vectorImage->getBoundsOfTransformedCurves().toRect()); + selectMan->setSelectionTransform(QTransform().translate(offsetFromPressPos().x(), offsetFromPressPos().y())); + vectorImage->setSelectionTransformation(selectMan->selectionTransform()); + blit.extend(vectorImage->getBoundsOfTransformedCurves().toRect()); - // And now tell the widget to update the portion in local coordinates - mScribbleArea->update(mEditor->view()->mapCanvasToScreen(blit).toRect().adjusted(-1, -1, 1, 1)); + // And now tell the widget to update the portion in local coordinates + mScribbleArea->update(mEditor->view()->mapCanvasToScreen(blit).toRect().adjusted(-1, -1, 1, 1)); + } } } - } - else // the user is moving the mouse without pressing it - { - if (layer->type() == Layer::VECTOR) + else // the user is moving the mouse without pressing it { - VectorImage* vectorImage = static_cast(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); - if (vectorImage == nullptr) { return; } + if (layer->type() == Layer::VECTOR) + { + VectorImage* vectorImage = static_cast(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); + if (vectorImage == nullptr) { return; } - selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), selectMan->selectionTolerance())); - mScribbleArea->update(); + selectMan->setVertices(vectorImage->getVerticesCloseTo(getCurrentPoint(), selectMan->selectionTolerance())); + mScribbleArea->update(); + } } } diff --git a/core_lib/src/tool/stroketool.cpp b/core_lib/src/tool/stroketool.cpp index ee7b509907..8a9fcfd265 100644 --- a/core_lib/src/tool/stroketool.cpp +++ b/core_lib/src/tool/stroketool.cpp @@ -47,7 +47,6 @@ const qreal StrokeTool::WIDTH_MIN = 1.; const qreal StrokeTool::WIDTH_MAX = 200.; // ---- shared static variables ---- ( only one instance for all the tools ) -bool StrokeTool::msIsAdjusting = false; bool StrokeTool::mQuickSizingEnabled = false; StrokeTool::StrokeTool(QObject* parent) : BaseTool(parent) @@ -63,6 +62,22 @@ void StrokeTool::loadSettings() /// Given the way that we update preferences currently, this connection should not be removed /// when the tool is not active. connect(mEditor->preference(), &PreferenceManager::optionChanged, this, &StrokeTool::onPreferenceChanged); + + connect(&mWidthSizingTool, &RadialOffsetTool::offsetChanged, this, [=](qreal offset) { + mEditor->tools()->setWidth(qBound(WIDTH_MIN, offset * 2.0, WIDTH_MAX)); + }); + + connect(&mFeatherSizingTool, &RadialOffsetTool::offsetChanged, this, [=](qreal offset){ + const qreal inputMin = FEATHER_MIN; + const qreal inputMax = properties.width * 0.5; + const qreal outputMax = FEATHER_MAX; + const qreal outputMin = inputMin; + + // We map the feather value to a value between the min width and max width + const qreal mappedValue = MathUtils::map(offset, inputMin, inputMax, outputMax, outputMin); + + mEditor->tools()->setFeather(qBound(FEATHER_MIN, mappedValue, FEATHER_MAX)); + }); } bool StrokeTool::enteringThisTool() @@ -202,22 +217,49 @@ void StrokeTool::drawStroke() bool StrokeTool::handleQuickSizing(PointerEvent* event) { + if (!mQuickSizingEnabled) { return false; } + + if (!mQuickSizingProperties.contains(event->modifiers())) { + mWidthSizingTool.stopAdjusting(); + mFeatherSizingTool.stopAdjusting(); + return false; + } + + ToolPropertyType setting = mQuickSizingProperties[event->modifiers()]; if (event->eventType() == PointerEvent::Press) { - if (mQuickSizingEnabled) { - return startAdjusting(event->modifiers()); + switch (setting) { + case WIDTH: { + mWidthSizingTool.setOffset(properties.width * 0.5); + break; + } + case FEATHER: { + const qreal factor = 0.5; + const qreal cursorRad = properties.width * factor; + + // Pull feather handle closer to center as feather increases + const qreal featherWidthFactor = MathUtils::normalize(properties.feather, FEATHER_MIN, FEATHER_MAX); + const qreal offset = (cursorRad * featherWidthFactor); + mFeatherSizingTool.setOffset(offset); + break; + } + default: break; } - } else if (event->eventType() == PointerEvent::Move) { - if (event->buttons() & Qt::LeftButton && msIsAdjusting) { - adjustCursor(event->modifiers()); - return true; + } + + switch (setting) { + case WIDTH: { + mWidthSizingTool.pointerEvent(event); + break; } - } else if (event->eventType() == PointerEvent::Release) { - if (msIsAdjusting) { - stopAdjusting(); - return true; + case FEATHER: { + mFeatherSizingTool.pointerEvent(event); + break; } + default: break; } - return false; + + updateCanvasCursor(); + return true; } void StrokeTool::pointerPressEvent(PointerEvent*) @@ -253,125 +295,77 @@ bool StrokeTool::leaveEvent(QEvent*) return true; } -void StrokeTool::updateCanvasCursor() + +QRectF StrokeTool::cursorRect(ToolPropertyType settingType, const QPointF& point) { const qreal brushWidth = properties.width; const qreal brushFeather = properties.feather; - const QPointF& cursorPos = msIsAdjusting ? mAdjustPosition : getCurrentPoint(); + const QPointF& cursorPos = point; const qreal cursorRad = brushWidth * 0.5; - const QPointF& cursorOffset = QPointF(cursorPos.x() - cursorRad, cursorPos.y() - cursorRad); - - CanvasCursorPainterOptions options; - options.widthRect = QRectF(cursorOffset, QSizeF(brushWidth, brushWidth)); - - const qreal featherWidthFactor = MathUtils::normalize(brushFeather, 0.0, FEATHER_MAX); - options.featherRect = QRectF(options.widthRect.center().x() - (cursorRad * featherWidthFactor), - options.widthRect.center().y() - (cursorRad * featherWidthFactor), - brushWidth * featherWidthFactor, - brushWidth * featherWidthFactor); - options.showCursor = mCanvasCursorEnabled; - options.isAdjusting = msIsAdjusting && mQuickSizingEnabled; - options.useFeather = mPropertyEnabled[FEATHER]; - - mCanvasCursorPainter.preparePainter(options, mEditor->view()->getView()); - - const QRect& dirtyRect = mCanvasCursorPainter.dirtyRect(); - const QRect& updateRect = mEditor->view()->getView().mapRect(QRectF(cursorOffset, QSizeF(brushWidth, brushWidth))).toAlignedRect(); - - if (!msIsAdjusting && !mCanvasCursorEnabled) { - if (mCanvasCursorPainter.isDirty()) { - // Adjusted to account for some pixel bleeding outside the update rect - mScribbleArea->update(mCanvasCursorPainter.dirtyRect().adjusted(-2, -2, 2, 2)); - mCanvasCursorPainter.clearDirty(); - } - return; + const QPointF& widthCursorTopLeft = QPointF(cursorPos.x() - cursorRad, cursorPos.y() - cursorRad); + + const QRectF widthCircleRect = QRectF(widthCursorTopLeft, QSizeF(brushWidth, brushWidth)); + if (settingType == WIDTH) { + return widthCircleRect; + } else if (settingType == FEATHER) { + const qreal featherWidthFactor = MathUtils::normalize(brushFeather, FEATHER_MIN, FEATHER_MAX); + QRectF featherRect = QRectF(widthCircleRect.center().x() - (cursorRad * featherWidthFactor), + widthCircleRect.center().y() - (cursorRad * featherWidthFactor), + brushWidth * featherWidthFactor, + brushWidth * featherWidthFactor); + + // Adjust the feather rect so it doesn't colide with the width rect; + // as this cancels out both circles when painted + return featherRect.adjusted(2, 2, -2, -2); } - // Adjusted to account for some pixel bleeding outside the update rect - mScribbleArea->update(updateRect.united(dirtyRect).adjusted(-2, -2, 2, 2)); + return QRectF(); } -bool StrokeTool::startAdjusting(Qt::KeyboardModifiers modifiers) + +void StrokeTool::updateCanvasCursor() { - if (!mQuickSizingProperties.contains(modifiers)) - { - return false; + CanvasCursorPainterOptions widthOptions; + widthOptions.circleRect = cursorRect(WIDTH, mWidthSizingTool.isAdjusting() ? mWidthSizingTool.offsetPoint() : getCurrentPoint()); + widthOptions.showCursor = mCanvasCursorEnabled; + widthOptions.showCross = true; + + CanvasCursorPainterOptions featherOptions; + featherOptions.circleRect = cursorRect(FEATHER, mFeatherSizingTool.isAdjusting() ? mFeatherSizingTool.offsetPoint() : getCurrentPoint()); + featherOptions.showCursor = mCanvasCursorEnabled; + featherOptions.showCross = false; + + if (mFeatherSizingTool.isAdjusting()) { + widthOptions.circleRect = cursorRect(WIDTH, mFeatherSizingTool.isAdjusting() ? mFeatherSizingTool.offsetPoint() : getCurrentPoint()); + } else if (mWidthSizingTool.isAdjusting()) { + featherOptions.circleRect = cursorRect(FEATHER, mWidthSizingTool.isAdjusting() ? mWidthSizingTool.offsetPoint() : getCurrentPoint()); } - const QPointF& currentPressPoint = getCurrentPressPoint(); - const QPointF& currentPoint = getCurrentPoint(); - auto propertyType = mQuickSizingProperties.value(modifiers); - switch (propertyType) { - case WIDTH: { - const qreal factor = 0.5; - const qreal rad = properties.width * factor; - const qreal distance = QLineF(currentPressPoint - QPointF(rad, rad), currentPoint).length(); - mAdjustPosition = currentPressPoint - QPointF(distance * factor, distance * factor); - break; - } - case FEATHER: { - const qreal factor = 0.5; - const qreal cursorRad = properties.width * factor; - const qreal featherWidthFactor = MathUtils::normalize(properties.feather, 0.0, FEATHER_MAX); - const qreal offset = (cursorRad * featherWidthFactor) * factor; - const qreal distance = QLineF(currentPressPoint - QPointF(offset, offset), currentPoint).length(); - mAdjustPosition = currentPressPoint - QPointF(distance, distance); - break; - } - default: - Q_UNREACHABLE(); - qWarning() << "Unhandled quick sizing property for tool" << typeName(); - return false; - } + mWidthCursorPainter.preparePainter(widthOptions); + mFeatherCursorPainter.preparePainter(featherOptions); - msIsAdjusting = true; - updateCanvasCursor(); - return true; -} + const QRect& dirtyRect = mWidthCursorPainter.dirtyRect(); -void StrokeTool::stopAdjusting() -{ - msIsAdjusting = false; - mAdjustPosition = QPointF(); + // We know that the width rect is bigger than the feather rect + // so we don't need to change this + const QRect& updateRect = widthOptions.circleRect.toAlignedRect(); - updateCanvasCursor(); + // Adjusted to account for some pixel bleeding outside the update rect + mScribbleArea->update(mEditor->view()->getView().mapRect(updateRect.united(dirtyRect).adjusted(-2, -2, 2, 2))); + mWidthCursorPainter.clearDirty(); } -void StrokeTool::adjustCursor(Qt::KeyboardModifiers modifiers) +void StrokeTool::paint(QPainter& painter, const QRect& blitRect) { - switch (mQuickSizingProperties.value(modifiers)) - { - case WIDTH: { - // The adjusted position is based on the radius of the circle, so in order to - // map it back to its original value, we can multiply by the factor we divided with - const qreal newValue = QLineF(mAdjustPosition, getCurrentPoint()).length() * 2.0; + painter.save(); + painter.setTransform(mEditor->view()->getView()); - mEditor->tools()->setWidth(qBound(WIDTH_MIN, newValue, WIDTH_MAX)); - break; + if (properties.useFeather) { + mFeatherCursorPainter.paint(painter, blitRect); } - case FEATHER: { - // The radius of the width is the max value we can get - const qreal inputMin = 0.0; - const qreal inputMax = properties.width * 0.5; - const qreal distance = QLineF(mAdjustPosition, getCurrentPoint()).length(); - const qreal outputMax = FEATHER_MAX; - const qreal outputMin = 0.0; - // We flip min and max here in order to get the inverted value for the UI - const qreal mappedValue = MathUtils::map(distance, inputMin, inputMax, outputMax, outputMin); + mWidthCursorPainter.paint(painter, blitRect); - mEditor->tools()->setFeather(qBound(FEATHER_MIN, mappedValue, FEATHER_MAX)); - break; - } - default: - Q_UNREACHABLE(); - qWarning() << "Unhandled quick sizing property for tool" << typeName(); - } - updateCanvasCursor(); -} - -void StrokeTool::paint(QPainter& painter, const QRect& blitRect) -{ - mCanvasCursorPainter.paint(painter, blitRect); + painter.restore(); } diff --git a/core_lib/src/tool/stroketool.h b/core_lib/src/tool/stroketool.h index 7b1b2fbe90..bc460db2de 100644 --- a/core_lib/src/tool/stroketool.h +++ b/core_lib/src/tool/stroketool.h @@ -25,6 +25,7 @@ GNU General Public License for more details. #include "undoredomanager.h" #include "canvascursorpainter.h" +#include "radialoffsettool.h" #include #include @@ -52,7 +53,7 @@ class StrokeTool : public BaseTool static const qreal WIDTH_MAX; void loadSettings() override; - bool isActive() const override { return mInterpolator.isActive(); }; + bool isActive() const override { return mInterpolator.isActive(); } bool keyPressEvent(QKeyEvent* event) override; void pointerPressEvent(PointerEvent* event) override; @@ -77,13 +78,9 @@ public slots: QPointF getLastPixel() const; QPointF getLastPoint() const; - // dynamic cursor adjustment - virtual bool startAdjusting(Qt::KeyboardModifiers modifiers); - virtual void stopAdjusting(); - virtual void adjustCursor(Qt::KeyboardModifiers modifiers); + QRectF cursorRect(ToolPropertyType settingType, const QPointF& point); static bool mQuickSizingEnabled; - static bool msIsAdjusting; QHash mQuickSizingProperties; bool mFirstDraw = false; @@ -108,11 +105,16 @@ public slots: QPointF mAdjustPosition; - CanvasCursorPainter mCanvasCursorPainter; - StrokeInterpolator mInterpolator; const UndoSaveState* mUndoSaveState = nullptr; + +private: + CanvasCursorPainter mWidthCursorPainter; + CanvasCursorPainter mFeatherCursorPainter; + + RadialOffsetTool mWidthSizingTool; + RadialOffsetTool mFeatherSizingTool; }; #endif // STROKETOOL_H