Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core_lib/core_lib.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
48 changes: 11 additions & 37 deletions core_lib/src/canvascursorpainter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -97,6 +70,7 @@ void CanvasCursorPainter::paintWidthCursor(QPainter& painter, const QRect& blitR
mDirtyRect = widthCircleBounds.toAlignedRect();
}


void CanvasCursorPainter::clearDirty()
{
mDirtyRect = QRect();
Expand Down
9 changes: 3 additions & 6 deletions core_lib/src/canvascursorpainter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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; }
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions core_lib/src/tool/penciltool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions core_lib/src/tool/pentool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions core_lib/src/tool/polylinetool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
79 changes: 79 additions & 0 deletions core_lib/src/tool/radialoffsettool.cpp
Original file line number Diff line number Diff line change
@@ -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 <QLineF>
#include <QDebug>
#include <QLineF>

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;
}
67 changes: 67 additions & 0 deletions core_lib/src/tool/radialoffsettool.h
Original file line number Diff line number Diff line change
@@ -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 <QObject>
#include <QPointF>

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
59 changes: 30 additions & 29 deletions core_lib/src/tool/smudgetool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ void SmudgeTool::pointerMoveEvent(PointerEvent* event)
return;
}

if (event->inputType() != mCurrentInputType) return;

Layer* layer = mEditor->layers()->currentLayer();
if (layer == nullptr) { return; }
Expand All @@ -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<LayerVector*>(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<LayerVector*>(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<LayerVector*>(layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
if (vectorImage == nullptr) { return; }
if (layer->type() == Layer::VECTOR)
{
VectorImage* vectorImage = static_cast<LayerVector*>(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();
}
}
}

Expand Down
Loading
Loading