Pencil: Shift key shows a real-time preview of a straight line (#1387)

This commit is contained in:
David Capello 2017-06-16 16:28:48 -03:00
parent 34e794519c
commit 265d18635c
13 changed files with 304 additions and 84 deletions

View File

@ -8,6 +8,7 @@
#define APP_TOOLS_CONTROLLER_H_INCLUDED #define APP_TOOLS_CONTROLLER_H_INCLUDED
#pragma once #pragma once
#include "app/tools/trace_policy.h"
#include "gfx/point.h" #include "gfx/point.h"
#include <string> #include <string>
@ -28,6 +29,8 @@ namespace app {
virtual bool isFreehand() { return false; } virtual bool isFreehand() { return false; }
virtual bool isOnePoint() { return false; } virtual bool isOnePoint() { return false; }
virtual void prepareController(ToolLoop* loop) { }
// Called when the user starts drawing and each time a new button is // Called when the user starts drawing and each time a new button is
// pressed. The controller could be sure that this method is called // pressed. The controller could be sure that this method is called
// at least one time. The point is a position relative to sprite // at least one time. The point is a position relative to sprite
@ -47,6 +50,13 @@ namespace app {
// Last point used by this controller, useful to save the last // Last point used by this controller, useful to save the last
// point of a freehand tool. // point of a freehand tool.
virtual gfx::Point getLastPoint() const { return gfx::Point(0, 0); } virtual gfx::Point getLastPoint() const { return gfx::Point(0, 0); }
// Special trace policy that can change in the middle of the
// ToolLoop. This is for LineFreehandController which uses a
// TracePolicy::Last for TwoPoints controller (Line-like tool)
// and then a TracePolicy::Accumulate for freehand (Pencil tool).
virtual bool handleTracePolicy() const { return false; }
virtual TracePolicy getTracePolicy() const { return TracePolicy::Accumulate; }
}; };
} // namespace tools } // namespace tools

View File

@ -99,6 +99,10 @@ private:
// Controls clicks for tools like line // Controls clicks for tools like line
class TwoPointsController : public MoveOriginCapability { class TwoPointsController : public MoveOriginCapability {
public: public:
TwoPointsController(const bool enableModifiers)
: m_enableModifiers(enableModifiers) {
}
void pressButton(Stroke& stroke, const Point& point) override { void pressButton(Stroke& stroke, const Point& point) override {
MoveOriginCapability::pressButton(stroke, point); MoveOriginCapability::pressButton(stroke, point);
@ -122,7 +126,8 @@ public:
stroke[1] = point; stroke[1] = point;
if (int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) { if (m_enableModifiers &&
(int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect))) {
int dx = stroke[1].x - m_first.x; int dx = stroke[1].x - m_first.x;
int dy = stroke[1].y - m_first.y; int dy = stroke[1].y - m_first.y;
int minsize = MIN(ABS(dx), ABS(dy)); int minsize = MIN(ABS(dx), ABS(dy));
@ -167,7 +172,8 @@ public:
stroke[0] = m_first; stroke[0] = m_first;
if (int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter)) { if (m_enableModifiers &&
(int(loop->getModifiers()) & int(ToolLoopModifiers::kFromCenter))) {
int rx = stroke[1].x - m_first.x; int rx = stroke[1].x - m_first.x;
int ry = stroke[1].y - m_first.y; int ry = stroke[1].y - m_first.y;
stroke[0].x = m_first.x - rx; stroke[0].x = m_first.x - rx;
@ -226,6 +232,7 @@ private:
} }
Point m_first; Point m_first;
bool m_enableModifiers;
}; };
// Controls clicks for tools like polygon // Controls clicks for tools like polygon
@ -379,5 +386,64 @@ private:
int m_clickCounter; int m_clickCounter;
}; };
// Controls the shift+click to draw a two-points line and then
// freehand until the mouse is released.
class LineFreehandController : public Controller {
public:
LineFreehandController() : m_twoPoints(false) { }
bool isFreehand() override { return true; }
gfx::Point getLastPoint() const override { return m_last; }
void prepareController(ToolLoop* loop) override {
m_controller = nullptr;
}
void pressButton(Stroke& stroke, const Point& point) override {
m_last = point;
if (m_controller == nullptr)
m_controller = &m_twoPoints;
else if (m_controller == &m_twoPoints) {
m_controller = &m_freehand;
return; // Don't send first pressButton() click to the freehand controller
}
m_controller->pressButton(stroke, point);
}
bool releaseButton(Stroke& stroke, const Point& point) override {
return false;
}
void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
m_last = point;
m_controller->movement(loop, stroke, point);
}
void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
m_controller->getStrokeToInterwine(input, output);
}
void getStatusBarText(const Stroke& stroke, std::string& text) override {
m_controller->getStatusBarText(stroke, text);
}
bool handleTracePolicy() const override {
return (m_controller == &m_twoPoints);
}
TracePolicy getTracePolicy() const override {
return TracePolicy::Last;
}
private:
Point m_last;
TwoPointsController m_twoPoints;
FreehandController m_freehand;
Controller* m_controller;
};
} // namespace tools } // namespace tools
} // namespace app } // namespace app

