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