1// Aseprite
2// Copyright (C) 2019-2020 Igara Studio S.A.
3// Copyright (C) 2001-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_rect.h"
14#include "app/commands/command.h"
15#include "app/commands/commands.h"
16#include "app/console.h"
17#include "app/context_access.h"
18#include "app/i18n/strings.h"
19#include "app/modules/editors.h"
20#include "app/tools/active_tool.h"
21#include "app/tools/ink.h"
22#include "app/tools/tool_box.h"
23#include "app/tx.h"
24#include "app/ui/context_bar.h"
25#include "app/ui/editor/editor.h"
26#include "app/ui/editor/select_box_state.h"
27#include "app/ui/keyboard_shortcuts.h"
28#include "app/ui/status_bar.h"
29#include "app/ui_context.h"
30#include "app/util/new_image_from_mask.h"
31#include "base/convert_to.h"
32#include "doc/mask.h"
33
34namespace app {
35
36class NewBrushCommand : public Command
37 , public SelectBoxDelegate {
38public:
39 NewBrushCommand();
40
41protected:
42 bool onEnabled(Context* context) override;
43 void onExecute(Context* context) override;
44
45 // SelectBoxDelegate impl
46 void onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button) override;
47 void onQuickboxCancel(Editor* editor) override;
48
49 std::string onGetContextBarHelp() override {
50 return Strings::new_brush_context_bar_help();
51 }
52
53private:
54 void createBrush(const Site& site, const Mask* mask);
55 void selectPencilTool();
56};
57
58NewBrushCommand::NewBrushCommand()
59 : Command(CommandId::NewBrush(), CmdUIOnlyFlag)
60{
61}
62
63bool NewBrushCommand::onEnabled(Context* context)
64{
65 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
66}
67
68void NewBrushCommand::onExecute(Context* context)
69{
70 ASSERT(current_editor);
71 if (!current_editor)
72 return;
73
74 // If there is no visible mask, the brush must be selected from the
75 // current editor.
76 if (!context->activeDocument()->isMaskVisible()) {
77 EditorStatePtr state = current_editor->getState();
78 if (dynamic_cast<SelectBoxState*>(state.get())) {
79 // If already are in "SelectBoxState" state, in this way we
80 // avoid creating a stack of several "SelectBoxState" states.
81 return;
82 }
83
84 current_editor->setState(
85 EditorStatePtr(
86 new SelectBoxState(
87 this, current_editor->sprite()->bounds(),
88 SelectBoxState::Flags(
89 int(SelectBoxState::Flags::DarkOutside) |
90 int(SelectBoxState::Flags::QuickBox)))));
91 }
92 // Create a brush from the active selection
93 else {
94 createBrush(context->activeSite(),
95 context->activeDocument()->mask());
96 selectPencilTool();
97
98 // Deselect mask
99 Command* cmd =
100 Commands::instance()->byId(CommandId::DeselectMask());
101 UIContext::instance()->executeCommand(cmd);
102 }
103}
104
105void NewBrushCommand::onQuickboxEnd(Editor* editor, const gfx::Rect& rect, ui::MouseButton button)
106{
107 Mask mask;
108 mask.replace(rect);
109 createBrush(editor->getSite(), &mask);
110 selectPencilTool();
111
112 // If the right-button was used, we clear the selected area.
113 if (button == ui::kButtonRight) {
114 try {
115 ContextWriter writer(UIContext::instance());
116 if (writer.cel()) {
117 gfx::Rect canvasRect = (rect & writer.cel()->bounds());
118 if (!canvasRect.isEmpty()) {
119 Tx tx(writer.context(), "Clear");
120 tx(new cmd::ClearRect(writer.cel(), canvasRect));
121 tx.commit();
122 }
123 }
124 }
125 catch (const std::exception& ex) {
126 Console::showException(ex);
127 }
128 }
129
130 // Update the context bar
131 // TODO find a way to avoid all these singletons. Maybe a simple
132 // signal in the context like "brush has changed" could be enough.
133 App::instance()->contextBar()->updateForActiveTool();
134
135 editor->backToPreviousState();
136}
137
138void NewBrushCommand::onQuickboxCancel(Editor* editor)
139{
140 editor->backToPreviousState();
141}
142
143void NewBrushCommand::createBrush(const Site& site, const Mask* mask)
144{
145 doc::ImageRef image(new_image_from_mask(site, mask,
146 Preferences::instance().experimental.newBlend()));
147 if (!image)
148 return;
149
150 // New brush
151 doc::BrushRef brush(new doc::Brush());
152 brush->setImage(image.get(), mask->bitmap());
153 brush->setPatternOrigin(mask->bounds().origin());
154
155 ContextBar* ctxBar = App::instance()->contextBar();
156 int flags = int(BrushSlot::Flags::BrushType);
157 {
158 // TODO merge this code with ContextBar::createBrushSlotFromPreferences()?
159 auto& pref = Preferences::instance();
160 auto& saveBrush = pref.saveBrush;
161 if (saveBrush.imageColor())
162 flags |= int(BrushSlot::Flags::ImageColor);
163 }
164
165 int slot = App::instance()->brushes().addBrushSlot(
166 BrushSlot(BrushSlot::Flags(flags), brush));
167 ctxBar->setActiveBrush(brush);
168
169 // Get the shortcut for this brush and show it to the user
170 Params params;
171 params.set("change", "custom");
172 params.set("slot", base::convert_to<std::string>(slot).c_str());
173 KeyPtr key = KeyboardShortcuts::instance()->command(
174 CommandId::ChangeBrush(), params);
175 if (key && !key->accels().empty()) {
176 std::string tooltip;
177 tooltip += Strings::new_brush_shortcut() + " ";
178 tooltip += key->accels().front().toString();
179 StatusBar::instance()->showTip(2000, tooltip);
180 }
181}
182
183void NewBrushCommand::selectPencilTool()
184{
185 App* app = App::instance();
186 if (app->activeToolManager()->selectedTool()->getInk(0)->isSelection()) {
187 app->activeToolManager()->setSelectedTool(
188 app->toolBox()->getToolById(tools::WellKnownTools::Pencil));
189 }
190}
191
192Command* CommandFactory::createNewBrushCommand()
193{
194 return new NewBrushCommand();
195}
196
197} // namespace app
198