| 1 | // Aseprite |
| 2 | // Copyright (C) 2019-2022 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/commands/command.h" |
| 14 | #include "app/console.h" |
| 15 | #include "app/context.h" |
| 16 | #include "app/file_selector.h" |
| 17 | #include "app/modules/editors.h" |
| 18 | #include "app/pref/preferences.h" |
| 19 | #include "app/ui/drop_down_button.h" |
| 20 | #include "app/ui/editor/editor.h" |
| 21 | #include "app/ui/font_popup.h" |
| 22 | #include "app/ui/timeline/timeline.h" |
| 23 | #include "app/util/freetype_utils.h" |
| 24 | #include "base/fs.h" |
| 25 | #include "base/string.h" |
| 26 | #include "doc/image.h" |
| 27 | #include "doc/image_ref.h" |
| 28 | #include "render/dithering.h" |
| 29 | #include "render/ordered_dither.h" |
| 30 | #include "render/quantization.h" |
| 31 | #include "ui/manager.h" |
| 32 | |
| 33 | #include "paste_text.xml.h" |
| 34 | |
| 35 | namespace app { |
| 36 | |
| 37 | static std::string last_text_used; |
| 38 | |
| 39 | class PasteTextCommand : public Command { |
| 40 | public: |
| 41 | PasteTextCommand(); |
| 42 | |
| 43 | protected: |
| 44 | bool onEnabled(Context* ctx) override; |
| 45 | void onExecute(Context* ctx) override; |
| 46 | }; |
| 47 | |
| 48 | PasteTextCommand::PasteTextCommand() |
| 49 | : Command(CommandId::PasteText(), CmdUIOnlyFlag) |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | bool PasteTextCommand::onEnabled(Context* ctx) |
| 54 | { |
| 55 | return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable | |
| 56 | ContextFlags::ActiveLayerIsEditable); |
| 57 | } |
| 58 | |
| 59 | class PasteTextWindow : public app::gen::PasteText { |
| 60 | public: |
| 61 | PasteTextWindow(const std::string& face, int size, |
| 62 | bool antialias, |
| 63 | const app::Color& color) |
| 64 | : m_face(face) { |
| 65 | ok()->setEnabled(!m_face.empty()); |
| 66 | if (!m_face.empty()) |
| 67 | updateFontFaceButton(); |
| 68 | |
| 69 | fontSize()->setTextf("%d" , size); |
| 70 | fontFace()->Click.connect([this]{ onSelectFontFile(); }); |
| 71 | fontFace()->DropDownClick.connect([this]{ onSelectSystemFont(); }); |
| 72 | fontColor()->setColor(color); |
| 73 | this->antialias()->setSelected(antialias); |
| 74 | } |
| 75 | |
| 76 | std::string faceValue() const { |
| 77 | return m_face; |
| 78 | } |
| 79 | |
| 80 | int sizeValue() const { |
| 81 | int size = fontSize()->textInt(); |
| 82 | size = std::clamp(size, 1, 5000); |
| 83 | return size; |
| 84 | } |
| 85 | |
| 86 | private: |
| 87 | void updateFontFaceButton() { |
| 88 | fontFace()->mainButton() |
| 89 | ->setTextf("Select Font: %s" , |
| 90 | base::get_file_title(m_face).c_str()); |
| 91 | } |
| 92 | |
| 93 | void onSelectFontFile() { |
| 94 | base::paths exts = { "ttf" , "ttc" , "otf" , "dfont" }; |
| 95 | base::paths face; |
| 96 | if (!show_file_selector( |
| 97 | "Select a TrueType Font" , |
| 98 | m_face, exts, |
| 99 | FileSelectorType::Open, face)) |
| 100 | return; |
| 101 | |
| 102 | ASSERT(!face.empty()); |
| 103 | setFontFace(face.front()); |
| 104 | } |
| 105 | |
| 106 | void setFontFace(const std::string& face) { |
| 107 | m_face = face; |
| 108 | ok()->setEnabled(true); |
| 109 | updateFontFaceButton(); |
| 110 | } |
| 111 | |
| 112 | void onSelectSystemFont() { |
| 113 | if (!m_fontPopup) { |
| 114 | try { |
| 115 | m_fontPopup.reset(new FontPopup()); |
| 116 | m_fontPopup->Load.connect(&PasteTextWindow::setFontFace, this); |
| 117 | m_fontPopup->Close.connect([this]{ onCloseFontPopup(); }); |
| 118 | } |
| 119 | catch (const std::exception& ex) { |
| 120 | Console::showException(ex); |
| 121 | return; |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | if (!m_fontPopup->isVisible()) { |
| 126 | m_fontPopup->showPopup(display(), fontFace()->bounds()); |
| 127 | } |
| 128 | else { |
| 129 | m_fontPopup->closeWindow(NULL); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | void () { |
| 134 | fontFace()->dropDown()->requestFocus(); |
| 135 | } |
| 136 | |
| 137 | std::string m_face; |
| 138 | std::unique_ptr<FontPopup> ; |
| 139 | }; |
| 140 | |
| 141 | void PasteTextCommand::onExecute(Context* ctx) |
| 142 | { |
| 143 | Editor* editor = current_editor; |
| 144 | if (editor == NULL) |
| 145 | return; |
| 146 | |
| 147 | Preferences& pref = Preferences::instance(); |
| 148 | PasteTextWindow window(pref.textTool.fontFace(), |
| 149 | pref.textTool.fontSize(), |
| 150 | pref.textTool.antialias(), |
| 151 | pref.colorBar.fgColor()); |
| 152 | |
| 153 | window.userText()->setText(last_text_used); |
| 154 | |
| 155 | window.openWindowInForeground(); |
| 156 | if (window.closer() != window.ok()) |
| 157 | return; |
| 158 | |
| 159 | last_text_used = window.userText()->text(); |
| 160 | |
| 161 | bool antialias = window.antialias()->isSelected(); |
| 162 | std::string faceName = window.faceValue(); |
| 163 | int size = window.sizeValue(); |
| 164 | size = std::clamp(size, 1, 999); |
| 165 | pref.textTool.fontFace(faceName); |
| 166 | pref.textTool.fontSize(size); |
| 167 | pref.textTool.antialias(antialias); |
| 168 | |
| 169 | try { |
| 170 | std::string text = window.userText()->text(); |
| 171 | app::Color appColor = window.fontColor()->getColor(); |
| 172 | doc::color_t color = doc::rgba(appColor.getRed(), |
| 173 | appColor.getGreen(), |
| 174 | appColor.getBlue(), |
| 175 | appColor.getAlpha()); |
| 176 | |
| 177 | doc::ImageRef image(render_text(faceName, size, text, color, antialias)); |
| 178 | if (image) { |
| 179 | Sprite* sprite = editor->sprite(); |
| 180 | if (image->pixelFormat() != sprite->pixelFormat()) { |
| 181 | RgbMap* rgbmap = sprite->rgbMap(editor->frame()); |
| 182 | image.reset( |
| 183 | render::convert_pixel_format( |
| 184 | image.get(), NULL, sprite->pixelFormat(), |
| 185 | render::Dithering(), |
| 186 | rgbmap, sprite->palette(editor->frame()), |
| 187 | false, |
| 188 | sprite->transparentColor())); |
| 189 | } |
| 190 | |
| 191 | // TODO we don't support pasting text in multiple cels at the |
| 192 | // moment, so we clear the range here (same as in |
| 193 | // clipboard::paste()) |
| 194 | if (auto timeline = App::instance()->timeline()) |
| 195 | timeline->clearAndInvalidateRange(); |
| 196 | |
| 197 | editor->pasteImage(image.get()); |
| 198 | } |
| 199 | } |
| 200 | catch (const std::exception& ex) { |
| 201 | Console::showException(ex); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | Command* CommandFactory::createPasteTextCommand() |
| 206 | { |
| 207 | return new PasteTextCommand; |
| 208 | } |
| 209 | |
| 210 | } // namespace app |
| 211 | |