Skip to content

Commit

Permalink
Overhaul canvas cursor and quick cursor implementation (#1806)
Browse files Browse the repository at this point in the history
* Implement width cursor

* Make feather cursor adjust properly

* Invert feather values

* Fix circle rendering becoming near invisible when width and feather are too close

* Cleanup cursor logic in strokeTool

* Remove tolerance quick cursor shortcut

As it currently is, it does not make sense to keep it as a cursor modification as it has no relation to the cursor.

* Don't show feather circle when it's fixed size

* Simplify cursor paint methods

* Fix CI Qt not compiling

* Add missing license to CanvasCursorPainter

* Review changes

* Fix build failure on old MSVC versions

* Review change: move quick cursor logic into stroke tool

* Remove redundant neutral multiplication

* Fix cursor lingers on canvas

* Migrate canvas cursor setting from dotted to canvas cursor

* Fix cursor would disappear if outside main window

* Fix view updates would send signals to all stroke tools

* Remove useFeather

* Keep internal config file key for canvas cursor setting

* Fix canvas cursor artifacts when leaving ScribbleArea

* Apply suggestion to return either current tool event or widget event

* Fix tool event handling

* Bucket tool: remove quick sizing

---------

Co-authored-by: Jakob Gahde <[email protected]>
  • Loading branch information
MrStevns and J5lx authored Mar 7, 2024
1 parent ab2063f commit 758a1a1
Show file tree
Hide file tree
Showing 35 changed files with 674 additions and 405 deletions.
10 changes: 5 additions & 5 deletions app/src/generalpage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ GeneralPage::GeneralPage() : ui(new Ui::GeneralPage)
connect(ui->antialiasingBox, &QCheckBox::stateChanged, this, &GeneralPage::antiAliasCheckboxStateChanged);
connect(ui->curveSmoothingLevel, &QSlider::valueChanged, this, &GeneralPage::curveSmoothingChanged);
connect(ui->highResBox, &QCheckBox::stateChanged, this, &GeneralPage::highResCheckboxStateChanged);
connect(ui->dottedCursorBox, &QCheckBox::stateChanged, this, &GeneralPage::dottedCursorCheckboxStateChanged);
connect(ui->canvasCursorBox, &QCheckBox::stateChanged, this, &GeneralPage::canvasCursorCheckboxStateChanged);
connect(ui->gridSizeInputW, spinValueChanged, this, &GeneralPage::gridWidthChanged);
connect(ui->gridSizeInputH, spinValueChanged, this, &GeneralPage::gridHeightChanged);
connect(ui->actionSafeCheckBox, &QCheckBox::stateChanged, this, &GeneralPage::actionSafeCheckBoxStateChanged);
Expand Down Expand Up @@ -141,8 +141,8 @@ void GeneralPage::updateValues()
ui->toolCursorsBox->setChecked(mManager->isOn(SETTING::TOOL_CURSOR));
QSignalBlocker b5(ui->antialiasingBox);
ui->antialiasingBox->setChecked(mManager->isOn(SETTING::ANTIALIAS));
QSignalBlocker b6(ui->dottedCursorBox);
ui->dottedCursorBox->setChecked(mManager->isOn(SETTING::DOTTED_CURSOR));
QSignalBlocker b6(ui->canvasCursorBox);
ui->canvasCursorBox->setChecked(mManager->isOn(SETTING::CANVAS_CURSOR));
QSignalBlocker b7(ui->gridSizeInputW);
ui->gridSizeInputW->setValue(mManager->getInt(SETTING::GRID_SIZE_W));
QSignalBlocker b11(ui->gridSizeInputH);
Expand Down Expand Up @@ -233,9 +233,9 @@ void GeneralPage::toolCursorsCheckboxStateChanged(int b)
mManager->set(SETTING::TOOL_CURSOR, b != Qt::Unchecked);
}

void GeneralPage::dottedCursorCheckboxStateChanged(int b)
void GeneralPage::canvasCursorCheckboxStateChanged(int b)
{
mManager->set(SETTING::DOTTED_CURSOR, b != Qt::Unchecked);
mManager->set(SETTING::CANVAS_CURSOR, b != Qt::Unchecked);
}

