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
{