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 | |
34 | namespace app { |
35 | |
36 | class NewBrushCommand : public Command |
37 | , public SelectBoxDelegate { |
38 | public: |
39 | NewBrushCommand(); |
40 | |
41 | protected: |
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 | |
53 | private: |
54 | void createBrush(const Site& site, const Mask* mask); |
55 | void selectPencilTool(); |
56 | }; |
57 | |
58 | NewBrushCommand::NewBrushCommand() |
59 | : Command(CommandId::NewBrush(), CmdUIOnlyFlag) |
60 | { |
61 | } |
62 | |
63 | bool NewBrushCommand::onEnabled(Context* context) |
64 | { |
65 | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable); |
66 | } |
67 | |
68 | void 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 | |
105 | void 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 | |
138 | void NewBrushCommand::onQuickboxCancel(Editor* editor) |
139 | { |
140 | editor->backToPreviousState(); |
141 | } |
142 | |
143 | void 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 | |
183 | void 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 | |
192 | Command* CommandFactory::createNewBrushCommand() |
193 | { |
194 | return new NewBrushCommand(); |
195 | } |
196 | |
197 | } // namespace app |
198 | |