2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2021-01-15 00:17:30 +08:00
|
|
|
// Copyright (C) 2019-2021 Igara Studio S.A.
|
2018-08-09 04:27:26 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/editor/tool_loop_impl.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/app.h"
|
2017-03-07 06:27:43 +08:00
|
|
|
#include "app/cmd/add_slice.h"
|
2017-06-16 23:18:22 +08:00
|
|
|
#include "app/cmd/set_last_point.h"
|
2015-01-19 09:05:33 +08:00
|
|
|
#include "app/cmd/set_mask.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
#include "app/color.h"
|
|
|
|
#include "app/color_utils.h"
|
2015-04-10 07:05:02 +08:00
|
|
|
#include "app/console.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/context.h"
|
|
|
|
#include "app/context_access.h"
|
2018-07-07 13:55:27 +08:00
|
|
|
#include "app/doc_undo.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "app/i18n/strings.h"
|
2014-12-09 01:57:56 +08:00
|
|
|
#include "app/modules/gui.h"
|
2015-08-27 03:48:01 +08:00
|
|
|
#include "app/modules/palettes.h"
|
2015-02-15 20:48:38 +08:00
|
|
|
#include "app/pref/preferences.h"
|
2014-12-09 01:57:56 +08:00
|
|
|
#include "app/tools/controller.h"
|
2015-05-22 03:14:26 +08:00
|
|
|
#include "app/tools/freehand_algorithm.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/tools/ink.h"
|
2015-04-27 02:59:28 +08:00
|
|
|
#include "app/tools/point_shape.h"
|
2021-01-15 00:17:30 +08:00
|
|
|
#include "app/tools/symmetry.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/tools/tool.h"
|
|
|
|
#include "app/tools/tool_box.h"
|
|
|
|
#include "app/tools/tool_loop.h"
|
2019-06-29 03:33:11 +08:00
|
|
|
#include "app/tx.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/color_bar.h"
|
2015-04-29 05:21:33 +08:00
|
|
|
#include "app/ui/context_bar.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/editor/editor.h"
|
2020-09-02 00:02:25 +08:00
|
|
|
#include "app/ui/editor/editor_observer.h"
|
2015-04-29 05:21:33 +08:00
|
|
|
#include "app/ui/main_window.h"
|
2018-09-13 21:53:14 +08:00
|
|
|
#include "app/ui/optional_alert.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/status_bar.h"
|
2019-02-21 19:20:00 +08:00
|
|
|
#include "app/ui_context.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/util/expand_cel_canvas.h"
|
2020-06-09 03:19:00 +08:00
|
|
|
#include "app/util/layer_utils.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/cel.h"
|
2015-04-27 02:59:28 +08:00
|
|
|
#include "doc/image.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/layer.h"
|
|
|
|
#include "doc/mask.h"
|
2015-08-27 03:48:01 +08:00
|
|
|
#include "doc/palette.h"
|
|
|
|
#include "doc/palette_picks.h"
|
|
|
|
#include "doc/remap.h"
|
2017-03-24 07:23:25 +08:00
|
|
|
#include "doc/slice.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/sprite.h"
|
2017-10-18 05:00:45 +08:00
|
|
|
#include "fmt/format.h"
|
2017-05-24 07:41:30 +08:00
|
|
|
#include "render/dithering.h"
|
2019-11-06 00:31:53 +08:00
|
|
|
#include "render/rasterize.h"
|
2016-05-04 02:31:27 +08:00
|
|
|
#include "render/render.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "ui/ui.h"
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2020-03-07 02:18:10 +08:00
|
|
|
#include <algorithm>
|
2021-03-29 23:34:19 +08:00
|
|
|
#include <memory>
|
2020-03-07 02:18:10 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
|
|
|
|
2012-06-18 09:02:54 +08:00
|
|
|
using namespace ui;
|
|
|
|
|
2020-05-22 22:15:04 +08:00
|
|
|
#ifdef ENABLE_UI
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
static void fill_toolloop_params_from_tool_preferences(ToolLoopParams& params)
|
|
|
|
{
|
|
|
|
ToolPreferences& toolPref =
|
|
|
|
Preferences::instance().tool(params.tool);
|
|
|
|
|
|
|
|
params.inkType = toolPref.ink();
|
|
|
|
params.opacity = toolPref.opacity();
|
|
|
|
params.tolerance = toolPref.tolerance();
|
|
|
|
params.contiguous = toolPref.contiguous();
|
|
|
|
params.freehandAlgorithm = toolPref.freehandAlgorithm();
|
|
|
|
}
|
|
|
|
|
2020-05-22 22:15:04 +08:00
|
|
|
#endif // ENABLE_UI
|
|
|
|
|
2015-04-27 02:59:28 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
2015-05-22 03:00:27 +08:00
|
|
|
// Common properties between drawing/preview ToolLoop impl
|
2015-04-27 02:59:28 +08:00
|
|
|
|
2015-08-27 03:48:01 +08:00
|
|
|
class ToolLoopBase : public tools::ToolLoop {
|
2015-05-22 03:00:27 +08:00
|
|
|
protected:
|
2012-01-06 06:45:03 +08:00
|
|
|
Editor* m_editor;
|
|
|
|
tools::Tool* m_tool;
|
2015-04-29 05:21:33 +08:00
|
|
|
BrushRef m_brush;
|
2020-04-22 09:27:49 +08:00
|
|
|
BrushRef m_origBrush;
|
2017-10-06 21:23:03 +08:00
|
|
|
gfx::Point m_oldPatternOrigin;
|
2018-07-07 22:54:44 +08:00
|
|
|
Doc* m_document;
|
2012-01-06 06:45:03 +08:00
|
|
|
Sprite* m_sprite;
|
|
|
|
Layer* m_layer;
|
2014-12-29 07:39:11 +08:00
|
|
|
frame_t m_frame;
|
2015-11-04 21:33:23 +08:00
|
|
|
RgbMap* m_rgbMap;
|
2015-02-15 20:48:38 +08:00
|
|
|
DocumentPreferences& m_docPref;
|
2015-05-19 03:53:25 +08:00
|
|
|
ToolPreferences& m_toolPref;
|
2012-01-06 06:45:03 +08:00
|
|
|
int m_opacity;
|
|
|
|
int m_tolerance;
|
2014-08-07 11:07:24 +08:00
|
|
|
bool m_contiguous;
|
2020-02-16 23:42:29 +08:00
|
|
|
bool m_snapToGrid;
|
|
|
|
bool m_isSelectingTiles;
|
2020-06-26 06:21:35 +08:00
|
|
|
doc::Grid m_grid;
|
2019-10-11 02:08:59 +08:00
|
|
|
gfx::Rect m_gridBounds;
|
2015-11-03 03:44:17 +08:00
|
|
|
gfx::Point m_celOrigin;
|
2020-09-01 21:28:21 +08:00
|
|
|
gfx::Point m_mainTilePos;
|
2012-01-06 06:45:03 +08:00
|
|
|
gfx::Point m_speed;
|
|
|
|
tools::ToolLoop::Button m_button;
|
2018-08-09 04:27:26 +08:00
|
|
|
std::unique_ptr<tools::Ink> m_ink;
|
2015-05-22 03:14:26 +08:00
|
|
|
tools::Controller* m_controller;
|
|
|
|
tools::PointShape* m_pointShape;
|
|
|
|
tools::Intertwine* m_intertwine;
|
|
|
|
tools::TracePolicy m_tracePolicy;
|
2018-08-09 04:27:26 +08:00
|
|
|
std::unique_ptr<tools::Symmetry> m_symmetry;
|
2020-05-06 22:20:52 +08:00
|
|
|
Shade m_shade;
|
2018-08-09 04:27:26 +08:00
|
|
|
std::unique_ptr<doc::Remap> m_shadingRemap;
|
2020-08-20 07:17:50 +08:00
|
|
|
bool m_tilesMode;
|
2016-05-03 05:42:02 +08:00
|
|
|
app::ColorTarget m_colorTarget;
|
2015-05-19 03:53:25 +08:00
|
|
|
doc::color_t m_fgColor;
|
|
|
|
doc::color_t m_bgColor;
|
|
|
|
doc::color_t m_primaryColor;
|
|
|
|
doc::color_t m_secondaryColor;
|
2020-04-23 22:30:36 +08:00
|
|
|
tools::DynamicsOptions m_dynamics;
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2021-04-23 00:00:13 +08:00
|
|
|
// Modifiers that can be used with scripts
|
|
|
|
tools::ToolLoopModifiers m_staticToolModifiers;
|
|
|
|
|
2020-09-01 21:28:21 +08:00
|
|
|
// Visible region (on the screen) of the all editors showing the
|
|
|
|
// given document.
|
|
|
|
gfx::Region m_allVisibleRgn;
|
|
|
|
|
2021-04-23 21:09:56 +08:00
|
|
|
// Helper struct to store an image's area that will be affected by the stroke
|
|
|
|
// point at the specified position of the original image.
|
|
|
|
struct SavedArea {
|
|
|
|
doc::ImageRef img;
|
|
|
|
// Original stroke point position.
|
|
|
|
tools::Stroke::Pt pos;
|
|
|
|
// Area of the original image that was saved into img.
|
|
|
|
gfx::Rect r;
|
|
|
|
};
|
|
|
|
// Holds the areas saved by savePointshapeStrokePtArea method and restored by
|
|
|
|
// restoreLastPts method.
|
|
|
|
std::vector<SavedArea> m_savedAreas;
|
2021-06-11 04:05:23 +08:00
|
|
|
// When a SavedArea is restored we add its Rect to this Region, then we use
|
|
|
|
// this to expand the modified region when editing a tilemap manually.
|
|
|
|
gfx::Region m_restoredRegion;
|
2021-04-23 21:09:56 +08:00
|
|
|
// Last point index.
|
|
|
|
int m_lastPti;
|
|
|
|
|
2021-05-29 02:46:13 +08:00
|
|
|
app::TiledModeHelper m_tiledModeHelper;
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
public:
|
2020-06-26 06:21:35 +08:00
|
|
|
ToolLoopBase(Editor* editor,
|
2020-08-19 03:13:00 +08:00
|
|
|
Site& site, const doc::Grid& grid,
|
2020-05-20 07:11:47 +08:00
|
|
|
ToolLoopParams& params)
|
2012-01-06 06:45:03 +08:00
|
|
|
: m_editor(editor)
|
2020-05-20 07:11:47 +08:00
|
|
|
, m_tool(params.tool)
|
|
|
|
, m_brush(params.brush)
|
|
|
|
, m_origBrush(params.brush)
|
2017-10-06 21:23:03 +08:00
|
|
|
, m_oldPatternOrigin(m_brush->patternOrigin())
|
2019-03-16 04:03:02 +08:00
|
|
|
, m_document(site.document())
|
|
|
|
, m_sprite(site.sprite())
|
|
|
|
, m_layer(site.layer())
|
|
|
|
, m_frame(site.frame())
|
2015-11-04 21:33:23 +08:00
|
|
|
, m_rgbMap(nullptr)
|
2015-05-19 03:53:25 +08:00
|
|
|
, m_docPref(Preferences::instance().document(m_document))
|
|
|
|
, m_toolPref(Preferences::instance().tool(m_tool))
|
2020-05-20 07:11:47 +08:00
|
|
|
, m_opacity(params.opacity)
|
|
|
|
, m_tolerance(params.tolerance)
|
|
|
|
, m_contiguous(params.contiguous)
|
2020-02-16 23:42:29 +08:00
|
|
|
, m_snapToGrid(m_docPref.grid.snap())
|
|
|
|
, m_isSelectingTiles(false)
|
2020-08-19 03:13:00 +08:00
|
|
|
, m_grid(grid)
|
|
|
|
, m_gridBounds(grid.origin(), grid.tileSize())
|
2020-09-01 21:28:21 +08:00
|
|
|
#ifdef ENABLE_UI
|
|
|
|
, m_mainTilePos(editor ? -editor->mainTilePosition(): gfx::Point(0, 0))
|
|
|
|
#endif
|
2020-05-20 07:11:47 +08:00
|
|
|
, m_button(params.button)
|
|
|
|
, m_ink(params.ink->clone())
|
|
|
|
, m_controller(params.controller)
|
2015-05-22 03:14:26 +08:00
|
|
|
, m_pointShape(m_tool->getPointShape(m_button))
|
|
|
|
, m_intertwine(m_tool->getIntertwine(m_button))
|
|
|
|
, m_tracePolicy(m_tool->getTracePolicy(m_button))
|
2015-10-27 04:51:32 +08:00
|
|
|
, m_symmetry(nullptr)
|
2020-08-20 07:17:50 +08:00
|
|
|
, m_tilesMode(site.tilemapMode() == TilemapMode::Tiles)
|
|
|
|
, m_colorTarget(m_tilesMode ? ColorTarget(ColorTarget::BackgroundLayer,
|
|
|
|
IMAGE_TILEMAP, 0):
|
2020-06-26 06:21:35 +08:00
|
|
|
m_layer ? ColorTarget(m_layer):
|
2016-05-03 05:42:02 +08:00
|
|
|
ColorTarget(ColorTarget::BackgroundLayer,
|
|
|
|
m_sprite->pixelFormat(),
|
|
|
|
m_sprite->transparentColor()))
|
2020-05-20 07:11:47 +08:00
|
|
|
, m_fgColor(color_utils::color_for_target_mask(params.fg, m_colorTarget))
|
|
|
|
, m_bgColor(color_utils::color_for_target_mask(params.bg, m_colorTarget))
|
|
|
|
, m_primaryColor(m_button == tools::ToolLoop::Left ? m_fgColor: m_bgColor)
|
|
|
|
, m_secondaryColor(m_button == tools::ToolLoop::Left ? m_bgColor: m_fgColor)
|
2021-04-23 00:00:13 +08:00
|
|
|
, m_staticToolModifiers(params.modifiers)
|
2021-05-29 02:46:13 +08:00
|
|
|
, m_tiledModeHelper(m_docPref.tiled.mode(), m_sprite)
|
2015-05-22 03:00:27 +08:00
|
|
|
{
|
2020-05-20 07:11:47 +08:00
|
|
|
ASSERT(m_tool);
|
|
|
|
ASSERT(m_ink);
|
|
|
|
ASSERT(m_controller);
|
|
|
|
|
2020-08-20 07:17:50 +08:00
|
|
|
if (m_tilesMode) {
|
2020-10-10 04:22:57 +08:00
|
|
|
// Use FloodFillPointShape or TilePointShape in tiles mode
|
|
|
|
if (!m_pointShape->isFloodFill()) {
|
|
|
|
m_pointShape = App::instance()->toolBox()->getPointShapeById(
|
|
|
|
tools::WellKnownPointShapes::Tile);
|
|
|
|
}
|
2020-06-30 03:37:14 +08:00
|
|
|
|
|
|
|
// In selection ink, we need the Pixels tilemap mode so
|
|
|
|
// ExpandCelCanvas uses the whole canvas for the selection
|
|
|
|
// preview.
|
|
|
|
//
|
|
|
|
// TODO in the future we could improve this, using 1) a special
|
|
|
|
// tilemap layer to preview the selection, or 2) using a
|
|
|
|
// path to show the selection (so there is no preview layer
|
|
|
|
// at all and nor ExpandCelCanvas)
|
|
|
|
if (m_ink->isSelection())
|
|
|
|
site.tilemapMode(TilemapMode::Pixels);
|
2020-06-26 06:21:35 +08:00
|
|
|
}
|
|
|
|
|
2020-04-23 22:30:36 +08:00
|
|
|
#ifdef ENABLE_UI // TODO add dynamics support when UI is not enabled
|
2020-04-24 05:03:34 +08:00
|
|
|
if (m_controller->isFreehand() &&
|
2020-05-09 06:44:29 +08:00
|
|
|
!m_pointShape->isFloodFill() &&
|
|
|
|
App::instance()->contextBar()) {
|
2020-04-23 22:30:36 +08:00
|
|
|
m_dynamics = App::instance()->contextBar()->getDynamics();
|
2020-04-24 05:03:34 +08:00
|
|
|
}
|
2020-04-23 22:30:36 +08:00
|
|
|
#endif
|
|
|
|
|
2021-04-23 21:09:56 +08:00
|
|
|
if (m_tracePolicy == tools::TracePolicy::Accumulate) {
|
2016-04-23 00:19:06 +08:00
|
|
|
tools::ToolBox* toolbox = App::instance()->toolBox();
|
2015-05-22 03:14:26 +08:00
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
switch (params.freehandAlgorithm) {
|
2015-05-22 03:14:26 +08:00
|
|
|
case tools::FreehandAlgorithm::DEFAULT:
|
|
|
|
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines);
|
|
|
|
break;
|
|
|
|
case tools::FreehandAlgorithm::PIXEL_PERFECT:
|
|
|
|
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsPixelPerfect);
|
|
|
|
break;
|
|
|
|
case tools::FreehandAlgorithm::DOTS:
|
|
|
|
m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::None);
|
|
|
|
break;
|
|
|
|
}
|
2020-04-23 07:17:14 +08:00
|
|
|
|
|
|
|
// Use overlap trace policy for dynamic gradient
|
2020-04-23 22:30:36 +08:00
|
|
|
if (m_dynamics.isDynamic() &&
|
|
|
|
m_dynamics.gradient != tools::DynamicSensor::Static &&
|
2020-05-08 06:32:48 +08:00
|
|
|
m_controller->isFreehand() &&
|
|
|
|
!m_ink->isEraser()) {
|
2020-04-23 07:17:14 +08:00
|
|
|
// Use overlap trace policy to accumulate changes of colors
|
|
|
|
// between stroke points.
|
|
|
|
//
|
|
|
|
// TODO this is connected with a condition in tools::PaintInk::prepareInk()
|
|
|
|
m_tracePolicy = tools::TracePolicy::Overlap;
|
|
|
|
}
|
2015-05-22 03:14:26 +08:00
|
|
|
}
|
2015-08-14 00:26:52 +08:00
|
|
|
|
2015-10-27 04:51:32 +08:00
|
|
|
// Symmetry mode
|
2015-10-29 07:00:18 +08:00
|
|
|
if (Preferences::instance().symmetryMode.enabled()) {
|
2021-01-15 00:17:30 +08:00
|
|
|
if (m_docPref.symmetry.mode() != gen::SymmetryMode::NONE)
|
|
|
|
m_symmetry.reset(new tools::Symmetry(m_docPref.symmetry.mode(),
|
|
|
|
m_docPref.symmetry.xAxis(),
|
|
|
|
m_docPref.symmetry.yAxis()));
|
2015-10-27 04:51:32 +08:00
|
|
|
}
|
|
|
|
|
2015-08-14 00:26:52 +08:00
|
|
|
// Ignore opacity for these inks
|
2020-05-20 07:11:47 +08:00
|
|
|
if (!tools::inkHasOpacity(params.inkType) &&
|
2015-09-30 19:59:32 +08:00
|
|
|
m_brush->type() != kImageBrushType &&
|
|
|
|
!m_ink->isEffect()) {
|
2015-08-14 00:26:52 +08:00
|
|
|
m_opacity = 255;
|
2015-08-14 02:41:37 +08:00
|
|
|
}
|
2015-08-27 03:48:01 +08:00
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI // TODO add support when UI is not enabled
|
2020-05-20 07:11:47 +08:00
|
|
|
if (params.inkType == tools::InkType::SHADING) {
|
2020-05-06 22:20:52 +08:00
|
|
|
m_shade = App::instance()->contextBar()->getShade();
|
2015-08-27 03:48:01 +08:00
|
|
|
m_shadingRemap.reset(
|
2016-04-23 00:19:06 +08:00
|
|
|
App::instance()->contextBar()->createShadeRemap(
|
2020-05-20 07:11:47 +08:00
|
|
|
m_button == tools::ToolLoop::Left));
|
2015-08-27 03:48:01 +08:00
|
|
|
}
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif
|
2020-09-01 21:28:21 +08:00
|
|
|
|
|
|
|
#ifdef ENABLE_UI
|
2020-09-02 00:02:25 +08:00
|
|
|
updateAllVisibleRegion();
|
|
|
|
#endif
|
2015-05-22 03:00:27 +08:00
|
|
|
}
|
|
|
|
|
2017-10-06 21:23:03 +08:00
|
|
|
~ToolLoopBase() {
|
2020-04-22 09:27:49 +08:00
|
|
|
m_origBrush->setPatternOrigin(m_oldPatternOrigin);
|
2017-10-06 21:23:03 +08:00
|
|
|
}
|
|
|
|
|
2020-02-16 23:42:29 +08:00
|
|
|
void forceSnapToTiles() {
|
|
|
|
m_snapToGrid = true;
|
|
|
|
m_isSelectingTiles = true;
|
|
|
|
}
|
|
|
|
|
2015-05-22 03:00:27 +08:00
|
|
|
// IToolLoop interface
|
|
|
|
tools::Tool* getTool() override { return m_tool; }
|
|
|
|
Brush* getBrush() override { return m_brush.get(); }
|
2020-04-22 09:27:49 +08:00
|
|
|
void setBrush(const BrushRef& newBrush) override { m_brush = newBrush; }
|
2018-07-07 22:54:44 +08:00
|
|
|
Doc* getDocument() override { return m_document; }
|
2015-05-22 03:00:27 +08:00
|
|
|
Sprite* sprite() override { return m_sprite; }
|
|
|
|
Layer* getLayer() override { return m_layer; }
|
2021-01-30 03:34:06 +08:00
|
|
|
bool isTilemapMode() override { return m_tilesMode; };
|
2015-05-22 03:00:27 +08:00
|
|
|
frame_t getFrame() override { return m_frame; }
|
2015-11-04 21:33:23 +08:00
|
|
|
RgbMap* getRgbMap() override {
|
|
|
|
if (!m_rgbMap) {
|
|
|
|
Sprite::RgbMapFor forLayer =
|
2021-04-09 22:11:41 +08:00
|
|
|
(((m_layer && m_layer->isBackground()) ||
|
|
|
|
(m_sprite->pixelFormat() == IMAGE_RGB)) ?
|
2015-11-04 21:33:23 +08:00
|
|
|
Sprite::RgbMapFor::OpaqueLayer:
|
|
|
|
Sprite::RgbMapFor::TransparentLayer);
|
|
|
|
m_rgbMap = m_sprite->rgbMap(m_frame, forLayer);
|
|
|
|
}
|
|
|
|
return m_rgbMap;
|
|
|
|
}
|
2015-05-22 03:00:27 +08:00
|
|
|
ToolLoop::Button getMouseButton() override { return m_button; }
|
|
|
|
doc::color_t getFgColor() override { return m_fgColor; }
|
|
|
|
doc::color_t getBgColor() override { return m_bgColor; }
|
|
|
|
doc::color_t getPrimaryColor() override { return m_primaryColor; }
|
|
|
|
void setPrimaryColor(doc::color_t color) override { m_primaryColor = color; }
|
|
|
|
doc::color_t getSecondaryColor() override { return m_secondaryColor; }
|
|
|
|
void setSecondaryColor(doc::color_t color) override { m_secondaryColor = color; }
|
|
|
|
int getOpacity() override { return m_opacity; }
|
|
|
|
int getTolerance() override { return m_tolerance; }
|
|
|
|
bool getContiguous() override { return m_contiguous; }
|
2019-03-16 04:03:02 +08:00
|
|
|
tools::ToolLoopModifiers getModifiers() override {
|
2021-04-23 00:00:13 +08:00
|
|
|
return
|
|
|
|
(m_staticToolModifiers == tools::ToolLoopModifiers::kNone &&
|
|
|
|
m_editor ? m_editor->getToolLoopModifiers():
|
|
|
|
m_staticToolModifiers);
|
2019-03-16 04:03:02 +08:00
|
|
|
}
|
2015-05-22 03:00:27 +08:00
|
|
|
filters::TiledMode getTiledMode() override { return m_docPref.tiled.mode(); }
|
2016-03-25 00:04:35 +08:00
|
|
|
bool getGridVisible() override { return m_docPref.show.grid(); }
|
2020-02-16 23:42:29 +08:00
|
|
|
bool getSnapToGrid() override { return m_snapToGrid; }
|
|
|
|
bool isSelectingTiles() override { return m_isSelectingTiles; }
|
2015-06-18 00:22:46 +08:00
|
|
|
bool getStopAtGrid() override {
|
|
|
|
switch (m_toolPref.floodfill.stopAtGrid()) {
|
|
|
|
case app::gen::StopAtGrid::NEVER:
|
|
|
|
return false;
|
|
|
|
case app::gen::StopAtGrid::IF_VISIBLE:
|
2016-03-25 00:04:35 +08:00
|
|
|
return m_docPref.show.grid();
|
2015-06-18 00:22:46 +08:00
|
|
|
case app::gen::StopAtGrid::ALWAYS:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-29 09:07:32 +08:00
|
|
|
|
|
|
|
bool isPixelConnectivityEightConnected() override {
|
|
|
|
return (m_toolPref.floodfill.pixelConnectivity()
|
|
|
|
== app::gen::PixelConnectivity::EIGHT_CONNECTED);
|
|
|
|
}
|
|
|
|
|
2020-06-26 06:21:35 +08:00
|
|
|
const doc::Grid& getGrid() const override { return m_grid; }
|
2019-10-11 02:08:59 +08:00
|
|
|
gfx::Rect getGridBounds() override { return m_gridBounds; }
|
2015-11-03 03:44:17 +08:00
|
|
|
gfx::Point getCelOrigin() override { return m_celOrigin; }
|
2020-08-20 07:17:50 +08:00
|
|
|
bool needsCelCoordinates() override { return m_ink->needsCelCoordinates(); }
|
2015-05-22 03:00:27 +08:00
|
|
|
void setSpeed(const gfx::Point& speed) override { m_speed = speed; }
|
|
|
|
gfx::Point getSpeed() override { return m_speed; }
|
2018-08-09 04:27:26 +08:00
|
|
|
tools::Ink* getInk() override { return m_ink.get(); }
|
2015-05-22 03:14:26 +08:00
|
|
|
tools::Controller* getController() override { return m_controller; }
|
|
|
|
tools::PointShape* getPointShape() override { return m_pointShape; }
|
|
|
|
tools::Intertwine* getIntertwine() override { return m_intertwine; }
|
2017-06-17 03:28:48 +08:00
|
|
|
tools::TracePolicy getTracePolicy() override {
|
|
|
|
if (m_controller->handleTracePolicy())
|
|
|
|
return m_controller->getTracePolicy();
|
|
|
|
else
|
|
|
|
return m_tracePolicy;
|
|
|
|
}
|
2015-10-27 04:51:32 +08:00
|
|
|
tools::Symmetry* getSymmetry() override { return m_symmetry.get(); }
|
2020-05-06 22:20:52 +08:00
|
|
|
const Shade& getShade() override { return m_shade; }
|
2018-08-09 04:27:26 +08:00
|
|
|
doc::Remap* getShadingRemap() override { return m_shadingRemap.get(); }
|
2015-05-22 03:00:27 +08:00
|
|
|
|
2019-02-21 04:29:20 +08:00
|
|
|
void limitDirtyAreaToViewport(gfx::Region& rgn) override {
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI
|
2020-09-01 21:28:21 +08:00
|
|
|
rgn &= m_allVisibleRgn;
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif // ENABLE_UI
|
2019-02-21 04:29:20 +08:00
|
|
|
}
|
|
|
|
|
2019-02-20 22:17:09 +08:00
|
|
|
void updateDirtyArea(const gfx::Region& dirtyArea) override {
|
2019-03-16 04:03:02 +08:00
|
|
|
if (!m_editor)
|
|
|
|
return;
|
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI
|
2016-09-23 21:22:30 +08:00
|
|
|
// This is necessary here so the "on sprite crosshair" is hidden,
|
|
|
|
// we update screen pixels with the new sprite, and then we show
|
|
|
|
// the crosshair saving the updated pixels. It fixes problems with
|
|
|
|
// filled shape tools when we release the button, or paint-bucket
|
|
|
|
// when we press the button.
|
2015-07-06 22:05:49 +08:00
|
|
|
HideBrushPreview hide(m_editor->brushPreview());
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif
|
2016-09-23 21:22:30 +08:00
|
|
|
|
2016-09-23 07:16:14 +08:00
|
|
|
m_document->notifySpritePixelsModified(
|
2019-02-20 22:17:09 +08:00
|
|
|
m_sprite, dirtyArea, m_frame);
|
2015-05-22 03:00:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void updateStatusBar(const char* text) override {
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI
|
2019-03-16 04:03:02 +08:00
|
|
|
if (auto statusBar = StatusBar::instance())
|
|
|
|
statusBar->setStatusText(0, text);
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif
|
2015-05-22 03:00:27 +08:00
|
|
|
}
|
2015-09-16 23:19:10 +08:00
|
|
|
|
2017-11-11 02:04:09 +08:00
|
|
|
gfx::Point statusBarPositionOffset() override {
|
2020-09-01 21:28:21 +08:00
|
|
|
return m_mainTilePos;
|
2017-11-11 02:04:09 +08:00
|
|
|
}
|
|
|
|
|
2017-05-24 07:41:30 +08:00
|
|
|
render::DitheringMatrix getDitheringMatrix() override {
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI // TODO add support when UI is not enabled
|
2017-05-24 07:41:30 +08:00
|
|
|
return App::instance()->contextBar()->ditheringMatrix();
|
2019-03-19 00:30:17 +08:00
|
|
|
#else
|
|
|
|
return render::DitheringMatrix();
|
|
|
|
#endif
|
2017-05-24 07:41:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
render::DitheringAlgorithmBase* getDitheringAlgorithm() override {
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI // TODO add support when UI is not enabled
|
2017-05-24 07:41:30 +08:00
|
|
|
return App::instance()->contextBar()->ditheringAlgorithm();
|
2019-03-19 00:30:17 +08:00
|
|
|
#else
|
|
|
|
return nullptr;
|
|
|
|
#endif
|
2017-05-24 07:41:30 +08:00
|
|
|
}
|
|
|
|
|
2019-03-20 07:54:25 +08:00
|
|
|
render::GradientType getGradientType() override {
|
|
|
|
#ifdef ENABLE_UI // TODO add support when UI is not enabled
|
|
|
|
return App::instance()->contextBar()->gradientType();
|
|
|
|
#else
|
|
|
|
return render::GradientType::Linear;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-04-22 09:27:49 +08:00
|
|
|
tools::DynamicsOptions getDynamics() override {
|
2020-04-23 22:30:36 +08:00
|
|
|
return m_dynamics;
|
2020-04-22 09:27:49 +08:00
|
|
|
}
|
2019-05-01 03:42:15 +08:00
|
|
|
|
|
|
|
void onSliceRect(const gfx::Rect& bounds) override { }
|
|
|
|
|
2021-06-11 04:05:23 +08:00
|
|
|
void clearPointshapeStrokePtAreas() override { }
|
|
|
|
|
|
|
|
void setLastPtIndex(const int pti) override { }
|
|
|
|
|
|
|
|
void savePointshapeStrokePtArea(const tools::Stroke::Pt& pt) override { }
|
2021-04-23 21:09:56 +08:00
|
|
|
|
|
|
|
void restoreLastPts(const int pti, const tools::Stroke::Pt& pt) override { }
|
|
|
|
|
2021-06-11 04:05:23 +08:00
|
|
|
void updateTempTileset(const tools::Stroke::Pt& pt) override { }
|
|
|
|
|
2021-05-29 02:46:13 +08:00
|
|
|
const app::TiledModeHelper& getTiledModeHelper() override {
|
|
|
|
return m_tiledModeHelper;
|
|
|
|
}
|
|
|
|
|
2020-09-02 00:02:25 +08:00
|
|
|
#ifdef ENABLE_UI
|
|
|
|
protected:
|
|
|
|
void updateAllVisibleRegion() {
|
|
|
|
m_allVisibleRgn.clear();
|
|
|
|
// TODO use the context given to the ToolLoopImpl ctor
|
|
|
|
for (auto e : UIContext::instance()->getAllEditorsIncludingPreview(m_document)) {
|
|
|
|
gfx::Region viewportRegion;
|
|
|
|
e->getDrawableRegion(viewportRegion, Widget::kCutTopWindows);
|
|
|
|
for (auto rc : viewportRegion) {
|
|
|
|
gfx::Region subrgn(e->screenToEditor(rc).inflate(1, 1));
|
|
|
|
e->collapseRegionByTiledMode(subrgn);
|
|
|
|
m_allVisibleRgn |= subrgn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // ENABLE_UI
|
|
|
|
|
2015-05-22 03:00:27 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// For drawing
|
|
|
|
|
2020-09-02 00:02:25 +08:00
|
|
|
class ToolLoopImpl : public ToolLoopBase,
|
|
|
|
public EditorObserver {
|
2015-05-22 03:00:27 +08:00
|
|
|
Context* m_context;
|
|
|
|
bool m_filled;
|
|
|
|
bool m_previewFilled;
|
|
|
|
int m_sprayWidth;
|
|
|
|
int m_spraySpeed;
|
|
|
|
bool m_useMask;
|
|
|
|
Mask* m_mask;
|
|
|
|
gfx::Point m_maskOrigin;
|
2020-09-01 22:57:47 +08:00
|
|
|
bool m_internalCancel = false;
|
2019-06-29 03:33:11 +08:00
|
|
|
Tx m_tx;
|
2021-03-29 23:34:19 +08:00
|
|
|
std::unique_ptr<ExpandCelCanvas> m_expandCelCanvas;
|
2016-05-04 02:31:27 +08:00
|
|
|
Image* m_floodfillSrcImage;
|
2017-06-17 03:28:48 +08:00
|
|
|
bool m_saveLastPoint;
|
2021-06-11 04:05:23 +08:00
|
|
|
// Temporal tileset with latest changes to be used by pixel perfect only when
|
|
|
|
// modifying a tilemap in Manual mode.
|
|
|
|
std::unique_ptr<Tileset> m_tempTileset;
|
2015-05-22 03:00:27 +08:00
|
|
|
|
|
|
|
public:
|
|
|
|
ToolLoopImpl(Editor* editor,
|
2020-06-30 03:37:14 +08:00
|
|
|
Site& site,
|
2020-08-19 03:13:00 +08:00
|
|
|
const doc::Grid& grid,
|
2015-05-22 03:00:27 +08:00
|
|
|
Context* context,
|
2020-05-20 07:11:47 +08:00
|
|
|
ToolLoopParams& params,
|
2017-06-17 03:28:48 +08:00
|
|
|
const bool saveLastPoint)
|
2020-08-19 03:13:00 +08:00
|
|
|
: ToolLoopBase(editor, site, grid, params)
|
2015-05-22 03:00:27 +08:00
|
|
|
, m_context(context)
|
2019-06-29 03:33:11 +08:00
|
|
|
, m_tx(m_context,
|
|
|
|
m_tool->getText().c_str(),
|
2020-08-26 03:08:59 +08:00
|
|
|
((m_ink->isSelection() ||
|
|
|
|
m_ink->isEyedropper() ||
|
|
|
|
m_ink->isScrollMovement() ||
|
|
|
|
m_ink->isSlice() ||
|
|
|
|
m_ink->isZoom()) ? DoesntModifyDocument:
|
|
|
|
ModifyDocument))
|
2016-05-04 11:02:56 +08:00
|
|
|
, m_floodfillSrcImage(nullptr)
|
2017-06-17 03:28:48 +08:00
|
|
|
, m_saveLastPoint(saveLastPoint)
|
2021-06-11 04:05:23 +08:00
|
|
|
, m_tempTileset(nullptr)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2016-05-04 11:02:56 +08:00
|
|
|
if (m_pointShape->isFloodFill()) {
|
2020-10-10 04:22:57 +08:00
|
|
|
if (m_tilesMode) {
|
|
|
|
// This will be set later to getSrcImage()
|
|
|
|
m_floodfillSrcImage = nullptr;
|
|
|
|
}
|
2016-05-04 11:02:56 +08:00
|
|
|
// Prepare a special image for floodfill when it's configured to
|
|
|
|
// stop using all visible layers.
|
2020-10-10 04:22:57 +08:00
|
|
|
else if (m_toolPref.floodfill.referTo() == gen::FillReferTo::ALL_LAYERS) {
|
2016-05-04 11:02:56 +08:00
|
|
|
m_floodfillSrcImage = Image::create(m_sprite->pixelFormat(),
|
|
|
|
m_sprite->width(),
|
|
|
|
m_sprite->height());
|
|
|
|
|
|
|
|
m_floodfillSrcImage->clear(m_sprite->transparentColor());
|
|
|
|
|
2019-02-26 05:02:58 +08:00
|
|
|
render::Render render;
|
|
|
|
render.setNewBlend(Preferences::instance().experimental.newBlend());
|
|
|
|
render.renderSprite(
|
2016-05-04 11:02:56 +08:00
|
|
|
m_floodfillSrcImage,
|
|
|
|
m_sprite,
|
|
|
|
m_frame,
|
2016-05-24 06:22:02 +08:00
|
|
|
gfx::Clip(m_sprite->bounds()));
|
2016-05-04 11:02:56 +08:00
|
|
|
}
|
2021-04-23 02:46:07 +08:00
|
|
|
else if (Cel* cel = m_layer->cel(m_frame)) {
|
|
|
|
m_floodfillSrcImage = render::rasterize_with_sprite_bounds(cel);
|
2016-05-04 11:02:56 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 23:34:19 +08:00
|
|
|
m_expandCelCanvas.reset(new ExpandCelCanvas(
|
|
|
|
site, m_layer,
|
2016-05-04 11:02:56 +08:00
|
|
|
m_docPref.tiled.mode(),
|
2019-06-29 03:33:11 +08:00
|
|
|
m_tx,
|
2016-05-04 11:02:56 +08:00
|
|
|
ExpandCelCanvas::Flags(
|
|
|
|
ExpandCelCanvas::NeedsSource |
|
2020-10-14 02:59:14 +08:00
|
|
|
(m_layer->isTilemap() &&
|
2020-11-05 03:54:12 +08:00
|
|
|
(!m_tilesMode ||
|
|
|
|
m_ink->isSelection()) ? ExpandCelCanvas::PixelsBounds:
|
|
|
|
ExpandCelCanvas::None) |
|
|
|
|
(m_layer->isTilemap() &&
|
|
|
|
site.tilemapMode() == TilemapMode::Pixels &&
|
|
|
|
site.tilesetMode() == TilesetMode::Manual &&
|
|
|
|
!m_ink->isSelection() ? ExpandCelCanvas::TilesetPreview:
|
2021-03-29 23:34:19 +08:00
|
|
|
ExpandCelCanvas::None) |
|
|
|
|
(m_ink->isSelection() ? ExpandCelCanvas::SelectionPreview:
|
|
|
|
ExpandCelCanvas::None))));
|
2016-05-04 11:02:56 +08:00
|
|
|
|
|
|
|
if (!m_floodfillSrcImage)
|
|
|
|
m_floodfillSrcImage = const_cast<Image*>(getSrcImage());
|
|
|
|
|
2012-11-18 21:21:06 +08:00
|
|
|
// Settings
|
2020-05-20 07:11:47 +08:00
|
|
|
switch (m_tool->getFill(m_button)) {
|
2012-01-06 06:45:03 +08:00
|
|
|
case tools::FillNone:
|
|
|
|
m_filled = false;
|
|
|
|
break;
|
|
|
|
case tools::FillAlways:
|
|
|
|
m_filled = true;
|
|
|
|
break;
|
|
|
|
case tools::FillOptional:
|
2015-05-19 03:53:25 +08:00
|
|
|
m_filled = m_toolPref.filled();
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2014-01-26 04:58:29 +08:00
|
|
|
|
2015-05-19 03:53:25 +08:00
|
|
|
m_previewFilled = m_toolPref.filledPreview();
|
|
|
|
m_sprayWidth = m_toolPref.spray.width();
|
|
|
|
m_spraySpeed = m_toolPref.spray.speed();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2021-03-29 23:34:19 +08:00
|
|
|
if (m_ink->isSelection()) {
|
2014-08-27 20:43:42 +08:00
|
|
|
m_useMask = false;
|
2021-03-29 23:34:19 +08:00
|
|
|
}
|
|
|
|
else {
|
2014-08-27 20:43:42 +08:00
|
|
|
m_useMask = m_document->isMaskVisible();
|
2021-03-29 23:34:19 +08:00
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-01-19 09:05:33 +08:00
|
|
|
// Start with an empty mask if the user is selecting with "default selection mode"
|
2020-08-26 03:08:59 +08:00
|
|
|
if (m_ink->isSelection() &&
|
2014-01-26 04:58:29 +08:00
|
|
|
(!m_document->isMaskVisible() ||
|
2016-04-05 05:46:48 +08:00
|
|
|
(int(getModifiers()) & int(tools::ToolLoopModifiers::kReplaceSelection)))) {
|
2012-01-06 06:45:03 +08:00
|
|
|
Mask emptyMask;
|
2019-06-29 03:33:11 +08:00
|
|
|
m_tx(new cmd::SetMask(m_document, &emptyMask));
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2020-08-20 07:17:50 +08:00
|
|
|
// Setup the new grid of ExpandCelCanvas which can be displaced to
|
|
|
|
// match the new temporal cel position (m_celOrigin).
|
|
|
|
m_grid = m_expandCelCanvas->getGrid();
|
2020-11-05 03:54:12 +08:00
|
|
|
m_celOrigin = m_expandCelCanvas->getCelOrigin();
|
2020-08-20 07:17:50 +08:00
|
|
|
|
2014-07-30 12:28:15 +08:00
|
|
|
m_mask = m_document->mask();
|
2015-11-03 03:44:17 +08:00
|
|
|
m_maskOrigin = (!m_mask->isEmpty() ? gfx::Point(m_mask->bounds().x-m_celOrigin.x,
|
|
|
|
m_mask->bounds().y-m_celOrigin.y):
|
2012-01-09 09:34:36 +08:00
|
|
|
gfx::Point(0, 0));
|
2020-09-02 00:02:25 +08:00
|
|
|
|
|
|
|
#ifdef ENABLE_UI
|
|
|
|
if (m_editor)
|
|
|
|
m_editor->add_observer(this);
|
|
|
|
#endif
|
2021-06-11 04:05:23 +08:00
|
|
|
|
|
|
|
if (m_layer->isTilemap() && site.tilesetMode() == TilesetMode::Manual) {
|
|
|
|
const Tileset* srcTileset = static_cast<LayerTilemap*>(m_layer)->tileset();
|
|
|
|
m_tempTileset.reset(Tileset::MakeCopyCopyingImages(srcTileset));
|
|
|
|
}
|
2016-05-04 02:31:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
~ToolLoopImpl() {
|
2020-09-02 00:02:25 +08:00
|
|
|
#ifdef ENABLE_UI
|
|
|
|
if (m_editor)
|
|
|
|
m_editor->remove_observer(this);
|
|
|
|
#endif
|
|
|
|
|
2016-05-04 02:31:27 +08:00
|
|
|
if (m_floodfillSrcImage != getSrcImage())
|
|
|
|
delete m_floodfillSrcImage;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2015-04-10 22:12:19 +08:00
|
|
|
// IToolLoop interface
|
2020-08-20 07:17:50 +08:00
|
|
|
bool needsCelCoordinates() override {
|
|
|
|
if (m_tilesMode) {
|
|
|
|
// When we are painting with tiles, we don't need to adjust the
|
|
|
|
// coordinates by the cel position in PointShape (points will be
|
|
|
|
// in tiles position relative to the tilemap origin already).
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return ToolLoopBase::needsCelCoordinates();
|
|
|
|
}
|
|
|
|
|
2020-09-01 22:57:47 +08:00
|
|
|
void commit() override {
|
2014-12-09 01:57:56 +08:00
|
|
|
bool redraw = false;
|
|
|
|
|
2020-09-01 22:57:47 +08:00
|
|
|
if (!m_internalCancel) {
|
2017-06-17 03:28:48 +08:00
|
|
|
// Freehand changes the last point
|
|
|
|
if (m_saveLastPoint) {
|
2019-06-29 03:33:11 +08:00
|
|
|
m_tx(new cmd::SetLastPoint(
|
|
|
|
m_document,
|
2020-04-23 07:17:14 +08:00
|
|
|
getController()->getLastPoint().toPoint()));
|
2017-06-17 03:28:48 +08:00
|
|
|
}
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
// Paint ink
|
2020-08-26 03:08:59 +08:00
|
|
|
if (m_ink->isPaint()) {
|
2015-04-10 22:10:42 +08:00
|
|
|
try {
|
|
|
|
ContextReader reader(m_context, 500);
|
2020-02-06 04:50:17 +08:00
|
|
|
ContextWriter writer(reader);
|
2016-05-04 11:02:56 +08:00
|
|
|
m_expandCelCanvas->commit();
|
2015-04-10 22:10:42 +08:00
|
|
|
}
|
2018-07-15 09:47:03 +08:00
|
|
|
catch (const LockedDocException& ex) {
|
2015-04-10 22:10:42 +08:00
|
|
|
Console::showException(ex);
|
2015-04-08 04:50:57 +08:00
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
// Selection ink
|
2020-08-26 03:08:59 +08:00
|
|
|
else if (m_ink->isSelection()) {
|
2014-12-09 01:57:56 +08:00
|
|
|
redraw = true;
|
2016-03-25 00:04:35 +08:00
|
|
|
|
|
|
|
// Show selection edges
|
2018-04-20 21:56:27 +08:00
|
|
|
if (Preferences::instance().selection.autoShowSelectionEdges())
|
|
|
|
m_docPref.show.selectionEdges(true);
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2017-03-07 06:27:43 +08:00
|
|
|
// Slice ink
|
2020-08-26 03:08:59 +08:00
|
|
|
else if (m_ink->isSlice()) {
|
2017-03-07 06:27:43 +08:00
|
|
|
redraw = true;
|
|
|
|
}
|
2012-07-08 12:25:26 +08:00
|
|
|
|
2019-06-29 03:33:11 +08:00
|
|
|
m_tx.commit();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2017-06-16 23:18:22 +08:00
|
|
|
else {
|
2020-09-01 22:57:47 +08:00
|
|
|
rollback();
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI
|
2014-12-09 01:57:56 +08:00
|
|
|
if (redraw)
|
|
|
|
update_screen_for_document(m_document);
|
2019-04-25 05:31:43 +08:00
|
|
|
#else
|
|
|
|
(void)redraw; // To avoid warning about unused variable
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
2020-09-01 22:57:47 +08:00
|
|
|
void rollback() override {
|
|
|
|
try {
|
|
|
|
ContextReader reader(m_context, 500);
|
|
|
|
ContextWriter writer(reader);
|
|
|
|
m_expandCelCanvas->rollback();
|
|
|
|
}
|
|
|
|
catch (const LockedDocException& ex) {
|
|
|
|
Console::showException(ex);
|
|
|
|
}
|
|
|
|
#ifdef ENABLE_UI
|
|
|
|
update_screen_for_document(m_document);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2016-05-04 11:02:56 +08:00
|
|
|
const Image* getSrcImage() override { return m_expandCelCanvas->getSourceCanvas(); }
|
2016-05-04 02:31:27 +08:00
|
|
|
const Image* getFloodFillSrcImage() override { return m_floodfillSrcImage; }
|
2016-05-04 11:02:56 +08:00
|
|
|
Image* getDstImage() override { return m_expandCelCanvas->getDestCanvas(); }
|
2020-11-05 03:54:12 +08:00
|
|
|
Tileset* getDstTileset() override { return m_expandCelCanvas->getDestTileset(); }
|
2014-12-09 01:57:56 +08:00
|
|
|
void validateSrcImage(const gfx::Region& rgn) override {
|
2016-05-04 11:02:56 +08:00
|
|
|
m_expandCelCanvas->validateSourceCanvas(rgn);
|
2014-12-09 01:57:56 +08:00
|
|
|
}
|
|
|
|
void validateDstImage(const gfx::Region& rgn) override {
|
2016-05-04 11:02:56 +08:00
|
|
|
m_expandCelCanvas->validateDestCanvas(rgn);
|
2014-12-09 01:57:56 +08:00
|
|
|
}
|
2020-11-05 03:54:12 +08:00
|
|
|
void validateDstTileset(const gfx::Region& rgn) override {
|
2021-06-11 04:05:23 +08:00
|
|
|
m_expandCelCanvas->validateDestTileset(rgn, m_restoredRegion);
|
2020-11-05 03:54:12 +08:00
|
|
|
}
|
2014-12-09 01:57:56 +08:00
|
|
|
void invalidateDstImage() override {
|
2016-05-04 11:02:56 +08:00
|
|
|
m_expandCelCanvas->invalidateDestCanvas();
|
2014-12-09 01:57:56 +08:00
|
|
|
}
|
|
|
|
void invalidateDstImage(const gfx::Region& rgn) override {
|
2016-05-04 11:02:56 +08:00
|
|
|
m_expandCelCanvas->invalidateDestCanvas(rgn);
|
2014-12-09 01:57:56 +08:00
|
|
|
}
|
|
|
|
void copyValidDstToSrcImage(const gfx::Region& rgn) override {
|
2016-05-04 11:02:56 +08:00
|
|
|
m_expandCelCanvas->copyValidDestToSourceCanvas(rgn);
|
2014-12-09 01:57:56 +08:00
|
|
|
}
|
|
|
|
|
2014-08-15 10:07:47 +08:00
|
|
|
bool useMask() override { return m_useMask; }
|
|
|
|
Mask* getMask() override { return m_mask; }
|
2015-01-19 09:05:33 +08:00
|
|
|
void setMask(Mask* newMask) override {
|
2019-06-29 03:33:11 +08:00
|
|
|
m_tx(new cmd::SetMask(m_document, newMask));
|
2015-01-19 09:05:33 +08:00
|
|
|
}
|
2014-08-15 10:07:47 +08:00
|
|
|
gfx::Point getMaskOrigin() override { return m_maskOrigin; }
|
|
|
|
bool getFilled() override { return m_filled; }
|
|
|
|
bool getPreviewFilled() override { return m_previewFilled; }
|
|
|
|
int getSprayWidth() override { return m_sprayWidth; }
|
|
|
|
int getSpraySpeed() override { return m_spraySpeed; }
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2019-05-01 03:42:15 +08:00
|
|
|
void onSliceRect(const gfx::Rect& bounds) override {
|
2019-05-09 06:18:19 +08:00
|
|
|
#ifdef ENABLE_UI // TODO add support for slice tool from batch scripts without UI?
|
|
|
|
if (m_editor && getMouseButton() == ToolLoop::Left) {
|
2019-05-03 03:26:13 +08:00
|
|
|
// Try to select slices, but if it returns false, it means that
|
|
|
|
// there are no slices in the box to be selected, so we show a
|
|
|
|
// popup menu to create a new one.
|
|
|
|
if (!m_editor->selectSliceBox(bounds) &&
|
|
|
|
(bounds.w > 1 || bounds.h > 1)) {
|
|
|
|
Slice* slice = new Slice;
|
2020-03-07 02:18:10 +08:00
|
|
|
slice->setName(getUniqueSliceName());
|
|
|
|
|
2019-05-03 03:26:13 +08:00
|
|
|
SliceKey key(bounds);
|
|
|
|
slice->insert(getFrame(), key);
|
|
|
|
|
|
|
|
auto color = Preferences::instance().slices.defaultColor();
|
|
|
|
slice->userData().setColor(
|
|
|
|
doc::rgba(color.getRed(),
|
|
|
|
color.getGreen(),
|
|
|
|
color.getBlue(),
|
|
|
|
color.getAlpha()));
|
|
|
|
|
2019-06-29 03:33:11 +08:00
|
|
|
m_tx(new cmd::AddSlice(m_sprite, slice));
|
2019-05-03 03:26:13 +08:00
|
|
|
return;
|
|
|
|
}
|
2019-05-01 03:42:15 +08:00
|
|
|
}
|
2019-05-09 06:18:19 +08:00
|
|
|
#endif
|
2019-05-03 03:26:13 +08:00
|
|
|
|
|
|
|
// Cancel the operation (do not create a new transaction for this
|
|
|
|
// no-op, e.g. just change the set of selected slices).
|
2020-09-01 22:57:47 +08:00
|
|
|
m_internalCancel = true;
|
2019-05-01 03:42:15 +08:00
|
|
|
}
|
|
|
|
|
2021-06-11 04:05:23 +08:00
|
|
|
void clearPointshapeStrokePtAreas() override {
|
|
|
|
m_savedAreas.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void setLastPtIndex(const int pti) override {
|
|
|
|
m_lastPti = pti;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Saves the destination image's area that will be updated by the point
|
|
|
|
// passed. The idea is to have the state of the image (only the
|
2021-06-03 21:33:28 +08:00
|
|
|
// portion modified by the stroke's point shape) before drawing the last
|
|
|
|
// point of the stroke, then if that point has to be deleted by the
|
|
|
|
// pixel-perfect algorithm, we can use this image to restore the image to the
|
|
|
|
// state previous to the deletion. This method is used by
|
|
|
|
// IntertwineAsPixelPerfect.joinStroke() method.
|
2021-06-11 04:05:23 +08:00
|
|
|
void savePointshapeStrokePtArea(const tools::Stroke::Pt& pt) override {
|
|
|
|
gfx::Rect r;
|
|
|
|
getPointShape()->getModifiedArea(this, pt.x, pt.y, r);
|
|
|
|
|
|
|
|
gfx::Region rgn(r);
|
|
|
|
// By wrapping the modified area's position when tiled mode is active, the
|
|
|
|
// user can draw outside the canvas and still get the pixel-perfect
|
|
|
|
// effect.
|
|
|
|
m_tiledModeHelper.wrapPosition(rgn);
|
|
|
|
m_tiledModeHelper.collapseRegionByTiledMode(rgn);
|
|
|
|
|
|
|
|
for (auto a : rgn) {
|
|
|
|
a.offset(-m_celOrigin);
|
|
|
|
|
|
|
|
if (m_tempTileset) {
|
|
|
|
forEachTilePos(
|
|
|
|
m_grid.tilesInCanvasRegion(gfx::Region(a)),
|
|
|
|
[this](const doc::ImageRef existentTileImage,
|
|
|
|
const gfx::Point tilePos) {
|
|
|
|
getDstImage()->copy(existentTileImage.get(),
|
|
|
|
gfx::Clip(tilePos.x, tilePos.y, 0, 0,
|
|
|
|
existentTileImage.get()->width(),
|
|
|
|
existentTileImage.get()->height()));
|
|
|
|
});
|
2021-04-23 21:09:56 +08:00
|
|
|
}
|
2021-06-11 04:05:23 +08:00
|
|
|
|
|
|
|
ImageRef i(Image::create(getDstImage()->pixelFormat(), a.w, a.h));
|
|
|
|
i->copy(getDstImage(), gfx::Clip(0, 0, a));
|
|
|
|
m_savedAreas.push_back(SavedArea{ i, pt, a});
|
2021-04-23 21:09:56 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Takes the images saved by savePointshapeStrokePtArea and copies them to
|
|
|
|
// the destination image. It restores the destination image because the
|
|
|
|
// images in m_savedAreas are from previous states of the destination
|
|
|
|
// image. This method is used by IntertwineAsPixelPerfect.joinStroke()
|
|
|
|
// method.
|
|
|
|
void restoreLastPts(const int pti, const tools::Stroke::Pt& pt) override {
|
|
|
|
if (m_savedAreas.empty() || pti != m_lastPti || m_savedAreas[0].pos != pt)
|
|
|
|
return;
|
|
|
|
|
2021-06-11 04:05:23 +08:00
|
|
|
m_restoredRegion.clear();
|
|
|
|
|
2021-04-23 21:09:56 +08:00
|
|
|
tools::Stroke::Pt pos;
|
|
|
|
for (int i=0; i<m_savedAreas.size(); ++i) {
|
|
|
|
getDstImage()->copy(m_savedAreas[i].img.get(),
|
|
|
|
gfx::Clip(m_savedAreas[i].r.origin(),
|
|
|
|
m_savedAreas[i].img->bounds()));
|
2021-06-11 04:05:23 +08:00
|
|
|
|
|
|
|
if (m_tempTileset) {
|
|
|
|
auto r = m_savedAreas[i].r;
|
|
|
|
forEachTilePos(
|
|
|
|
m_grid.tilesInCanvasRegion(gfx::Region(r)),
|
|
|
|
[this, i, r](const doc::ImageRef existentTileImage,
|
|
|
|
const gfx::Point tilePos) {
|
|
|
|
existentTileImage->copy(m_savedAreas[i].img.get(), gfx::Clip(r.x - tilePos.x, r.y - tilePos.y, 0, 0, r.w, r.h));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
m_restoredRegion |= gfx::Region(m_savedAreas[i].r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void updateTempTileset(const tools::Stroke::Pt& pt) override {
|
|
|
|
if (!m_tempTileset)
|
|
|
|
return;
|
|
|
|
|
|
|
|
gfx::Rect r;
|
|
|
|
getPointShape()->getModifiedArea(this, pt.x, pt.y, r);
|
|
|
|
|
|
|
|
auto tilesPts = m_grid.tilesInCanvasRegion(gfx::Region(r));
|
|
|
|
forEachTilePos(
|
|
|
|
tilesPts,
|
|
|
|
[this, r](const doc::ImageRef existentTileImage,
|
|
|
|
const gfx::Point tilePos) {
|
|
|
|
existentTileImage->copy(getDstImage(), gfx::Clip(r.x - tilePos.x, r.y - tilePos.y, r.x, r.y, r.w, r.h));
|
|
|
|
});
|
|
|
|
|
|
|
|
if (tilesPts.size() > 1) {
|
|
|
|
forEachTilePos(
|
|
|
|
tilesPts,
|
|
|
|
[this](const doc::ImageRef existentTileImage,
|
|
|
|
const gfx::Point tilePos) {
|
|
|
|
getDstImage()->copy(existentTileImage.get(), gfx::Clip(tilePos.x, tilePos.y, 0, 0,
|
|
|
|
existentTileImage.get()->width(),
|
|
|
|
existentTileImage.get()->height()));
|
|
|
|
});
|
2021-04-23 21:09:56 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-07 02:18:10 +08:00
|
|
|
private:
|
2021-04-23 21:09:56 +08:00
|
|
|
|
2021-06-11 04:05:23 +08:00
|
|
|
// Loops over the points in tilesPts, and for each one calls the provided
|
|
|
|
// processTempTileImage callback passing to it the corresponding temp tile
|
|
|
|
// image and canvas position.
|
|
|
|
void forEachTilePos(const std::vector<gfx::Point>& tilesPts,
|
|
|
|
const std::function<void(const doc::ImageRef existentTileImage,
|
|
|
|
const gfx::Point tilePos)>& processTempTileImage) {
|
|
|
|
auto cel = m_expandCelCanvas->getCel();
|
|
|
|
for (const gfx::Point& tilePt : tilesPts) {
|
|
|
|
// Ignore modifications outside the tilemap
|
|
|
|
if (!cel->image()->bounds().contains(tilePt.x, tilePt.y))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const doc::tile_t t = cel->image()->getPixel(tilePt.x, tilePt.y);
|
|
|
|
if (t == doc::notile)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const doc::tile_index ti = doc::tile_geti(t);
|
|
|
|
const doc::ImageRef existentTileImage = m_tempTileset->get(ti);
|
|
|
|
if (!existentTileImage) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto tilePos = m_grid.tileToCanvas(tilePt);
|
|
|
|
|
|
|
|
processTempTileImage(existentTileImage, tilePos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-23 21:09:56 +08:00
|
|
|
#ifdef ENABLE_UI
|
2020-09-02 00:02:25 +08:00
|
|
|
// EditorObserver impl
|
|
|
|
void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
|
|
|
void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
2020-03-07 02:18:10 +08:00
|
|
|
|
|
|
|
std::string getUniqueSliceName() const {
|
|
|
|
std::string prefix = "Slice";
|
|
|
|
int max = 0;
|
|
|
|
|
|
|
|
for (Slice* slice : m_sprite->slices())
|
|
|
|
if (std::strncmp(slice->name().c_str(), prefix.c_str(), prefix.size()) == 0)
|
|
|
|
max = std::max(max, (int)std::strtol(slice->name().c_str()+prefix.size(), nullptr, 10));
|
|
|
|
|
|
|
|
return fmt::format("{} {}", prefix, max+1);
|
|
|
|
}
|
2020-09-02 00:02:25 +08:00
|
|
|
#endif // ENABLE_UI
|
2020-03-07 02:18:10 +08:00
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
};
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// For user UI painting
|
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI
|
|
|
|
|
2020-10-06 21:40:08 +08:00
|
|
|
// TODO add inks for tilemaps
|
|
|
|
static void adjust_ink_for_tilemaps(const Site& site,
|
|
|
|
ToolLoopParams& params)
|
|
|
|
{
|
|
|
|
if (!params.ink->isSelection() &&
|
|
|
|
!params.ink->isEraser()) {
|
|
|
|
params.ink = App::instance()->toolBox()->getInkById(tools::WellKnownInks::PaintCopy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-17 03:28:48 +08:00
|
|
|
tools::ToolLoop* create_tool_loop(
|
|
|
|
Editor* editor,
|
|
|
|
Context* context,
|
2017-11-28 00:53:56 +08:00
|
|
|
const tools::Pointer::Button button,
|
2020-02-16 23:42:29 +08:00
|
|
|
const bool convertLineToFreehand,
|
|
|
|
const bool selectTiles)
|
2012-01-06 06:45:03 +08:00
|
|
|
{
|
2020-06-26 06:21:35 +08:00
|
|
|
Site site = editor->getSite();
|
2020-08-19 03:13:00 +08:00
|
|
|
doc::Grid grid = site.grid();
|
2020-06-26 06:21:35 +08:00
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
ToolLoopParams params;
|
|
|
|
params.tool = editor->getCurrentEditorTool();
|
|
|
|
params.ink = editor->getCurrentEditorInk();
|
2020-06-26 06:21:35 +08:00
|
|
|
|
|
|
|
if (site.tilemapMode() == TilemapMode::Tiles) {
|
2020-10-06 21:40:08 +08:00
|
|
|
adjust_ink_for_tilemaps(site, params);
|
2020-06-26 06:21:35 +08:00
|
|
|
}
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
if (!params.tool || !params.ink)
|
2017-06-17 03:28:48 +08:00
|
|
|
return nullptr;
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2020-02-16 23:42:29 +08:00
|
|
|
if (selectTiles) {
|
2020-05-20 07:11:47 +08:00
|
|
|
params.tool = App::instance()->toolBox()->getToolById(tools::WellKnownTools::RectangularMarquee);
|
|
|
|
params.ink = params.tool->getInk(button == tools::Pointer::Left ? 0: 1);
|
2020-02-16 23:42:29 +08:00
|
|
|
}
|
|
|
|
|
2016-05-03 05:42:02 +08:00
|
|
|
// For selection tools, we can use any layer (even without layers at
|
|
|
|
// all), so we specify a nullptr here as the active layer. This is
|
|
|
|
// used as a special case by the render::Render class to show the
|
|
|
|
// preview image/selection stroke as a xor'd overlay in the render
|
|
|
|
// result.
|
|
|
|
//
|
|
|
|
// Anyway this cannot be used in 'magic wand' tool (isSelection +
|
|
|
|
// isFloodFill) because we need the original layer source
|
|
|
|
// image/pixels to stop the flood-fill algorithm.
|
2020-05-20 07:11:47 +08:00
|
|
|
if (params.ink->isSelection() &&
|
|
|
|
!params.tool->getPointShape(
|
|
|
|
button != tools::Pointer::Left ? 1: 0)->isFloodFill()) {
|
2020-08-26 23:30:48 +08:00
|
|
|
// Don't call site.layer(nullptr) because we want to keep the
|
|
|
|
// site.layer() to know if we are in a tilemap layer
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2016-05-03 05:42:02 +08:00
|
|
|
else {
|
2019-03-16 04:03:02 +08:00
|
|
|
Layer* layer = site.layer();
|
2016-05-03 05:42:02 +08:00
|
|
|
if (!layer) {
|
|
|
|
StatusBar::instance()->showTip(
|
|
|
|
1000, "There is no active layer");
|
|
|
|
return nullptr;
|
|
|
|
}
|
2016-06-16 02:27:38 +08:00
|
|
|
else if (!layer->isVisibleHierarchy()) {
|
2016-05-03 05:42:02 +08:00
|
|
|
StatusBar::instance()->showTip(
|
2020-05-09 04:39:55 +08:00
|
|
|
1000, fmt::format("Layer '{}' is hidden", layer->name()));
|
2016-05-03 05:42:02 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
// If the active layer is read-only.
|
2020-06-09 03:19:00 +08:00
|
|
|
else if (layer_is_locked(editor)) {
|
2016-05-03 05:42:02 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
2016-10-18 00:48:52 +08:00
|
|
|
// If the active layer is reference.
|
|
|
|
else if (layer->isReference()) {
|
|
|
|
StatusBar::instance()->showTip(
|
2020-05-09 04:39:55 +08:00
|
|
|
1000, fmt::format("Layer '{}' is reference, cannot be modified", layer->name()));
|
2016-10-18 00:48:52 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get fg/bg colors
|
2012-07-10 00:20:58 +08:00
|
|
|
ColorBar* colorbar = ColorBar::instance();
|
2020-06-26 06:21:35 +08:00
|
|
|
if (site.tilemapMode() == TilemapMode::Tiles) {
|
|
|
|
params.fg = app::Color::fromIndex(colorbar->getFgTile()); // TODO Color::fromTileIndex?
|
|
|
|
params.bg = app::Color::fromIndex(colorbar->getBgTile());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params.fg = colorbar->getFgColor();
|
|
|
|
params.bg = colorbar->getBgColor();
|
2020-08-22 05:07:37 +08:00
|
|
|
if (!params.fg.isValid() ||
|
|
|
|
!params.bg.isValid()) {
|
|
|
|
if (Preferences::instance().colorBar.showInvalidFgBgColorAlert()) {
|
|
|
|
OptionalAlert::show(
|
|
|
|
Preferences::instance().colorBar.showInvalidFgBgColorAlert,
|
|
|
|
1, Strings::alerts_invalid_fg_or_bg_colors());
|
|
|
|
return nullptr;
|
|
|
|
}
|
2018-09-13 21:53:14 +08:00
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create the new tool loop
|
2014-08-19 19:17:57 +08:00
|
|
|
try {
|
2020-05-20 07:11:47 +08:00
|
|
|
params.button =
|
2017-11-28 00:53:56 +08:00
|
|
|
(button == tools::Pointer::Left ? tools::ToolLoop::Left:
|
|
|
|
tools::ToolLoop::Right);
|
2017-06-17 03:28:48 +08:00
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
params.controller =
|
2017-06-17 03:28:48 +08:00
|
|
|
(convertLineToFreehand ?
|
|
|
|
App::instance()->toolBox()->getControllerById(
|
|
|
|
tools::WellKnownControllers::LineFreehand):
|
2020-05-20 07:11:47 +08:00
|
|
|
params.tool->getController(params.button));
|
2017-06-17 03:28:48 +08:00
|
|
|
|
|
|
|
const bool saveLastPoint =
|
2020-05-20 07:11:47 +08:00
|
|
|
(params.ink->isPaint() &&
|
|
|
|
(params.controller->isFreehand() ||
|
2017-06-17 03:28:48 +08:00
|
|
|
convertLineToFreehand));
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
params.brush = App::instance()->contextBar()
|
|
|
|
->activeBrush(params.tool, params.ink);
|
|
|
|
|
|
|
|
fill_toolloop_params_from_tool_preferences(params);
|
|
|
|
|
2019-03-16 04:03:02 +08:00
|
|
|
ASSERT(context->activeDocument() == editor->document());
|
2020-02-16 23:42:29 +08:00
|
|
|
auto toolLoop = new ToolLoopImpl(
|
2020-08-19 03:13:00 +08:00
|
|
|
editor, site, grid, context, params, saveLastPoint);
|
2020-02-16 23:42:29 +08:00
|
|
|
|
|
|
|
if (selectTiles)
|
|
|
|
toolLoop->forceSnapToTiles();
|
|
|
|
|
|
|
|
return toolLoop;
|
2013-11-11 03:20:20 +08:00
|
|
|
}
|
2014-08-19 19:17:57 +08:00
|
|
|
catch (const std::exception& ex) {
|
2017-10-18 05:00:45 +08:00
|
|
|
Console::showException(ex);
|
2013-11-11 03:20:20 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif // ENABLE_UI
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// For scripting
|
|
|
|
|
|
|
|
#ifdef ENABLE_SCRIPTING
|
|
|
|
|
2019-03-16 04:03:02 +08:00
|
|
|
tools::ToolLoop* create_tool_loop_for_script(
|
|
|
|
Context* context,
|
2019-03-24 03:34:51 +08:00
|
|
|
const Site& site,
|
2020-05-20 07:11:47 +08:00
|
|
|
ToolLoopParams& params)
|
2019-03-16 04:03:02 +08:00
|
|
|
{
|
2020-05-20 07:11:47 +08:00
|
|
|
ASSERT(params.tool);
|
|
|
|
ASSERT(params.ink);
|
2019-03-16 04:03:02 +08:00
|
|
|
if (!site.layer())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
try {
|
2019-04-25 05:31:43 +08:00
|
|
|
// If we don't have the UI available, we reset the tools
|
|
|
|
// preferences, so scripts that are executed in batch mode have a
|
|
|
|
// reproducible behavior.
|
|
|
|
if (!context->isUIAvailable())
|
2020-05-20 07:11:47 +08:00
|
|
|
Preferences::instance().resetToolPreferences(params.tool);
|
2019-04-25 05:31:43 +08:00
|
|
|
|
2020-06-30 03:37:14 +08:00
|
|
|
Site site2(site);
|
2019-03-16 04:03:02 +08:00
|
|
|
return new ToolLoopImpl(
|
2020-08-19 03:13:00 +08:00
|
|
|
nullptr, site2, site2.grid(),
|
|
|
|
context, params, false);
|
2019-03-16 04:03:02 +08:00
|
|
|
}
|
|
|
|
catch (const std::exception& ex) {
|
|
|
|
Console::showException(ex);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
#endif // ENABLE_SCRIPTING
|
|
|
|
|
2015-04-27 02:59:28 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
2020-05-20 07:11:47 +08:00
|
|
|
// For UI preview
|
2015-04-27 02:59:28 +08:00
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#ifdef ENABLE_UI
|
|
|
|
|
2015-05-22 03:00:27 +08:00
|
|
|
class PreviewToolLoopImpl : public ToolLoopBase {
|
2015-04-27 02:59:28 +08:00
|
|
|
Image* m_image;
|
|
|
|
|
|
|
|
public:
|
|
|
|
PreviewToolLoopImpl(
|
|
|
|
Editor* editor,
|
2020-06-30 03:37:14 +08:00
|
|
|
Site& site,
|
2020-05-20 07:11:47 +08:00
|
|
|
ToolLoopParams& params,
|
2015-04-27 02:59:28 +08:00
|
|
|
Image* image,
|
2015-11-03 03:44:17 +08:00
|
|
|
const gfx::Point& celOrigin)
|
2020-08-19 03:13:00 +08:00
|
|
|
: ToolLoopBase(editor, site, site.grid(), params)
|
2015-04-27 02:59:28 +08:00
|
|
|
, m_image(image)
|
|
|
|
{
|
2015-11-03 03:44:17 +08:00
|
|
|
m_celOrigin = celOrigin;
|
2015-04-27 02:59:28 +08:00
|
|
|
|
|
|
|
// Avoid preview for spray and flood fill like tools
|
|
|
|
if (m_pointShape->isSpray()) {
|
2016-04-23 00:19:06 +08:00
|
|
|
m_pointShape = App::instance()->toolBox()->getPointShapeById(
|
2015-04-27 02:59:28 +08:00
|
|
|
tools::WellKnownPointShapes::Brush);
|
|
|
|
}
|
|
|
|
else if (m_pointShape->isFloodFill()) {
|
2020-10-10 04:22:57 +08:00
|
|
|
m_pointShape = App::instance()->toolBox()->getPointShapeById
|
|
|
|
(m_tilesMode ? tools::WellKnownPointShapes::Tile:
|
|
|
|
tools::WellKnownPointShapes::Pixel);
|
2015-04-27 02:59:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IToolLoop interface
|
2020-09-01 22:57:47 +08:00
|
|
|
void commit() override { }
|
|
|
|
void rollback() override { }
|
2015-04-27 02:59:28 +08:00
|
|
|
const Image* getSrcImage() override { return m_image; }
|
2016-05-04 02:31:27 +08:00
|
|
|
const Image* getFloodFillSrcImage() override { return m_image; }
|
2015-04-27 02:59:28 +08:00
|
|
|
Image* getDstImage() override { return m_image; }
|
2020-11-05 03:54:12 +08:00
|
|
|
Tileset* getDstTileset() override { return nullptr; }
|
2015-04-27 02:59:28 +08:00
|
|
|
void validateSrcImage(const gfx::Region& rgn) override { }
|
|
|
|
void validateDstImage(const gfx::Region& rgn) override { }
|
2020-11-05 03:54:12 +08:00
|
|
|
void validateDstTileset(const gfx::Region& rgn) override { }
|
2015-04-27 02:59:28 +08:00
|
|
|
void invalidateDstImage() override { }
|
|
|
|
void invalidateDstImage(const gfx::Region& rgn) override { }
|
|
|
|
void copyValidDstToSrcImage(const gfx::Region& rgn) override { }
|
|
|
|
|
2015-05-22 03:00:27 +08:00
|
|
|
bool useMask() override { return false; }
|
|
|
|
Mask* getMask() override { return nullptr; }
|
2015-04-27 02:59:28 +08:00
|
|
|
void setMask(Mask* newMask) override { }
|
2015-05-22 03:00:27 +08:00
|
|
|
gfx::Point getMaskOrigin() override { return gfx::Point(0, 0); }
|
2015-04-27 02:59:28 +08:00
|
|
|
bool getFilled() override { return false; }
|
|
|
|
bool getPreviewFilled() override { return false; }
|
|
|
|
int getSprayWidth() override { return 0; }
|
|
|
|
int getSpraySpeed() override { return 0; }
|
|
|
|
|
2020-04-23 07:17:14 +08:00
|
|
|
tools::DynamicsOptions getDynamics() override {
|
|
|
|
// Preview without dynamics
|
|
|
|
return tools::DynamicsOptions();
|
|
|
|
}
|
|
|
|
|
2015-04-27 02:59:28 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
tools::ToolLoop* create_tool_loop_preview(
|
2020-05-06 06:42:07 +08:00
|
|
|
Editor* editor,
|
|
|
|
const doc::BrushRef& brush,
|
|
|
|
Image* image,
|
2015-11-03 03:44:17 +08:00
|
|
|
const gfx::Point& celOrigin)
|
2015-04-27 02:59:28 +08:00
|
|
|
{
|
2020-06-26 06:21:35 +08:00
|
|
|
Site site = editor->getSite();
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
ToolLoopParams params;
|
|
|
|
params.tool = editor->getCurrentEditorTool();
|
|
|
|
params.ink = editor->getCurrentEditorInk();
|
2020-06-26 06:21:35 +08:00
|
|
|
|
|
|
|
if (site.tilemapMode() == TilemapMode::Tiles &&
|
|
|
|
image->pixelFormat() == IMAGE_TILEMAP) {
|
2020-10-06 21:40:08 +08:00
|
|
|
adjust_ink_for_tilemaps(site, params);
|
2020-06-26 06:21:35 +08:00
|
|
|
}
|
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
if (!params.tool || !params.ink)
|
|
|
|
return nullptr;
|
2015-04-27 02:59:28 +08:00
|
|
|
|
|
|
|
Layer* layer = editor->layer();
|
|
|
|
if (!layer ||
|
2016-06-16 02:27:38 +08:00
|
|
|
!layer->isVisibleHierarchy() ||
|
2016-10-18 00:48:52 +08:00
|
|
|
!layer->isEditableHierarchy() ||
|
|
|
|
layer->isReference()) {
|
2015-04-27 02:59:28 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get fg/bg colors
|
|
|
|
ColorBar* colorbar = ColorBar::instance();
|
2020-06-26 06:21:35 +08:00
|
|
|
if (site.tilemapMode() == TilemapMode::Tiles) {
|
|
|
|
params.fg = app::Color::fromIndex(colorbar->getFgTile()); // TODO Color::fromTileIndex?
|
|
|
|
params.bg = app::Color::fromIndex(colorbar->getBgTile());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params.fg = colorbar->getFgColor();
|
|
|
|
params.bg = colorbar->getBgColor();
|
2020-08-22 05:07:37 +08:00
|
|
|
if (!params.fg.isValid() ||
|
|
|
|
!params.bg.isValid())
|
|
|
|
return nullptr;
|
2020-06-26 06:21:35 +08:00
|
|
|
}
|
2015-04-27 02:59:28 +08:00
|
|
|
|
2020-05-20 07:11:47 +08:00
|
|
|
params.brush = brush;
|
|
|
|
params.button = tools::ToolLoop::Left;
|
|
|
|
params.controller = params.tool->getController(params.button);
|
|
|
|
|
2015-04-27 02:59:28 +08:00
|
|
|
// Create the new tool loop
|
|
|
|
try {
|
2020-05-20 07:11:47 +08:00
|
|
|
fill_toolloop_params_from_tool_preferences(params);
|
|
|
|
|
2015-04-27 02:59:28 +08:00
|
|
|
return new PreviewToolLoopImpl(
|
2020-06-26 06:21:35 +08:00
|
|
|
editor, site, params, image, celOrigin);
|
2015-04-27 02:59:28 +08:00
|
|
|
}
|
2020-05-20 07:11:47 +08:00
|
|
|
catch (const std::exception& e) {
|
|
|
|
LOG(ERROR, e.what());
|
2015-04-27 02:59:28 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:30:17 +08:00
|
|
|
#endif // ENABLE_UI
|
|
|
|
|
2015-04-27 02:59:28 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
} // namespace app
|