void GeneralPage::gridWidthChanged(int value)
Expand Down
2 changes: 1 addition & 1 deletion app/src/generalpage.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private slots:
void shadowsCheckboxStateChanged(int b);
void antiAliasCheckboxStateChanged(int b);
void toolCursorsCheckboxStateChanged(int b);
void dottedCursorCheckboxStateChanged(int b);
void canvasCursorCheckboxStateChanged(int b);
void highResCheckboxStateChanged(int b);
void gridCheckBoxStateChanged(int b);
void curveSmoothingChanged(int value);
Expand Down
1 change: 0 additions & 1 deletion app/src/mainwindow2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,6 @@ void MainWindow2::preferences()
{
clearKeyboardShortcuts();
setupKeyboardShortcuts();
ui->scribbleArea->updateCanvasCursor();
mPrefDialog = nullptr;
});

Expand Down
5 changes: 3 additions & 2 deletions app/src/tooloptionwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ GNU General Public License for more details.
#include "util.h"
#include "layer.h"
#include "layermanager.h"
#include "stroketool.h"
#include "toolmanager.h"

ToolOptionWidget::ToolOptionWidget(QWidget* parent) : BaseDockWidget(parent)
Expand All @@ -53,11 +54,11 @@ void ToolOptionWidget::initUI()

QSettings settings(PENCIL2D, PENCIL2D);

ui->sizeSlider->init(tr("Width"), SpinSlider::EXPONENT, SpinSlider::INTEGER, 1, 200);
ui->sizeSlider->init(tr("Width"), SpinSlider::EXPONENT, SpinSlider::INTEGER, StrokeTool::WIDTH_MIN, StrokeTool::WIDTH_MAX);
ui->sizeSlider->setValue(settings.value("brushWidth", "3").toDouble());
ui->brushSpinBox->setValue(settings.value("brushWidth", "3").toDouble());

ui->featherSlider->init(tr("Feather"), SpinSlider::LOG, SpinSlider::INTEGER, 1, 99);
ui->featherSlider->init(tr("Feather"), SpinSlider::LOG, SpinSlider::INTEGER, StrokeTool::FEATHER_MIN, StrokeTool::FEATHER_MAX);
ui->featherSlider->setValue(settings.value("brushFeather", "5").toDouble());
ui->featherSpinBox->setValue(settings.value("brushFeather", "5").toDouble());
}
Expand Down
6 changes: 3 additions & 3 deletions app/ui/generalpage.ui
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="dottedCursorBox">
<widget class="QCheckBox" name="canvasCursorBox">
<property name="text">
<string>Dotted Cursor</string>
<string>Canvas Cursor</string>
</property>
</widget>
</item>
Expand Down Expand Up @@ -515,7 +515,7 @@
<tabstop>windowOpacityLevel</tabstop>
<tabstop>shadowsBox</tabstop>
<tabstop>toolCursorsBox</tabstop>
<tabstop>dottedCursorBox</tabstop>
<tabstop>canvasCursorBox</tabstop>
<tabstop>checkerBackgroundButton</tabstop>
<tabstop>whiteBackgroundButton</tabstop>
<tabstop>greyBackgroundButton</tabstop>
Expand Down
2 changes: 2 additions & 0 deletions core_lib/core_lib.pro
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ INCLUDEPATH += src \
PRECOMPILED_HEADER = src/corelib-pch.h

HEADERS += \
src/canvascursorpainter.h \
src/corelib-pch.h \
src/graphics/bitmap/bitmapbucket.h \
src/graphics/bitmap/bitmapimage.h \
Expand Down Expand Up @@ -115,6 +116,7 @@ HEADERS += \


SOURCES += src/graphics/bitmap/bitmapimage.cpp \
src/canvascursorpainter.cpp \
src/graphics/bitmap/bitmapbucket.cpp \
src/graphics/bitmap/tile.cpp \
src/graphics/bitmap/tiledbuffer.cpp \
Expand Down
97 changes: 97 additions & 0 deletions core_lib/src/canvascursorpainter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
Pencil2D - Traditional Animation Software
Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
Copyright (C) 2012-2020 Matthew Chiawen Chang
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 "canvascursorpainter.h"

#include <QPainter>

CanvasCursorPainter::CanvasCursorPainter()
{
setupPen();
}

void CanvasCursorPainter::setupPen()
{
mCursorPen = QPen(Qt::gray);
mCursorPen.setWidthF(1);
mCursorPen.setCosmetic(true);
}

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);
mIsDirty = true;
}
}

void CanvasCursorPainter::preparePainter(const CanvasCursorPainterOptions& painterOptions, const QTransform& viewTransform)
{
mOptions = painterOptions;
if (mOptions.isAdjusting || mOptions.showCursor) {
mOptions.widthRect = viewTransform.mapRect(mOptions.widthRect);
mOptions.featherRect = viewTransform.mapRect(mOptions.featherRect);
}
}

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.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();
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.drawEllipse(widthCircleBounds);
painter.restore();

