18#include "stroketool.h"
21#include "scribblearea.h"
22#include "viewmanager.h"
23#include "preferencemanager.h"
25#include "toolmanager.h"
28#include "canvascursorpainter.h"
32 void detectWhichOSX();
33 void disableCoalescing();
34 void enableCoalescing();
38 void detectWhichOSX() {}
39 void disableCoalescing() {}
40 void enableCoalescing() {}
44const qreal StrokeTool::FEATHER_MIN = 1.;
45const qreal StrokeTool::FEATHER_MAX = 99.;
46const qreal StrokeTool::WIDTH_MIN = 1.;
47const qreal StrokeTool::WIDTH_MAX = 200.;
50bool StrokeTool::mQuickSizingEnabled =
false;
59 mQuickSizingEnabled = mEditor->preference()->isOn(SETTING::QUICK_SIZING);
60 mCanvasCursorEnabled = mEditor->preference()->isOn(SETTING::CANVAS_CURSOR);
64 connect(mEditor->preference(), &PreferenceManager::optionChanged,
this, &StrokeTool::onPreferenceChanged);
66 connect(&mWidthSizingTool, &RadialOffsetTool::offsetChanged,
this, [=](qreal offset) {
67 mEditor->tools()->setWidth(qBound(WIDTH_MIN, offset * 2.0, WIDTH_MAX));
70 connect(&mFeatherSizingTool, &RadialOffsetTool::offsetChanged,
this, [=](qreal offset){
71 const qreal inputMin = FEATHER_MIN;
72 const qreal inputMax = properties.width * 0.5;
73 const qreal outputMax = FEATHER_MAX;
74 const qreal outputMin = inputMin;
77 const qreal mappedValue = MathUtils::map(offset, inputMin, inputMax, outputMax, outputMin);
79 mEditor->tools()->setFeather(qBound(FEATHER_MIN, mappedValue, FEATHER_MAX));
85 mActiveConnections.
append(
connect(mEditor->view(), &ViewManager::viewChanged,
this, &StrokeTool::onViewUpdated));
94void StrokeTool::onPreferenceChanged(SETTING setting)
96 if (setting == SETTING::QUICK_SIZING) {
97 mQuickSizingEnabled = mEditor->preference()->isOn(setting);
98 }
else if (setting == SETTING::CANVAS_CURSOR) {
99 mCanvasCursorEnabled = mEditor->preference()->isOn(setting);
103void StrokeTool::onViewUpdated()
105 updateCanvasCursor();
108QPointF StrokeTool::getCurrentPressPixel()
const
110 return mInterpolator.getCurrentPressPixel();
113QPointF StrokeTool::getCurrentPressPoint()
const
115 return mEditor->view()->mapScreenToCanvas(mInterpolator.getCurrentPressPixel());
118QPointF StrokeTool::getCurrentPixel()
const
120 return mInterpolator.getCurrentPixel();
123QPointF StrokeTool::getCurrentPoint()
const
125 return mEditor->view()->mapScreenToCanvas(getCurrentPixel());
128QPointF StrokeTool::getLastPixel()
const
130 return mInterpolator.getLastPixel();
133QPointF StrokeTool::getLastPoint()
const
135 return mEditor->view()->mapScreenToCanvas(getLastPixel());
138void StrokeTool::startStroke(PointerEvent::InputType inputType)
146 mLastPixel = getCurrentPixel();
148 mStrokePoints.
clear();
151 QPointF startStrokes = mInterpolator.interpolateStart(mLastPixel);
152 mStrokePoints << mEditor->view()->mapScreenToCanvas(startStrokes);
154 mStrokePressures.
clear();
155 mStrokePressures << mInterpolator.getPressure();
157 mCurrentInputType = inputType;
158 mUndoSaveState = mEditor->undoRedo()->
state(UndoRedoRecordType::KEYFRAME_MODIFY);
163bool StrokeTool::keyPressEvent(
QKeyEvent *event)
165 switch (
event->key()) {
167 if (mEditor->tools()->setTemporaryTool(EYEDROPPER, {},
Qt::AltModifier))
179 return BaseTool::keyPressEvent(
event);
187void StrokeTool::endStroke()
189 mInterpolator.interpolateEnd();
190 mStrokePressures << mInterpolator.getPressure();
191 mStrokePoints.
clear();
192 mStrokePressures.
clear();
196 mEditor->setModified(mEditor->currentLayerIndex(), mEditor->currentFrame());
197 mScribbleArea->endStroke();
199 mEditor->undoRedo()->
record(mUndoSaveState, typeName());
202void StrokeTool::drawStroke()
204 QPointF pixel = getCurrentPixel();
205 if (pixel != mLastPixel || !mFirstDraw)
208 QPointF startStrokes = mInterpolator.interpolateStart(getLastPixel());
209 mStrokePoints << mEditor->view()->mapScreenToCanvas(startStrokes);
210 mStrokePressures << mInterpolator.getPressure();
220 if (!mQuickSizingEnabled) {
return false; }
223 mWidthSizingTool.stopAdjusting();
224 mFeatherSizingTool.stopAdjusting();
228 ToolPropertyType setting = mQuickSizingProperties[
event->modifiers()];
229 if (
event->eventType() == PointerEvent::Press) {
232 mWidthSizingTool.setOffset(properties.width * 0.5);
236 const qreal factor = 0.5;
237 const qreal cursorRad = properties.width * factor;
240 const qreal featherWidthFactor = MathUtils::normalize(properties.feather, FEATHER_MIN, FEATHER_MAX);
241 const qreal offset = (cursorRad * featherWidthFactor);
242 mFeatherSizingTool.setOffset(offset);
251 mWidthSizingTool.pointerEvent(
event);
255 mFeatherSizingTool.pointerEvent(
event);
261 updateCanvasCursor();
267 updateCanvasCursor();
272 updateCanvasCursor();
277 updateCanvasCursor();
282 mCanvasCursorEnabled = mEditor->preference()->isOn(SETTING::CANVAS_CURSOR);
286bool StrokeTool::leaveEvent(
QEvent*)
293 mCanvasCursorEnabled =
false;
294 updateCanvasCursor();
299QRectF StrokeTool::cursorRect(ToolPropertyType settingType,
const QPointF& point)
301 const qreal brushWidth = properties.width;
302 const qreal brushFeather = properties.feather;
304 const QPointF& cursorPos = point;
305 const qreal cursorRad = brushWidth * 0.5;
306 const QPointF& widthCursorTopLeft =
QPointF(cursorPos.
x() - cursorRad, cursorPos.
y() - cursorRad);
308 const QRectF widthCircleRect =
QRectF(widthCursorTopLeft,
QSizeF(brushWidth, brushWidth));
309 if (settingType == WIDTH) {
310 return widthCircleRect;
311 }
else if (settingType == FEATHER) {
312 const qreal featherWidthFactor = MathUtils::normalize(brushFeather, FEATHER_MIN, FEATHER_MAX);
313 QRectF featherRect =
QRectF(widthCircleRect.
center().
x() - (cursorRad * featherWidthFactor),
314 widthCircleRect.
center().
y() - (cursorRad * featherWidthFactor),
315 brushWidth * featherWidthFactor,
316 brushWidth * featherWidthFactor);
320 return featherRect.
adjusted(2, 2, -2, -2);
327void StrokeTool::updateCanvasCursor()
330 widthOptions.circleRect = cursorRect(WIDTH, mWidthSizingTool.isAdjusting() ? mWidthSizingTool.offsetPoint() : getCurrentPoint());
331 widthOptions.showCursor = mCanvasCursorEnabled;
332 widthOptions.showCross =
true;
335 featherOptions.circleRect = cursorRect(FEATHER, mFeatherSizingTool.isAdjusting() ? mFeatherSizingTool.offsetPoint() : getCurrentPoint());
336 featherOptions.showCursor = mCanvasCursorEnabled;
337 featherOptions.showCross =
false;
339 if (mFeatherSizingTool.isAdjusting()) {
340 widthOptions.circleRect = cursorRect(WIDTH, mFeatherSizingTool.offsetPoint());
341 }
else if (mWidthSizingTool.isAdjusting()) {
342 featherOptions.circleRect = cursorRect(FEATHER, mWidthSizingTool.offsetPoint());
345 mWidthCursorPainter.preparePainter(widthOptions);
346 mFeatherCursorPainter.preparePainter(featherOptions);
348 const QRect& dirtyRect = mWidthCursorPainter.dirtyRect();
356 mWidthCursorPainter.clearDirty();
359void StrokeTool::paint(
QPainter& painter,
const QRect& blitRect)
364 if (properties.useFeather) {
365 mFeatherCursorPainter.paint(painter, blitRect);
368 mWidthCursorPainter.paint(painter, blitRect);
void handleDrawingOnEmptyFrame()
Call this when starting to use a paint tool.
void record(const UndoSaveState *&undoState, const QString &description)
Records the given save state.
const UndoSaveState * state(UndoRedoRecordType recordType) const
Prepares and returns a save state with the given scope.
bool contains(const Key &key) const const
void append(const T &value)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual bool event(QEvent *e)
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
QRect united(const QRect &rectangle) const const
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
QPointF center() const const
QRect toAlignedRect() const const