1// Aseprite
2// Copyright (C) 2019-2021 Igara Studio S.A.
3// Copyright (C) 2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/app.h"
13#include "app/cmd/clear_mask.h"
14#include "app/cmd/trim_cel.h"
15#include "app/color_utils.h"
16#include "app/commands/command.h"
17#include "app/context_access.h"
18#include "app/modules/editors.h"
19#include "app/pref/preferences.h"
20#include "app/tx.h"
21#include "app/ui/editor/editor.h"
22#include "app/util/expand_cel_canvas.h"
23#include "doc/algorithm/fill_selection.h"
24#include "doc/algorithm/stroke_selection.h"
25#include "doc/mask.h"
26
27namespace app {
28
29class FillCommand : public Command {
30public:
31 enum Type { Fill, Stroke };
32 FillCommand(Type type);
33protected:
34 bool onEnabled(Context* ctx) override;
35 void onExecute(Context* ctx) override;
36private:
37 Type m_type;
38};
39
40FillCommand::FillCommand(Type type)
41 : Command(type == Stroke ? CommandId::Stroke():
42 CommandId::Fill(), CmdUIOnlyFlag)
43 , m_type(type)
44{
45}
46
47bool FillCommand::onEnabled(Context* ctx)
48{
49 if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
50 ContextFlags::ActiveLayerIsVisible |
51 ContextFlags::ActiveLayerIsEditable |
52 ContextFlags::ActiveLayerIsImage)) {
53 return true;
54 }
55#if ENABLE_UI
56 else if (current_editor &&
57 current_editor->isMovingPixels()) {
58 return true;
59 }
60#endif
61 else
62 return false;
63}
64
65void FillCommand::onExecute(Context* ctx)
66{
67 ContextWriter writer(ctx);
68 Site site = *writer.site();
69 Doc* doc = site.document();
70 Sprite* sprite = site.sprite();
71 Layer* layer = site.layer();
72 Mask* mask = doc->mask();
73 if (!doc || !sprite ||
74 !layer || !layer->isImage() ||
75 !mask || !doc->isMaskVisible())
76 return;
77
78 Preferences& pref = Preferences::instance();
79 doc::color_t color;
80 if (site.tilemapMode() == TilemapMode::Tiles)
81 color = pref.colorBar.fgTile();
82 else
83 color = color_utils::color_for_layer(pref.colorBar.fgColor(), layer);
84
85 {
86 Tx tx(writer.context(), "Fill Selection with Foreground Color");
87 {
88 ExpandCelCanvas expand(
89 site, layer,
90 TiledMode::NONE, tx,
91 ExpandCelCanvas::None);
92
93 gfx::Region rgn(sprite->bounds() |
94 mask->bounds());
95 expand.validateDestCanvas(rgn);
96
97 gfx::Rect imageBounds(expand.getCel()->position(),
98 expand.getDestCanvas()->size());
99 doc::Grid grid = site.grid();
100
101 if (site.tilemapMode() == TilemapMode::Tiles)
102 imageBounds = grid.tileToCanvas(imageBounds);
103
104 if (m_type == Stroke) {
105 doc::algorithm::stroke_selection(
106 expand.getDestCanvas(),
107 imageBounds,
108 mask,
109 color,
110 (site.tilemapMode() == TilemapMode::Tiles ? &grid: nullptr));
111 }
112 else {
113 doc::algorithm::fill_selection(
114 expand.getDestCanvas(),
115 imageBounds,
116 mask,
117 color,
118 (site.tilemapMode() == TilemapMode::Tiles ? &grid: nullptr));
119 }
120
121 expand.commit();
122 }
123
124 // If the cel wasn't deleted by cmd::ClearMask, we trim it.
125 Cel* cel = ctx->activeSite().cel();
126 if (site.shouldTrimCel(cel))
127 tx(new cmd::TrimCel(cel));
128
129 tx.commit();
130 }
131
132 doc->notifyGeneralUpdate();
133}
134
135Command* CommandFactory::createFillCommand()
136{
137 return new FillCommand(FillCommand::Fill);
138}
139
140Command* CommandFactory::createStrokeCommand()
141{
142 return new FillCommand(FillCommand::Stroke);
143}
144
145} // namespace app
146