View File

@ -69,6 +69,13 @@ const char* WellKnownInks::MoveSlice = "move_slice";
const char* WellKnownInks::Blur = "blur"; const char* WellKnownInks::Blur = "blur";
const char* WellKnownInks::Jumble = "jumble"; const char* WellKnownInks::Jumble = "jumble";
const char* WellKnownControllers::Freehand = "freehand";
const char* WellKnownControllers::PointByPoint = "point_by_point";
const char* WellKnownControllers::OnePoints = "one_point";
const char* WellKnownControllers::TwoPoints = "two_points";
const char* WellKnownControllers::FourPoints = "four_points";
const char* WellKnownControllers::LineFreehand = "line_freehand";
const char* WellKnownIntertwiners::None = "none"; const char* WellKnownIntertwiners::None = "none";
const char* WellKnownIntertwiners::FirstPoint = "first_point"; const char* WellKnownIntertwiners::FirstPoint = "first_point";
const char* WellKnownIntertwiners::AsLines = "as_lines"; const char* WellKnownIntertwiners::AsLines = "as_lines";
@ -105,11 +112,12 @@ ToolBox::ToolBox()
m_inks[WellKnownInks::Blur] = new BlurInk(); m_inks[WellKnownInks::Blur] = new BlurInk();
m_inks[WellKnownInks::Jumble] = new JumbleInk(); m_inks[WellKnownInks::Jumble] = new JumbleInk();
m_controllers["freehand"] = new FreehandController(); m_controllers[WellKnownControllers::Freehand] = new FreehandController();
m_controllers["point_by_point"] = new PointByPointController(); m_controllers[WellKnownControllers::PointByPoint] = new PointByPointController();
m_controllers["one_point"] = new OnePointController(); m_controllers[WellKnownControllers::OnePoints] = new OnePointController();
m_controllers["two_points"] = new TwoPointsController(); m_controllers[WellKnownControllers::TwoPoints] = new TwoPointsController(true);
m_controllers["four_points"] = new FourPointsController(); m_controllers[WellKnownControllers::FourPoints] = new FourPointsController();
m_controllers[WellKnownControllers::LineFreehand] = new LineFreehandController();
m_pointshapers[WellKnownPointShapes::None] = new NonePointShape(); m_pointshapers[WellKnownPointShapes::None] = new NonePointShape();
m_pointshapers[WellKnownPointShapes::Pixel] = new PixelPointShape(); m_pointshapers[WellKnownPointShapes::Pixel] = new PixelPointShape();
@ -161,6 +169,11 @@ Ink* ToolBox::getInkById(const std::string& id)
return m_inks[id]; return m_inks[id];
} }
Controller* ToolBox::getControllerById(const std::string& id)
{
return m_controllers[id];
}
Intertwine* ToolBox::getIntertwinerById(const std::string& id) Intertwine* ToolBox::getIntertwinerById(const std::string& id)
{ {
return m_intertwiners[id]; return m_intertwiners[id];

View File

@ -52,6 +52,15 @@ namespace app {
extern const char* Jumble; extern const char* Jumble;
}; };
namespace WellKnownControllers {
extern const char* Freehand;
extern const char* PointByPoint;
extern const char* OnePoints;
extern const char* TwoPoints;
extern const char* FourPoints;
extern const char* LineFreehand;
};
namespace WellKnownIntertwiners { namespace WellKnownIntertwiners {
extern const char* None; extern const char* None;
extern const char* FirstPoint; extern const char* FirstPoint;
@ -92,6 +101,7 @@ namespace app {
Tool* getToolById(const std::string& id); Tool* getToolById(const std::string& id);
Ink* getInkById(const std::string& id); Ink* getInkById(const std::string& id);
Controller* getControllerById(const std::string& id);
Intertwine* getIntertwinerById(const std::string& id); Intertwine* getIntertwinerById(const std::string& id);
PointShape* getPointShapeById(const std::string& id); PointShape* getPointShapeById(const std::string& id);
int getGroupsCount() const { return m_groups.size(); } int getGroupsCount() const { return m_groups.size(); }

View File

@ -54,6 +54,7 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer)
// Prepare the ink // Prepare the ink
m_toolLoop->getInk()->prepareInk(m_toolLoop); m_toolLoop->getInk()->prepareInk(m_toolLoop);
m_toolLoop->getController()->prepareController(m_toolLoop);
m_toolLoop->getIntertwine()->prepareIntertwine(); m_toolLoop->getIntertwine()->prepareIntertwine();
m_toolLoop->getPointShape()->preparePointShape(m_toolLoop); m_toolLoop->getPointShape()->preparePointShape(m_toolLoop);
} }

View File

@ -39,10 +39,13 @@ namespace app {
using namespace ui; using namespace ui;
DrawingState::DrawingState(tools::ToolLoop* toolLoop) DrawingState::DrawingState(tools::ToolLoop* toolLoop,
: m_toolLoop(toolLoop) const DrawingType type)
: m_type(type)
, m_toolLoop(toolLoop)
, m_toolLoopManager(new tools::ToolLoopManager(toolLoop)) , m_toolLoopManager(new tools::ToolLoopManager(toolLoop))
, m_mouseMoveReceived(false) , m_mouseMoveReceived(false)
, m_mousePressedReceived(false)
{ {
} }
@ -51,7 +54,8 @@ DrawingState::~DrawingState()
destroyLoop(nullptr); destroyLoop(nullptr);
} }
void DrawingState::initToolLoop(Editor* editor, MouseMessage* msg) void DrawingState::initToolLoop(Editor* editor,
const tools::Pointer& pointer)
{ {
// Prepare preview image (the destination image will be our preview // Prepare preview image (the destination image will be our preview
// in the tool-loop time, so we can see what we are drawing) // in the tool-loop time, so we can see what we are drawing)
@ -65,35 +69,18 @@ void DrawingState::initToolLoop(Editor* editor, MouseMessage* msg)
static_cast<LayerImage*>(m_toolLoop->getLayer())->blendMode(): static_cast<LayerImage*>(m_toolLoop->getLayer())->blendMode():
BlendMode::NEG_BW)); BlendMode::NEG_BW));
tools::Pointer pointer;
bool movement = false;
if (m_toolLoop->getController()->isFreehand() &&
m_toolLoop->getInk()->isPaint() &&
(editor->getCustomizationDelegate()
->getPressedKeyAction(KeyContext::FreehandTool) & KeyAction::StraightLineFromLastPoint) == KeyAction::StraightLineFromLastPoint &&
editor->document()->lastDrawingPoint() != app::Document::NoLastDrawingPoint()) {
pointer = tools::Pointer(editor->document()->lastDrawingPoint(),
button_from_msg(msg));
movement = true;
}
else {
pointer = pointer_from_msg(editor, msg);
}
m_toolLoopManager->prepareLoop(pointer); m_toolLoopManager->prepareLoop(pointer);
m_toolLoopManager->pressButton(pointer); m_toolLoopManager->pressButton(pointer);
// This first movement is done when the user pressed Shift+click in
// a freehand tool to draw a straight line.
if (movement) {
pointer = pointer_from_msg(editor, msg);
m_toolLoopManager->movement(pointer);
}
editor->captureMouse(); editor->captureMouse();
} }
void DrawingState::sendMovementToToolLoop(const tools::Pointer& pointer)
{
ASSERT(m_toolLoopManager);
m_toolLoopManager->movement(pointer);
}
void DrawingState::notifyToolLoopModifiersChange(Editor* editor) void DrawingState::notifyToolLoopModifiersChange(Editor* editor)
{ {
if (!m_toolLoopManager->isCanceled()) if (!m_toolLoopManager->isCanceled())
@ -105,6 +92,8 @@ bool DrawingState::onMouseDown(Editor* editor, MouseMessage* msg)
// Drawing loop // Drawing loop
ASSERT(m_toolLoopManager != NULL); ASSERT(m_toolLoopManager != NULL);
m_mousePressedReceived = true;
// Notify the mouse button down to the tool loop manager. // Notify the mouse button down to the tool loop manager.
m_toolLoopManager->pressButton(pointer_from_msg(editor, msg)); m_toolLoopManager->pressButton(pointer_from_msg(editor, msg));
@ -195,8 +184,15 @@ bool DrawingState::onKeyDown(Editor* editor, KeyMessage* msg)
bool DrawingState::onKeyUp(Editor* editor, KeyMessage* msg) bool DrawingState::onKeyUp(Editor* editor, KeyMessage* msg)
{ {
if (msg->scancode() == ui::kKeyEsc) // Cancel loop pressing Esc key...
if (msg->scancode() == ui::kKeyEsc ||
// Cancel "Shift on freehand" line preview when the Shift key is
// released and the user didn't press the mouse button..
(m_type == DrawingType::LineFreehand &&
!m_mousePressedReceived &&
!editor->startStraightLineWithFreehandTool())) {
m_toolLoop->cancel(); m_toolLoop->cancel();
}
// The user might have canceled the tool loop pressing the 'Esc' key. // The user might have canceled the tool loop pressing the 'Esc' key.
destroyLoopIfCanceled(editor); destroyLoopIfCanceled(editor);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -12,13 +12,15 @@
namespace app { namespace app {
namespace tools { namespace tools {
class Pointer;
class ToolLoop; class ToolLoop;
class ToolLoopManager; class ToolLoopManager;
} }
class DrawingState : public StandbyState { class DrawingState : public StandbyState {
public: public:
DrawingState(tools::ToolLoop* loop); DrawingState(tools::ToolLoop* loop,
const DrawingType type);
virtual ~DrawingState(); virtual ~DrawingState();
virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override; virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override; virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
@ -33,13 +35,21 @@ namespace app {
// already drawing (viewing the real trace). // already drawing (viewing the real trace).
virtual bool requireBrushPreview() override { return false; } virtual bool requireBrushPreview() override { return false; }
void initToolLoop(Editor* editor, ui::MouseMessage* msg); void initToolLoop(Editor* editor,
const tools::Pointer& pointer);
// Used to send a movement() to the ToolLoopManager when
// Shift+brush tool is used to paint a line.
void sendMovementToToolLoop(const tools::Pointer& pointer);
void notifyToolLoopModifiersChange(Editor* editor); void notifyToolLoopModifiersChange(Editor* editor);
private: private:
void destroyLoopIfCanceled(Editor* editor); void destroyLoopIfCanceled(Editor* editor);
void destroyLoop(Editor* editor); void destroyLoop(Editor* editor);
DrawingType m_type;
// The tool-loop. // The tool-loop.
tools::ToolLoop* m_toolLoop; tools::ToolLoop* m_toolLoop;
@ -55,6 +65,12 @@ namespace app {
// DrawingState. It's used to restore the last drawing position in // DrawingState. It's used to restore the last drawing position in
// case this stroke is canceled. // case this stroke is canceled.
gfx::Point m_lastPoint; gfx::Point m_lastPoint;
// Used to know if the button was pressed after the DrawingState
// was initialized. In this way we can cancel the ToolLoop if the
// Shift press is used to draw a line, but then released without a
// mouse click.
bool m_mousePressedReceived;
}; };
} // namespace app } // namespace app

View File

@ -24,6 +24,7 @@
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/tools/active_tool.h" #include "app/tools/active_tool.h"
#include "app/tools/controller.h"
#include "app/tools/ink.h" #include "app/tools/ink.h"
#include "app/tools/tool.h" #include "app/tools/tool.h"
#include "app/tools/tool_box.h" #include "app/tools/tool_box.h"
@ -59,6 +60,7 @@
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <limits>
namespace app { namespace app {
@ -1512,6 +1514,18 @@ app::Color Editor::getColorByPosition(const gfx::Point& mousePos)
return app::Color::fromMask(); return app::Color::fromMask();
} }
bool Editor::startStraightLineWithFreehandTool()
{
tools::Tool* tool = App::instance()->activeToolManager()->selectedTool();
return
(tool &&
tool->getController(0)->isFreehand() &&
tool->getInk(0)->isPaint() &&
(getCustomizationDelegate()
->getPressedKeyAction(KeyContext::FreehandTool) & KeyAction::StraightLineFromLastPoint) == KeyAction::StraightLineFromLastPoint &&
document()->lastDrawingPoint() != app::Document::NoLastDrawingPoint());
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Message handler for the editor // Message handler for the editor

View File

@ -254,6 +254,10 @@ namespace app {
void setTagFocusBand(int value) { m_tagFocusBand = value; } void setTagFocusBand(int value) { m_tagFocusBand = value; }
int tagFocusBand() const { return m_tagFocusBand; } int tagFocusBand() const { return m_tagFocusBand; }
// Returns true if the Shift key to draw straight lines with a
// freehand tool is pressed.
bool startStraightLineWithFreehandTool();
protected: protected:
bool onProcessMessage(ui::Message* msg) override; bool onProcessMessage(ui::Message* msg) override;
void onSizeHint(ui::SizeHintEvent& ev) override; void onSizeHint(ui::SizeHintEvent& ev) override;

View File

@ -28,6 +28,7 @@
#include "app/ui/editor/drawing_state.h" #include "app/ui/editor/drawing_state.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_customization_delegate.h" #include "app/ui/editor/editor_customization_delegate.h"
#include "app/ui/editor/glue.h"
#include "app/ui/editor/handle_type.h" #include "app/ui/editor/handle_type.h"
#include "app/ui/editor/moving_cel_state.h" #include "app/ui/editor/moving_cel_state.h"
#include "app/ui/editor/moving_pixels_state.h" #include "app/ui/editor/moving_pixels_state.h"
@ -320,20 +321,9 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
if (layerEdges) if (layerEdges)
layerEdgesOption(false); layerEdgesOption(false);
// We need to clear and redraw the brush boundaries after the startDrawingState(editor,
// first mouse pressed/point shape if drawn. This is to avoid DrawingType::Regular,
// graphical glitches (invalid areas in the ToolLoop's src/dst pointer_from_msg(editor, msg));
// images).
HideBrushPreview hide(editor->brushPreview());
tools::ToolLoop* toolLoop = create_tool_loop(editor, context);
if (toolLoop) {
EditorStatePtr newState(new DrawingState(toolLoop));
editor->setState(newState);
static_cast<DrawingState*>(newState.get())
->initToolLoop(editor, msg);
}
// Restore layer edges // Restore layer edges
if (layerEdges) if (layerEdges)
@ -497,6 +487,22 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg) bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
{ {
// Start line preview with shift key
if (editor->startStraightLineWithFreehandTool()) {
DrawingState* drawingState =
startDrawingState(editor,
DrawingType::LineFreehand,
tools::Pointer(
editor->document()->lastDrawingPoint(),
tools::Pointer::Left));
if (drawingState) {
drawingState->sendMovementToToolLoop(
tools::Pointer(
editor->screenToEditor(ui::get_mouse_position()),
tools::Pointer::Left));
return true;
}
}
return false; return false;
} }
@ -586,6 +592,35 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
return true; return true;
} }
DrawingState* StandbyState::startDrawingState(Editor* editor,
const DrawingType drawingType,
const tools::Pointer& pointer)
{
// We need to clear and redraw the brush boundaries after the
// first mouse pressed/point shape if drawn. This is to avoid
// graphical glitches (invalid areas in the ToolLoop's src/dst
// images).
HideBrushPreview hide(editor->brushPreview());
tools::ToolLoop* toolLoop = create_tool_loop(
editor,
UIContext::instance(),
(drawingType == DrawingType::LineFreehand));
if (!toolLoop)
return nullptr;
EditorStatePtr newState(
new DrawingState(toolLoop,
drawingType));
editor->setState(newState);
static_cast<DrawingState*>(newState.get())
->initToolLoop(editor,
pointer);
return static_cast<DrawingState*>(newState.get());
}
Transformation StandbyState::getTransformation(Editor* editor) Transformation StandbyState::getTransformation(Editor* editor)
{ {
Transformation t = editor->document()->getTransformation(); Transformation t = editor->document()->getTransformation();

View File

@ -17,12 +17,16 @@
namespace app { namespace app {
namespace tools { namespace tools {
class Ink; class Ink;
class Pointer;
} }
class DrawingState;
class TransformHandles; class TransformHandles;
class StandbyState : public StateWithWheelBehavior { class StandbyState : public StateWithWheelBehavior {
public: public:
enum class DrawingType { Regular, LineFreehand };
StandbyState(); StandbyState();
virtual ~StandbyState(); virtual ~StandbyState();
virtual void onEnterState(Editor* editor) override; virtual void onEnterState(Editor* editor) override;
@ -76,6 +80,9 @@ namespace app {
}; };
private: private:
DrawingState* startDrawingState(Editor* editor,
const DrawingType drawingType,
const tools::Pointer& pointer);
void transformSelection(Editor* editor, ui::MouseMessage* msg, HandleType handle); void transformSelection(Editor* editor, ui::MouseMessage* msg, HandleType handle);
void onPivotChange(Editor* editor); void onPivotChange(Editor* editor);
gfx::Rect resizeCelBounds(Editor* editor) const; gfx::Rect resizeCelBounds(Editor* editor) const;

View File

@ -97,6 +97,7 @@ public:
Layer* layer, Layer* layer,
tools::Tool* tool, tools::Tool* tool,
tools::Ink* ink, tools::Ink* ink,
tools::Controller* controller,
Document* document, Document* document,
tools::ToolLoop::Button button, tools::ToolLoop::Button button,
const app::Color& fgColor, const app::Color& fgColor,
@ -116,7 +117,7 @@ public:
, m_contiguous(m_toolPref.contiguous()) , m_contiguous(m_toolPref.contiguous())
, m_button(button) , m_button(button)
, m_ink(ink->clone()) , m_ink(ink->clone())
, m_controller(m_tool->getController(m_button)) , m_controller(controller)
, m_pointShape(m_tool->getPointShape(m_button)) , m_pointShape(m_tool->getPointShape(m_button))
, m_intertwine(m_tool->getIntertwine(m_button)) , m_intertwine(m_tool->getIntertwine(m_button))
, m_tracePolicy(m_tool->getTracePolicy(m_button)) , m_tracePolicy(m_tool->getTracePolicy(m_button))
@ -243,7 +244,12 @@ public:
tools::Controller* getController() override { return m_controller; } tools::Controller* getController() override { return m_controller; }
tools::PointShape* getPointShape() override { return m_pointShape; } tools::PointShape* getPointShape() override { return m_pointShape; }
tools::Intertwine* getIntertwine() override { return m_intertwine; } tools::Intertwine* getIntertwine() override { return m_intertwine; }
tools::TracePolicy getTracePolicy() override { return m_tracePolicy; } tools::TracePolicy getTracePolicy() override {
if (m_controller->handleTracePolicy())
return m_controller->getTracePolicy();
else
return m_tracePolicy;
}
tools::Symmetry* getSymmetry() override { return m_symmetry.get(); } tools::Symmetry* getSymmetry() override { return m_symmetry.get(); }
doc::Remap* getShadingRemap() override { return m_shadingRemap; } doc::Remap* getShadingRemap() override { return m_shadingRemap; }
@ -293,6 +299,7 @@ class ToolLoopImpl : public ToolLoopBase {
Transaction m_transaction; Transaction m_transaction;
ExpandCelCanvas* m_expandCelCanvas; ExpandCelCanvas* m_expandCelCanvas;
Image* m_floodfillSrcImage; Image* m_floodfillSrcImage;
bool m_saveLastPoint;
public: public:
ToolLoopImpl(Editor* editor, ToolLoopImpl(Editor* editor,
@ -300,12 +307,21 @@ public:
Context* context, Context* context,
tools::Tool* tool, tools::Tool* tool,
tools::Ink* ink, tools::Ink* ink,
tools::Controller* controller,
Document* document, Document* document,
tools::ToolLoop::Button button, tools::ToolLoop::Button button,
const app::Color& fgColor, const app::Color& fgColor,
const app::Color& bgColor) const app::Color& bgColor,
: ToolLoopBase(editor, layer, tool, ink, document, const bool saveLastPoint)
button, fgColor, bgColor) : ToolLoopBase(editor,
layer,
tool,
ink,
controller,
document,
button,
fgColor,
bgColor)
, m_context(context) , m_context(context)
, m_canceled(false) , m_canceled(false)
, m_transaction(m_context, , m_transaction(m_context,
@ -318,6 +334,7 @@ public:
ModifyDocument)) ModifyDocument))
, m_expandCelCanvas(nullptr) , m_expandCelCanvas(nullptr)
, m_floodfillSrcImage(nullptr) , m_floodfillSrcImage(nullptr)
, m_saveLastPoint(saveLastPoint)
{ {
ASSERT(m_context->activeDocument() == m_editor->document()); ASSERT(m_context->activeDocument() == m_editor->document());
@ -414,15 +431,16 @@ public:
bool redraw = false; bool redraw = false;
if (!m_canceled) { if (!m_canceled) {
// Paint ink
if (getInk()->isPaint()) {
// Freehand changes the last point // Freehand changes the last point
if (getController()->isFreehand()) if (m_saveLastPoint) {
m_transaction.execute( m_transaction.execute(
new cmd::SetLastPoint( new cmd::SetLastPoint(
m_document, m_document,
getController()->getLastPoint())); getController()->getLastPoint()));
}
// Paint ink
if (getInk()->isPaint()) {
try { try {
ContextReader reader(m_context, 500); ContextReader reader(m_context, 500);
ContextWriter writer(reader, 500); ContextWriter writer(reader, 500);
@ -512,12 +530,15 @@ public:
}; };
tools::ToolLoop* create_tool_loop(Editor* editor, Context* context) tools::ToolLoop* create_tool_loop(
Editor* editor,
Context* context,
const bool convertLineToFreehand)
{ {
tools::Tool* current_tool = editor->getCurrentEditorTool(); tools::Tool* tool = editor->getCurrentEditorTool();
tools::Ink* current_ink = editor->getCurrentEditorInk(); tools::Ink* ink = editor->getCurrentEditorInk();
if (!current_tool || !current_ink) if (!tool || !ink)
return NULL; return nullptr;
Layer* layer; Layer* layer;
@ -530,8 +551,8 @@ tools::ToolLoop* create_tool_loop(Editor* editor, Context* context)
// Anyway this cannot be used in 'magic wand' tool (isSelection + // Anyway this cannot be used in 'magic wand' tool (isSelection +
// isFloodFill) because we need the original layer source // isFloodFill) because we need the original layer source
// image/pixels to stop the flood-fill algorithm. // image/pixels to stop the flood-fill algorithm.
if (current_ink->isSelection() && if (ink->isSelection() &&
!current_tool->getPointShape(editor->isSecondaryButton() ? 1: 0)->isFloodFill()) { !tool->getPointShape(editor->isSecondaryButton() ? 1: 0)->isFloodFill()) {
layer = nullptr; layer = nullptr;
} }
else { else {
@ -578,13 +599,30 @@ tools::ToolLoop* create_tool_loop(Editor* editor, Context* context)
// Create the new tool loop // Create the new tool loop
try { try {
tools::ToolLoop::Button button =
(!editor->isSecondaryButton() ? tools::ToolLoop::Left:
tools::ToolLoop::Right);
tools::Controller* controller =
(convertLineToFreehand ?
App::instance()->toolBox()->getControllerById(
tools::WellKnownControllers::LineFreehand):
tool->getController(button));
const bool saveLastPoint =
(ink->isPaint() &&
(controller->isFreehand() ||
convertLineToFreehand));
return new ToolLoopImpl( return new ToolLoopImpl(
editor, layer, context, editor, layer, context,
current_tool, tool,
current_ink, ink,
controller,
editor->document(), editor->document(),
!editor->isSecondaryButton() ? tools::ToolLoop::Left: tools::ToolLoop::Right, button,
fg, bg); fg, bg,
saveLastPoint);
} }
catch (const std::exception& ex) { catch (const std::exception& ex) {
Alert::show(PACKAGE Alert::show(PACKAGE
@ -612,8 +650,15 @@ public:
const app::Color& bgColor, const app::Color& bgColor,
Image* image, Image* image,
const gfx::Point& celOrigin) const gfx::Point& celOrigin)
: ToolLoopBase(editor, editor->layer(), tool, ink, document, : ToolLoopBase(editor,
tools::ToolLoop::Left, fgColor, bgColor) editor->layer(),
tool,
ink,
tool->getController(tools::ToolLoop::Left),
document,
tools::ToolLoop::Left,
fgColor,
bgColor)
, m_image(image) , m_image(image)
{ {
m_celOrigin = celOrigin; m_celOrigin = celOrigin;
@ -661,9 +706,9 @@ tools::ToolLoop* create_tool_loop_preview(
Editor* editor, Image* image, Editor* editor, Image* image,
const gfx::Point& celOrigin) const gfx::Point& celOrigin)
{ {
tools::Tool* current_tool = editor->getCurrentEditorTool(); tools::Tool* tool = editor->getCurrentEditorTool();
tools::Ink* current_ink = editor->getCurrentEditorInk(); tools::Ink* ink = editor->getCurrentEditorInk();
if (!current_tool || !current_ink) if (!tool || !ink)
return NULL; return NULL;
Layer* layer = editor->layer(); Layer* layer = editor->layer();
@ -685,8 +730,8 @@ tools::ToolLoop* create_tool_loop_preview(
try { try {
return new PreviewToolLoopImpl( return new PreviewToolLoopImpl(
editor, editor,
current_tool, tool,
current_ink, ink,
editor->document(), editor->document(),
fg, bg, image, celOrigin); fg, bg, image, celOrigin);
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2001-2015 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -24,10 +24,13 @@ namespace app {
} }
tools::ToolLoop* create_tool_loop( tools::ToolLoop* create_tool_loop(
Editor* editor, Context* context); Editor* editor,
Context* context,
const bool convertLineToFreehand);
tools::ToolLoop* create_tool_loop_preview( tools::ToolLoop* create_tool_loop_preview(
Editor* editor, doc::Image* image, Editor* editor,
doc::Image* image,
const gfx::Point& celOrigin); const gfx::Point& celOrigin);
} // namespace app } // namespace app