Commit 1b262b8a authored by Tusooa Zhu's avatar Tusooa Zhu

Port gradient editing and some related actions to stroke system

We created an abstract class KoCanvasStrokeHelperBase to handle
the actions to be executed in a stroke, and derived from it in
kritaui, mainly because flake cannot depend on image.
parent 80d9a036
......@@ -16,6 +16,7 @@ set(kritaflake_SRCS
KoGradientHelper.cpp
KoFlake.cpp
KoCanvasBase.cpp
KoCanvasStrokeHelperBase.cpp
KoResourceManager_p.cpp
KoDerivedResourceConverter.cpp
KoResourceUpdateMediator.cpp
......
......@@ -129,3 +129,8 @@ KoSnapGuide * KoCanvasBase::snapGuide() const
{
return d->snapGuide;
}
KoCanvasStrokeHelperBase *KoCanvasBase::strokeHelper() const
{
return 0;
}
......@@ -20,6 +20,7 @@
Boston, MA 02110-1301, USA.
*/
class KoCanvasStrokeHelperBase;
#ifndef KOCANVASBASE_H
#define KOCANVASBASE_H
......@@ -243,6 +244,8 @@ public:
/// called by KoCanvasController to set the controller that handles this canvas.
void setCanvasController(KoCanvasController *controller);
virtual KoCanvasStrokeHelperBase *strokeHelper() const;
private:
// we need a KoShapeControllerBase so that it can work
KoCanvasBase();
......
/* This file is part of the KDE project
*
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoCanvasStrokeHelperBase.h"
#include <KoCanvasBase.h>
struct KoCanvasStrokeHelperBase::Private
{
Private(KoCanvasBase *canvas) : canvas(canvas) {}
~Private() = default;
KoCanvasBase *canvas;
};
KoCanvasStrokeHelperBase::KoCanvasStrokeHelperBase(KoCanvasBase* canvas)
: m_d(new Private(canvas))
{
}
KoCanvasStrokeHelperBase::~KoCanvasStrokeHelperBase()
{
}
KoCanvasBase *KoCanvasStrokeHelperBase::canvas() const
{
return m_d->canvas;
}
/* This file is part of the KDE project
*
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KO_CANVAS_STROKE_HELPER_BASE_H_
#define KO_CANVAS_STROKE_HELPER_BASE_H_
#include <kundo2magicstring.h>
#include <kis_stroke_job_strategy.h>
#include <kritaflake_export.h>
class KoCanvasBase;
/**
* Base class for a helper that lets code in lambda functions run in a stroke.
*/
class KRITAFLAKE_EXPORT KoCanvasStrokeHelperBase
{
public:
KoCanvasStrokeHelperBase(KoCanvasBase *canvas);
virtual ~KoCanvasStrokeHelperBase();
KoCanvasBase *canvas() const;
/**
* Runs lambda in a stroke which replaces the state of the current node.
* If lambda() returns true, the stroke will create an undo command named name
* and add it to the document.
*
* We set the default sequentiality and exclusivity to barrier and exclusive
* respectively because the old code is often not guaranteed to be thread-safe.
*/
virtual void run(const KUndo2MagicString &name,
std::function<bool()> lambda,
KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::BARRIER,
KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::EXCLUSIVE) = 0;
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
......@@ -29,11 +29,12 @@
namespace KoFlake {
/// @return true if shapes is not empty, false otherwise
template <typename ModifyFunction>
auto modifyShapesStrokes(QList<KoShape*> shapes, ModifyFunction modifyFunction)
-> decltype(modifyFunction(KoShapeStrokeSP()), (KUndo2Command*)(0))
-> decltype(modifyFunction(KoShapeStrokeSP()), bool())
{
if (shapes.isEmpty()) return 0;
if (shapes.isEmpty()) return false;
QList<KoShapeStrokeModelSP> newStrokes;
......@@ -49,10 +50,12 @@ template <typename ModifyFunction>
modifyFunction(newStroke);
newStrokes << newStroke;
shape->update();
shape->setStroke(newStroke);
shape->update();
}
return new KoShapeStrokeCommand(shapes, newStrokes);
return true;
}
template <class Policy>
......
......@@ -29,6 +29,7 @@
#include <KoShapeFillWrapper.h>
#include <KoSelection.h>
#include <KoCanvasBase.h>
#include "KoCanvasStrokeHelperBase.h"
......@@ -93,9 +94,10 @@ void KoShapeFillResourceConnector::Private::applyShapeColoring(KoFlake::FillVari
}
KoShapeFillWrapper wrapper(selectedEditableShapes, fillVariant);
KUndo2Command *command = wrapper.setColor(color.toQColor()); // TODO: do the conversion in a better way!
if (command) {
canvas->addCommand(command);
}
canvas->strokeHelper()->run(kundo2_i18n("Set background"),
[selectedEditableShapes, fillVariant, color]() {
KoShapeFillWrapper wrapper(selectedEditableShapes, fillVariant);
return wrapper.setColor(color.toQColor()); // TODO: do the conversion in a better way!
});
}
......@@ -314,42 +314,40 @@ QTransform KoShapeFillWrapper::gradientTransform() const
KUndo2Command *KoShapeFillWrapper::setColor(const QColor &color)
bool KoShapeFillWrapper::setColor(const QColor &color)
{
KUndo2Command *command = 0;
bool changed = false;
if (m_d->fillVariant == KoFlake::Fill) {
QSharedPointer<KoShapeBackground> bg;
QSharedPointer<KoShapeBackground> bg;
if (color.isValid()) {
bg = toQShared(new KoColorBackground(color));
}
QSharedPointer<KoShapeBackground> fill(bg);
command = new KoShapeBackgroundCommand(m_d->shapes, fill);
Q_FOREACH (KoShape *shape, m_d->shapes) {
shape->setBackground(fill);
shape->update();
}
} else {
command = KoFlake::modifyShapesStrokes(m_d->shapes,
changed = KoFlake::modifyShapesStrokes(m_d->shapes,
[color] (KoShapeStrokeSP stroke) {
stroke->setLineBrush(Qt::NoBrush);
stroke->setColor(color.isValid() ? color : Qt::transparent);
});
}
return command;
return changed;
}
KUndo2Command *KoShapeFillWrapper::setLineWidth(const float &lineWidth)
bool KoShapeFillWrapper::setLineWidth(const float &lineWidth)
{
KUndo2Command *command = 0;
command = KoFlake::modifyShapesStrokes(m_d->shapes, [lineWidth](KoShapeStrokeSP stroke) {
return KoFlake::modifyShapesStrokes(m_d->shapes, [lineWidth](KoShapeStrokeSP stroke) {
stroke->setColor(Qt::transparent);
stroke->setLineWidth(lineWidth);
});
return command;
}
......@@ -371,25 +369,20 @@ bool KoShapeFillWrapper::hasZeroLineWidth() const
}
KUndo2Command *KoShapeFillWrapper::setGradient(const QGradient *gradient, const QTransform &transform)
bool KoShapeFillWrapper::setGradient(const QGradient *gradient, const QTransform &transform)
{
KUndo2Command *command = 0;
bool changed = false;
if (m_d->fillVariant == KoFlake::Fill) {
QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
foreach (KoShape *shape, m_d->shapes) {
Q_UNUSED(shape);
KoGradientBackground *newGradient = new KoGradientBackground(KoFlake::cloneGradient(gradient));
newGradient->setTransform(transform);
newBackgrounds << toQShared(newGradient);
shape->setBackground(toQShared(newGradient));
shape->update();
}
command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
changed = true;
} else {
command = KoFlake::modifyShapesStrokes(m_d->shapes,
changed = KoFlake::modifyShapesStrokes(m_d->shapes,
[gradient, transform] (KoShapeStrokeSP stroke) {
QBrush newBrush = *gradient;
newBrush.setTransform(transform);
......@@ -399,33 +392,32 @@ KUndo2Command *KoShapeFillWrapper::setGradient(const QGradient *gradient, const
});
}
return command;
return changed;
}
KUndo2Command* KoShapeFillWrapper::applyGradient(const QGradient *gradient)
bool KoShapeFillWrapper::applyGradient(const QGradient *gradient)
{
return setGradient(gradient, gradientTransform());
}
KUndo2Command* KoShapeFillWrapper::applyGradientStopsOnly(const QGradient *gradient)
bool KoShapeFillWrapper::applyGradientStopsOnly(const QGradient *gradient)
{
KUndo2Command *command = 0;
bool changed = false;
if (m_d->fillVariant == KoFlake::Fill) {
QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
foreach (KoShape *shape, m_d->shapes) {
newBackgrounds << m_d->applyFillGradientStops(shape, gradient);
QSharedPointer<KoShapeBackground> newBackground(m_d->applyFillGradientStops(shape, gradient));
shape->setBackground(newBackground);
shape->update();
}
command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
changed = true;
} else {
command = KoFlake::modifyShapesStrokes(m_d->shapes,
changed = KoFlake::modifyShapesStrokes(m_d->shapes,
[this, gradient] (KoShapeStrokeSP stroke) {
m_d->applyFillGradientStops(stroke, gradient);
});
}
return command;
return changed;
}
......@@ -46,12 +46,13 @@ public:
QTransform gradientTransform() const;
bool hasZeroLineWidth() const;
KUndo2Command* setColor(const QColor &color);
KUndo2Command* setLineWidth(const float &lineWidth);
/// returns whether the shape has changed
bool setColor(const QColor &color);
bool setLineWidth(const float &lineWidth);
KUndo2Command* setGradient(const QGradient *gradient, const QTransform &transform);
KUndo2Command* applyGradient(const QGradient *gradient);
KUndo2Command* applyGradientStopsOnly(const QGradient *gradient);
bool setGradient(const QGradient *gradient, const QTransform &transform);
bool applyGradient(const QGradient *gradient);
bool applyGradientStopsOnly(const QGradient *gradient);
private:
struct Private;
......
......@@ -403,6 +403,8 @@ set(kritaui_LIB_SRCS
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
KisMouseClickEater.cpp
KisCanvasStrokeHelper.cpp
)
if(WIN32)
......
/* This file is part of the KDE project
*
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisCanvasStrokeHelper.h"
#include <strokes/KisNodeReplaceBasedStrokeStrategy.h>
#include <kis_canvas2.h>
#include <KisRunnableStrokeJobData.h>
struct KisCanvasStrokeHelper::Private
{
};
KisCanvasStrokeHelper::KisCanvasStrokeHelper(KoCanvasBase* canvas)
: KoCanvasStrokeHelperBase(canvas)
{
}
KisCanvasStrokeHelper::~KisCanvasStrokeHelper()
{
}
void KisCanvasStrokeHelper::run(const KUndo2MagicString& name,
std::function<bool()> lambda,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
KisStrokesFacade *strokesFacade = kisCanvas->image().data();
KisNodeReplaceBasedStrokeStrategy *strategy = new KisNodeReplaceBasedStrokeStrategy("LAMBDA_STROKE", kisCanvas, name);
KisStrokeId stroke = strokesFacade->startStroke(strategy);
strokesFacade->addJob(stroke, new KisRunnableStrokeJobData(
[strategy, lambda]()
{
if (lambda()) {
strategy->setChanged(true);
}
},
sequentiality,
exclusivity));
strokesFacade->endStroke(stroke);
}
/* This file is part of the KDE project
*
* Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CANVAS_STROKE_HELPER_H_
#define KIS_CANVAS_STROKE_HELPER_H_
#include <KoCanvasStrokeHelperBase.h>
#include <kritaui_export.h>
class KoCanvasBase;
/**
* Base class for a helper that lets code in lambda functions run in a stroke.
*/
class KRITAUI_EXPORT KisCanvasStrokeHelper : public KoCanvasStrokeHelperBase
{
public:
KisCanvasStrokeHelper(KoCanvasBase *canvas);
~KisCanvasStrokeHelper() override;
/**
* Runs lambda in a stroke which replaces the state of the current node.
* If lambda() returns true, the stroke will create an undo command named name
* and add it to the document.
*/
void run(const KUndo2MagicString &name,
std::function<bool()> lambda,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity) override;
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
......@@ -103,6 +103,8 @@
#include "KisSnapPixelStrategy.h"
#include <KisCanvasStrokeHelper.h>
class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private
{
......@@ -117,6 +119,7 @@ public:
, toolProxy(parent)
, displayColorConverter(resourceManager, view)
, regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
, strokeHelper(parent)
{
}
......@@ -159,6 +162,8 @@ public:
QRect renderingLimit;
int isBatchUpdateActive = 0;
KisCanvasStrokeHelper strokeHelper;
bool effectiveLodAllowedInImage() {
return lodAllowedInImage && !bootstrapLodBlocked;
}
......@@ -1295,3 +1300,8 @@ KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const
KisCanvasDecorationSP deco = decoration("referenceImagesDecoration");
return qobject_cast<KisReferenceImagesDecoration*>(deco.data());
}
KoCanvasStrokeHelperBase *KisCanvas2::strokeHelper() const
{
return &(m_d->strokeHelper);
}
......@@ -232,6 +232,8 @@ public: // KisCanvas2 methods
*/
QRect renderingLimit() const;
KoCanvasStrokeHelperBase *strokeHelper() const override;
Q_SIGNALS:
void sigCanvasEngineChanged();
......
......@@ -67,6 +67,9 @@
#include "kis_global.h"
#include "kis_debug.h"
#include <strokes/KisNodeReplaceBasedStrokeStrategy.h>
#include <kis_canvas2.h>
#include <KoCanvasStrokeHelperBase.h>
static const char* const buttonnone[]={
"16 16 3 1",
......@@ -488,23 +491,19 @@ void KoFillConfigWidget::noColorSelected()
return;
}
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
KUndo2Command *command = wrapper.setColor(QColor());
if (command) {
d->canvas->addCommand(command);
}
if (d->fillVariant == KoFlake::StrokeFill) {
KUndo2Command *lineCommand = wrapper.setLineWidth(0.0);
if (lineCommand) {
d->canvas->addCommand(lineCommand);
}
}
KoFlake::FillVariant fillVariant = d->fillVariant;
d->canvas->strokeHelper()->run(kundo2_i18n("Set background"),
[this, fillVariant, selectedShapes]() {
KoShapeFillWrapper wrapper(selectedShapes, fillVariant);
bool res = wrapper.setColor(QColor());
if (fillVariant == KoFlake::StrokeFill) {
res = res || wrapper.setLineWidth(0.0);
}
emit this->sigFillChanged();
emit sigFillChanged();
return res;
});
}
void KoFillConfigWidget::colorChanged()
......@@ -518,36 +517,30 @@ void KoFillConfigWidget::colorChanged()
return;
}
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
d->canvas->strokeHelper()->run(kundo2_i18n("Set background"),
[this, selectedShapes]() {
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
bool res = wrapper.setColor(d->colorAction->currentColor());
KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor());
if (command) {
d->canvas->addCommand(command);
}
// only returns true if it is a stroke object that has a 0 for line width
if (wrapper.hasZeroLineWidth()) {
res = res || wrapper.setLineWidth(1.0);
// only returns true if it is a stroke object that has a 0 for line width
if (wrapper.hasZeroLineWidth() ) {
KUndo2Command *lineCommand = wrapper.setLineWidth(1.0);
if (lineCommand) {
d->canvas->addCommand(lineCommand);
}
// * line to test out
QColor solidColor = d->colorAction->currentColor();
solidColor.setAlpha(255);
command = wrapper.setColor(solidColor);
if (command) {
d->canvas->addCommand(command);
}
// * line to test out
QColor solidColor = d->colorAction->currentColor();
solidColor.setAlpha(255);
res = res || wrapper.setColor(solidColor);
}
d->colorAction->setCurrentColor(wrapper.color());
}
emit sigFillChanged();
emit sigInternalRequestColorToResourceManager();
return res;
});
d->colorAction->setCurrentColor(wrapper.color());
emit sigFillChanged();
emit sigInternalRequestColorToResourceManager();
}
void KoFillConfigWidget::slotProposeCurrentColorToResourceManager()
......@@ -692,15 +685,15 @@ void KoFillConfigWidget::setNewGradientBackgroundToShape()
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
QScopedPointer<QGradient> srcQGradient(d->activeGradient->toQGradient());
KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data());
if (command) {
d->canvas->addCommand(command);
}
d->canvas->strokeHelper()->run(kundo2_i18n("Set background"),
[this, selectedShapes]() {
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
QScopedPointer<QGradient> srcQGradient(d->activeGradient->toQGradient());
bool res = wrapper.applyGradientStopsOnly(srcQGradient.data());
emit sigFillChanged();
return res;
});
emit sigFillChanged();
}
void KoFillConfigWidget::updateGradientSaveButtonAvailability()
......
......@@ -73,6 +73,7 @@
// Krita
#include "kis_double_parse_unit_spin_box.h"
#include <KoCanvasStrokeHelperBase.h>
class CapNJoinMenu : public QMenu
{
......@@ -514,11 +515,10 @@ template <typename ModifyFunction>
QList<KoShape*> shapes = selection->selectedEditableShapes();
KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction);
if (command) {
canvas->addCommand(command);
}
canvas->strokeHelper()->run(kundo2_i18n("Set stroke"),
[shapes, modifyFunction]() {
return KoFlake::modifyShapesStrokes(shapes, modifyFunction);
});
}
void KoStrokeConfigWidget::applyDashStyleChanges()
......
......@@ -89,7 +89,7 @@ QGradient::Type KoShapeGradientHandles::type() const
return g ? g->type() : QGradient::NoGradient;
}
KUndo2Command *KoShapeGradientHandles::moveGradientHandle(KoShapeGradientHandles::Handle::Type handleType, const QPointF &absoluteOffset)
bool KoShapeGradientHandles::moveGradientHandle(KoShapeGradientHandles::Handle::Type handleType, const QPointF &absoluteOffset)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(handleType != Handle::None, 0);
......
......@@ -52,7 +52,7 @@ public:
QVector<Handle> handles(const KoViewConverter *converter = 0) const;
QGradient::Type type() const;
KUndo2Command* moveGradientHandle(Handle::Type handleType, const QPointF &absoluteOffset);
bool moveGradientHandle(Handle::Type handleType, const QPointF &absoluteOffset);
Handle getHandle(Handle::Type handleType);
......
......@@ -28,36 +28,67 @@
#include <kundo2command.h>
#include <KoSnapGuide.h>
#include <KisSnapPointStrategy.h>
#include <kis_canvas2.h>
#include "kis_debug.h"
ShapeGradientEditStrategy::ShapeGradientEditStrategy(KoToolBase *tool,
KoFlake::FillVariant fillVariant,
KoShape *shape,
KoShapeGradientHandles::Handle::Type startHandleType,
const QPointF &clicked)
: KisStrokeBasedInteractionStrategy(tool)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
startStroke(new ShapeGradientEditStrokeStrategy(tool, fillVariant, shape, startHandleType, clicked));
}
ShapeGradientEditStrategy::~ShapeGradientEditStrategy()
{
}
void ShapeGradientEditStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
KisStrokeBasedInteractionStrategy::finishInteraction(modifiers);
const QRectF dirtyRect = tool()->canvas()->snapGuide()->boundingRect();
tool()->canvas()->snapGuide()->reset();
tool()->canvas()->updateCanvas(dirtyRect);
}
void ShapeGradientEditStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
struct ShapeGradientEditStrategy::Private
struct ShapeGradientEditStrokeStrategy::Private