| 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 |  | 
|---|