mDirtyRect = widthCircleBounds.toAlignedRect();
}

void CanvasCursorPainter::clearDirty()
{
mDirtyRect = QRect();
mIsDirty = false;
}
61 changes: 61 additions & 0 deletions core_lib/src/canvascursorpainter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Pencil2D - Traditional Animation Software
Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
Copyright (C) 2012-2020 Matthew Chiawen Chang
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 CANVASCURSORPAINTER_H
#define CANVASCURSORPAINTER_H

#include <QPen>

class QPainter;

struct CanvasCursorPainterOptions
{
QRectF widthRect;
QRectF featherRect;
bool isAdjusting;
bool useFeather = false;
bool showCursor = false;
};

class CanvasCursorPainter
{

public:
CanvasCursorPainter();
void paint(QPainter& painter, const QRect& blitRect);

void preparePainter(const CanvasCursorPainterOptions& painterOptions, const QTransform& viewTransform);

const QRect dirtyRect() { return mDirtyRect; }
bool isDirty() const { return mIsDirty; }
void clearDirty();

private:

void setupPen();

/// @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;
bool mIsDirty = false;

QPen mCursorPen;
};

#endif // CANVASCURSORPAINTER_H
16 changes: 7 additions & 9 deletions core_lib/src/graphics/bitmap/tiledbuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ Tile* TiledBuffer::getTileFromIndex(const TileIndex& tileIndex)
return selectedTile;
}

void TiledBuffer::drawBrush(const QPointF& point, int brushWidth, int brushCursorWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing) {
void TiledBuffer::drawBrush(const QPointF& point, int brushWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing) {
const QRectF brushRect(point.x() - 0.5 * brushWidth, point.y() - 0.5 * brushWidth, brushWidth, brushWidth);
const float tileSize = UNIFORM_TILE_SIZE;
const int width = qMax(brushCursorWidth,brushWidth);

// Gather the number of tiles that fits the size of the brush width
const int xLeft = qFloor((qFloor(point.x() - width)) / tileSize);
const int xRight = qFloor((qFloor(point.x() + width)) / tileSize);
const int yTop = qFloor(qFloor(point.y() - width) / tileSize);
const int yBottom = qFloor(qFloor(point.y() + width) / tileSize);
const int xLeft = qFloor((qFloor(point.x() - brushWidth)) / tileSize);
const int xRight = qFloor((qFloor(point.x() + brushWidth)) / tileSize);
const int yTop = qFloor(qFloor(point.y() - brushWidth) / tileSize);
const int yBottom = qFloor(qFloor(point.y() + brushWidth) / tileSize);

for (int tileY = yTop; tileY <= yBottom; tileY++) {
for (int tileX = xLeft; tileX <= xRight; tileX++) {
Expand Down Expand Up @@ -108,11 +107,10 @@ void TiledBuffer::drawImage(const QImage& image, const QRect& imageBounds, QPain
}


void TiledBuffer::drawPath(QPainterPath path, int cursorWidth, QPen pen, QBrush brush,
void TiledBuffer::drawPath(QPainterPath path, QPen pen, QBrush brush,
QPainter::CompositionMode cm, bool antialiasing)
{
const int pathWidth = pen.width();
const int width = (qMax(pathWidth,cursorWidth) + 1);
const int width = pen.width();;
const float tileSize = UNIFORM_TILE_SIZE;
const QRectF pathRect = path.boundingRect();

Expand Down
4 changes: 2 additions & 2 deletions core_lib/src/graphics/bitmap/tiledbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ class TiledBuffer: public QObject
bool isValid() const { return !mTiles.isEmpty(); }

/** Draws a brush with the specified parameters to the tiled buffer */
void drawBrush(const QPointF& point, int brushWidth, int brushCursorWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing);
void drawBrush(const QPointF& point, int brushWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing);
/** Draws a path with the specified parameters to the tiled buffer */
void drawPath(QPainterPath path, int cursorWidth, QPen pen, QBrush brush,
void drawPath(QPainterPath path, QPen pen, QBrush brush,
QPainter::CompositionMode cm, bool antialiasing);
/** Draws a image with the specified parameters to the tiled buffer */
void drawImage(const QImage& image, const QRect& imageBounds, QPainter::CompositionMode cm, bool antialiasing);
Expand Down
Loading

0 comments on commit 758a1a1

Please sign in to comment.