Commit e6ce7e36 authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

[FEATURE] Pressure sensitive line tool

Now when you draw the line tool, the preview is updated automatically,
more than that, you can control pressure and other sensors on the fly!

What's more, now you can use all the complicated sensors and mirroring
with the line tool!

BUG:333873
CCMAIL:kimageshop@kde.org
parent 7af9112b
......@@ -135,5 +135,9 @@ inline T kisGrowRect(const T &rect, U offset) {
return rect.adjusted(-offset, -offset, offset, offset);
}
inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) {
return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y()));
}
#endif // KISGLOBAL_H_
......@@ -65,13 +65,19 @@ private:
KisSimpleStrokeStrategy::KisSimpleStrokeStrategy(QString id, const KUndo2MagicString &name)
: KisStrokeStrategy(id, name),
m_jobEnabled(4, false)
m_jobEnabled(4, false),
m_jobSequentiality(4, KisStrokeJobData::SEQUENTIAL),
m_jobExclusivity(4, KisStrokeJobData::NORMAL)
{
}
void KisSimpleStrokeStrategy::enableJob(JobType type, bool enable)
void KisSimpleStrokeStrategy::enableJob(JobType type, bool enable,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
m_jobEnabled[(int)type] = enable;
m_jobSequentiality[(int)type] = sequentiality;
m_jobExclusivity[(int)type] = exclusivity;
}
KisStrokeJobStrategy*
......@@ -106,20 +112,27 @@ KisStrokeJobStrategy* KisSimpleStrokeStrategy::createDabStrategy()
return createStrategy(JOB_DOSTROKE);
}
KisStrokeJobData* KisSimpleStrokeStrategy::createData(JobType type)
{
KisStrokeJobData::Sequentiality sequentiality = m_jobSequentiality[(int)type];
KisStrokeJobData::Exclusivity exclusivity = m_jobExclusivity[(int)type];
return new KisStrokeJobData(sequentiality, exclusivity);
}
KisStrokeJobData* KisSimpleStrokeStrategy::createInitData()
{
return 0;
return createData(JOB_INIT);
}
KisStrokeJobData* KisSimpleStrokeStrategy::createFinishData()
{
return 0;
return createData(JOB_FINISH);
}
KisStrokeJobData* KisSimpleStrokeStrategy::createCancelData()
{
return 0;
return createData(JOB_CANCEL);
}
void KisSimpleStrokeStrategy::initStrokeCallback()
......
......@@ -51,13 +51,18 @@ public:
virtual void doStrokeCallback(KisStrokeJobData *data);
protected:
void enableJob(JobType type, bool enable = true);
void enableJob(JobType type, bool enable = true,
KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL);
private:
KisStrokeJobStrategy* createStrategy(JobType type);
KisStrokeJobData* createData(JobType type);
private:
QVector<bool> m_jobEnabled;
QVector<KisStrokeJobData::Sequentiality> m_jobSequentiality;
QVector<KisStrokeJobData::Exclusivity> m_jobExclusivity;
};
#endif /* __KIS_SIMPLE_STROKE_STRATEGY_H */
......@@ -7,6 +7,7 @@ set(kritadefaulttools_PART_SRCS
kis_tool_colorpicker.cc
kis_tool_brush.cc
kis_tool_line.cc
kis_tool_line_helper.cpp
kis_tool_fill.cc
kis_tool_rectangle.cc
kis_tool_ellipse.cc
......
......@@ -42,17 +42,26 @@
#include <recorder/kis_recorded_path_paint_action.h>
#include <recorder/kis_node_query_path.h>
#include "kis_painting_information_builder.h"
#include "kis_tool_line_helper.h"
#define ENABLE_RECORDING
KisToolLine::KisToolLine(KoCanvasBase * canvas)
: KisToolPaint(canvas, KisCursor::load("tool_line_cursor.png", 6, 6))
: KisToolPaint(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)),
m_infoBuilder(new KisToolPaintingInformationBuilder(this)),
m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))),
m_strokeUpdateCompressor(500, KisSignalCompressor::FIRST_ACTIVE),
m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE)
{
setObjectName("tool_line");
m_painter = 0;
currentImage() = 0;
setSupportOutline(true);
connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke()));
connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke()));
}
KisToolLine::~KisToolLine()
......@@ -67,23 +76,10 @@ int KisToolLine::flags() const
QWidget* KisToolLine::createOptionWidget()
{
QWidget* widget = KisToolPaint::createOptionWidget();
m_cbPressure = new QCheckBox(i18n("Pressure"));
m_cbTilt = new QCheckBox(i18n("Tilt"));
m_cbRotation = new QCheckBox(i18n("Rotation"));
m_cbTangPressure = new QCheckBox(i18n("Tangential Pressure"));
m_bnVaryingEnds = new QPushButton(i18n("Varying End-Points"));
m_cbPressure->setChecked(true);
m_cbTilt->setChecked(true);
m_cbRotation->setChecked(true);
m_cbTangPressure->setChecked(true);
m_bnVaryingEnds->setCheckable(true);
addOptionWidgetOption(m_cbPressure);
addOptionWidgetOption(m_cbTilt);
addOptionWidgetOption(m_cbRotation);
addOptionWidgetOption(m_cbTangPressure);
addOptionWidgetOption(m_bnVaryingEnds);
m_chkUseSensors = new QCheckBox(i18n("Use sensors"));
m_chkUseSensors->setChecked(true);
addOptionWidgetOption(m_chkUseSensors);
return widget;
}
......@@ -99,24 +95,29 @@ void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter)
void KisToolLine::beginPrimaryAction(KoPointerEvent *event)
{
if (nodePaintAbility() == NONE || !nodeEditable()) {
NodePaintAbility nodeAbility = nodePaintAbility();
if (nodeAbility == NONE || !nodeEditable()) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
m_startPos = KisPaintInformation(
convertToPixelCoord(event),
PRESSURE_DEFAULT,
m_cbTilt->isChecked() ? event->xTilt() : 0.0,
m_cbTilt->isChecked() ? event->yTilt() : 0.0,
m_cbRotation->isChecked() ? event->rotation() : 0.0,
m_cbTangPressure->isChecked() ? event->tangentialPressure() : 0.0
);
m_endPos = m_startPos;
m_maxPressure = 0.0f;
m_helper->setEnabled(nodeAbility == PAINT);
m_helper->setUseSensors(m_chkUseSensors->isChecked());
m_helper->start(event);
m_startPoint = convertToPixelCoord(event);
m_endPoint = m_startPoint;
m_lastUpdatedPoint = m_startPoint;
}
void KisToolLine::updateStroke()
{
m_helper->repaintLine(canvas()->resourceManager(),
image(),
image().data(),
image()->postExecutionUndoAdapter());
}
void KisToolLine::continuePrimaryAction(KoPointerEvent *event)
......@@ -129,66 +130,46 @@ void KisToolLine::continuePrimaryAction(KoPointerEvent *event)
QPointF pos = convertToPixelCoord(event);
if (event->modifiers() == Qt::AltModifier) {
QPointF trans = pos - m_endPos.pos();
m_startPos.setPos(m_startPos.pos() + trans);
m_endPos.setPos(m_endPos.pos() + trans);
QPointF trans = pos - m_endPoint;
m_helper->translatePoints(trans);
} else if (event->modifiers() == Qt::ShiftModifier) {
m_endPos.setPos(straightLine(pos));
m_helper->addPoint(event, straightLine(pos));
} else {
m_endPos.setPos(pos);
m_helper->addPoint(event);
}
m_maxPressure = qMax(m_maxPressure, float(pressureToCurve(event->pressure())));
if ((pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength() > 10) {
m_longStrokeUpdateCompressor.stop();
m_strokeUpdateCompressor.start();
m_lastUpdatedPoint = pos;
} else {
m_longStrokeUpdateCompressor.start();
}
m_endPoint = pos;
updatePreview();
KisToolPaint::requestUpdateOutline(event->point, event);
}
void KisToolLine::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
updatePreview();
if(m_bnVaryingEnds->isChecked()) {
m_endPos = KisPaintInformation(
m_endPos.pos(),
PRESSURE_DEFAULT,
m_cbTilt->isChecked() ? event->xTilt() : 0.0,
m_cbTilt->isChecked() ? event->yTilt() : 0.0,
m_cbRotation->isChecked() ? event->rotation() : 0.0,
m_cbTangPressure->isChecked() ? event->tangentialPressure() : 0.0
);
}
if (m_startPos.pos() == m_endPos.pos())
if (m_startPoint == m_endPoint)
return;
if(m_cbPressure->isChecked()) {
m_startPos.setPressure(m_maxPressure);
m_endPos.setPressure(m_maxPressure);
}
NodePaintAbility nodeAbility = nodePaintAbility();
if (nodeAbility == NONE) {
return;
}
#ifdef ENABLE_RECORDING
if (image()) {
KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset());
setupPaintAction(&linePaintAction);
linePaintAction.addLine(m_startPos, m_endPos);
image()->actionRecorder()->addAction(linePaintAction);
}
#endif
if (nodeAbility == PAINT) {
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Line"),
image(),
canvas()->resourceManager(),
KisPainter::StrokeStyleBrush,
KisPainter::FillStyleNone);
helper.paintLine(m_startPos, m_endPos);
updateStroke();
m_helper->end();
}
else {
KoPathShape* path = new KoPathShape();
......@@ -196,8 +177,8 @@ void KisToolLine::endPrimaryAction(KoPointerEvent *event)
QTransform resolutionMatrix;
resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
path->moveTo(resolutionMatrix.map(m_startPos.pos()));
path->lineTo(resolutionMatrix.map(m_endPos.pos()));
path->moveTo(resolutionMatrix.map(m_startPoint));
path->lineTo(resolutionMatrix.map(m_endPoint));
path->normalize();
KoShapeStroke* border = new KoShapeStroke(1.0, currentFgColor().toQColor());
......@@ -210,7 +191,7 @@ void KisToolLine::endPrimaryAction(KoPointerEvent *event)
QPointF KisToolLine::straightLine(QPointF point)
{
const QPointF lineVector = point - m_startPos.pos();
const QPointF lineVector = point - m_startPoint;
qreal lineAngle = std::atan2(lineVector.y(), lineVector.x());
if (lineAngle < 0) {
......@@ -226,7 +207,7 @@ QPointF KisToolLine::straightLine(QPointF point)
const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle));
const QPointF result = m_startPos.pos() + constrainedLineVector;
const QPointF result = m_startPoint + constrainedLineVector;
return result;
}
......@@ -235,7 +216,7 @@ QPointF KisToolLine::straightLine(QPointF point)
void KisToolLine::updatePreview()
{
if (canvas()) {
QRectF bound(m_startPos.pos(), m_endPos.pos());
QRectF bound(m_startPoint, m_endPoint);
canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3)));
}
}
......@@ -243,8 +224,8 @@ void KisToolLine::updatePreview()
void KisToolLine::paintLine(QPainter& gc, const QRect&)
{
QPointF viewStartPos = pixelToView(m_startPos.pos());
QPointF viewStartEnd = pixelToView(m_endPos.pos());
QPointF viewStartPos = pixelToView(m_startPoint);
QPointF viewStartEnd = pixelToView(m_endPoint);
if (canvas()) {
QPainterPath path;
......
......@@ -25,11 +25,12 @@
#include "kis_tool_paint.h"
#include <QScopedPointer>
#include "kis_global.h"
#include "kis_types.h"
#include "KoToolFactoryBase.h"
#include "flake/kis_node_shape.h"
#include "kis_paint_information.h"
#include "kis_signal_compressor.h"
#include <KoIcon.h>
class KisPainter;
......@@ -38,6 +39,9 @@ class KoCanvasBase;
class KisRecordedPolyLinePaintAction;
class QCheckBox;
class QPushButton;
class KisToolPaintingInformationBuilder;
class KisToolLineHelper;
class KisToolLine : public KisToolPaint
{
......@@ -56,6 +60,9 @@ public:
virtual QString quickHelp() const;
private slots:
void updateStroke();
private:
void paintLine(QPainter& gc, const QRect& rc);
QPointF straightLine(QPointF point);
......@@ -63,15 +70,16 @@ private:
virtual QWidget* createOptionWidget();
private:
KisPaintInformation m_startPos;
KisPaintInformation m_endPos;
KisPainter* m_painter;
QCheckBox* m_cbPressure;
QCheckBox* m_cbTilt;
QCheckBox* m_cbRotation;
QCheckBox* m_cbTangPressure;
QPushButton* m_bnVaryingEnds;
float m_maxPressure;
QPointF m_startPoint;
QPointF m_endPoint;
QPointF m_lastUpdatedPoint;
QCheckBox *m_chkUseSensors;
QScopedPointer<KisToolPaintingInformationBuilder> m_infoBuilder;
QScopedPointer<KisToolLineHelper> m_helper;
KisSignalCompressor m_strokeUpdateCompressor;
KisSignalCompressor m_longStrokeUpdateCompressor;
};
......
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_line_helper.h"
#include "kis_painting_information_builder.h"
struct KisToolLineHelper::Private
{
Private(KisPaintingInformationBuilder *_infoBuilder)
: infoBuilder(_infoBuilder),
useSensors(true),
enabled(true)
{
}
QVector<KisPaintInformation> linePoints;
KisPaintingInformationBuilder *infoBuilder;
bool useSensors;
bool enabled;
};
KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText,
KisRecordingAdapter *recordingAdapter)
: KisToolFreehandHelper(infoBuilder, transactionText, recordingAdapter),
m_d(new Private(infoBuilder))
{
}
KisToolLineHelper::~KisToolLineHelper()
{
}
void KisToolLineHelper::setEnabled(bool value)
{
m_d->enabled = value;
}
void KisToolLineHelper::setUseSensors(bool value)
{
m_d->useSensors = value;
}
void KisToolLineHelper::repaintLine(KoCanvasResourceManager *resourceManager,
KisImageWSP image,
KisStrokesFacade *strokesFacade,
KisPostExecutionUndoAdapter *undoAdapter)
{
if (!m_d->enabled) return;
cancelPaint();
if (m_d->linePoints.isEmpty()) return;
QVector<KisPaintInformation>::const_iterator it = m_d->linePoints.constBegin();
QVector<KisPaintInformation>::const_iterator end = m_d->linePoints.constEnd();
initPaintImpl(*it, resourceManager, image, strokesFacade, undoAdapter);
++it;
while (it != end) {
paintLine(*(it - 1), *it);
++it;
}
}
void KisToolLineHelper::start(KoPointerEvent *event)
{
if (!m_d->enabled) return;
KisPaintInformation pi =
m_d->infoBuilder->startStroke(event, elapsedStrokeTime());
if (!m_d->useSensors) {
pi = KisPaintInformation(pi.pos());
}
m_d->linePoints.append(pi);
}
void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos)
{
if (!m_d->enabled) return;
KisPaintInformation pi =
m_d->infoBuilder->continueStroke(event, elapsedStrokeTime());
if (!m_d->useSensors) {
pi = KisPaintInformation(pi.pos());
}
if (!overridePos.isNull()) {
pi.setPos(overridePos);
}
if (m_d->linePoints.size() > 1) {
const QPointF startPos = m_d->linePoints.first().pos();
const QPointF endPos = pi.pos();
const qreal maxDistance = kisDistance(startPos, endPos);
const QPointF unit = (endPos - startPos) / maxDistance;
QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
++it;
while (it != m_d->linePoints.end()) {
qreal dist = kisDistance(startPos, it->pos());
if (dist < maxDistance) {
QPointF pos = startPos + unit * dist;
it->setPos(pos);
++it;
} else {
it = m_d->linePoints.erase(it);
}
}
}
m_d->linePoints.append(pi);
}
void KisToolLineHelper::translatePoints(const QPointF &offset)
{
if (!m_d->enabled) return;
QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
while (it != m_d->linePoints.end()) {
it->setPos(it->pos() + offset);
++it;
}
}
void KisToolLineHelper::end()
{
if (!m_d->enabled) return;
endPaint();
m_d->linePoints.clear();
}
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_TOOL_LINE_HELPER_H
#define __KIS_TOOL_LINE_HELPER_H
#include "kis_tool_freehand_helper.h"
class KisToolLineHelper : private KisToolFreehandHelper
{
public:
KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText,
KisRecordingAdapter *recordingAdapter = 0);
~KisToolLineHelper();
void setEnabled(bool value);
void setUseSensors(bool value);
void repaintLine(KoCanvasResourceManager *resourceManager,
KisImageWSP image,
KisStrokesFacade *strokesFacade,
KisPostExecutionUndoAdapter *undoAdapter);
void start(KoPointerEvent *event);
void addPoint(KoPointerEvent *event, const QPointF &overridePos = QPointF());
void translatePoints(const QPointF &offset);
void end();
private:
struct Private;
Private * const m_d;
};