xref: /aosp_15_r20/external/skia/modules/skplaintexteditor/app/editor_application.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 // Copyright 2019 Google LLC.
2 // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3 
4 // Proof of principle of a text editor written with Skia & SkShaper.
5 // https://bugs.skia.org/9020
6 
7 #include "include/core/SkCanvas.h"
8 #include "include/core/SkData.h"
9 #include "include/core/SkFontMgr.h"
10 #include "include/core/SkSurface.h"
11 #include "src/base/SkTime.h"
12 
13 #include "tools/sk_app/Application.h"
14 #include "tools/sk_app/Window.h"
15 #include "tools/skui/ModifierKey.h"
16 
17 #include "modules/skplaintexteditor/include/editor.h"
18 
19 #if defined(SK_FONTMGR_FONTCONFIG_AVAILABLE) && defined(SK_TYPEFACE_FACTORY_FREETYPE)
20 #include "include/ports/SkFontMgr_fontconfig.h"
21 #include "include/ports/SkFontScanner_FreeType.h"
22 #endif
23 
24 #if defined(SK_FONTMGR_CORETEXT_AVAILABLE)
25 #include "include/ports/SkFontMgr_mac_ct.h"
26 #endif
27 
28 #if defined(SK_FONTMGR_DIRECTWRITE_AVAILABLE)
29 #include "include/ports/SkTypeface_win.h"
30 #endif
31 
32 #include <cfloat>
33 #include <fstream>
34 #include <memory>
35 
36 using SkPlainTextEditor::Editor;
37 using SkPlainTextEditor::StringView;
38 
39 #ifdef SK_EDITOR_DEBUG_OUT
key_name(skui::Key k)40 static const char* key_name(skui::Key k) {
41     switch (k) {
42         #define M(X) case skui::Key::k ## X: return #X
43         M(NONE); M(LeftSoftKey); M(RightSoftKey); M(Home); M(Back); M(Send); M(End); M(0); M(1);
44         M(2); M(3); M(4); M(5); M(6); M(7); M(8); M(9); M(Star); M(Hash); M(Up); M(Down); M(Left);
45         M(Right); M(Tab); M(PageUp); M(PageDown); M(Delete); M(Escape); M(Shift); M(Ctrl);
46         M(Option); M(A); M(C); M(V); M(X); M(Y); M(Z); M(OK); M(VolUp); M(VolDown); M(Power);
47         M(Camera);
48         #undef M
49         default: return "?";
50     }
51 }
52 
modifiers_desc(skui::ModifierKey m)53 static SkString modifiers_desc(skui::ModifierKey m) {
54     SkString s;
55     #define M(X) if (m & skui::ModifierKey::k ## X ##) { s.append(" {" #X "}"); }
56     M(Shift) M(Control) M(Option) M(Command) M(FirstPress)
57     #undef M
58     return s;
59 }
60 
debug_on_char(SkUnichar c,skui::ModifierKey modifiers)61 static void debug_on_char(SkUnichar c, skui::ModifierKey modifiers) {
62     SkString m = modifiers_desc(modifiers);
63     if ((unsigned)c < 0x100) {
64         SkDebugf("char: %c (0x%02X)%s\n", (char)(c & 0xFF), (unsigned)c, m.c_str());
65     } else {
66         SkDebugf("char: 0x%08X%s\n", (unsigned)c, m.c_str());
67     }
68 }
69 
debug_on_key(skui::Key key,skui::InputState,skui::ModifierKey modi)70 static void debug_on_key(skui::Key key, skui::InputState, skui::ModifierKey modi) {
71     SkDebugf("key: %s%s\n", key_name(key), modifiers_desc(modi).c_str());
72 }
73 #endif  // SK_EDITOR_DEBUG_OUT
74 
convert(skui::Key key)75 static Editor::Movement convert(skui::Key key) {
76     switch (key) {
77         case skui::Key::kLeft:  return Editor::Movement::kLeft;
78         case skui::Key::kRight: return Editor::Movement::kRight;
79         case skui::Key::kUp:    return Editor::Movement::kUp;
80         case skui::Key::kDown:  return Editor::Movement::kDown;
81         case skui::Key::kHome:  return Editor::Movement::kHome;
82         case skui::Key::kEnd:   return Editor::Movement::kEnd;
83         default: return Editor::Movement::kNowhere;
84     }
85 }
86 namespace {
87 
88 struct Timer {
89     double fTime;
90     const char* fDesc;
Timer__anon8032d36e0111::Timer91     Timer(const char* desc = "") : fTime(SkTime::GetNSecs()), fDesc(desc) {}
~Timer__anon8032d36e0111::Timer92     ~Timer() { SkDebugf("%s: %5d μs\n", fDesc, (int)((SkTime::GetNSecs() - fTime) * 1e-3)); }
93 };
94 
95 static constexpr float kFontSize = 18;
96 static const char* kTypefaces[3] = {"sans-serif", "serif", "monospace"};
97 static constexpr size_t kTypefaceCount = std::size(kTypefaces);
98 
99 static constexpr SkFontStyle::Weight kFontWeight = SkFontStyle::kNormal_Weight;
100 static constexpr SkFontStyle::Width  kFontWidth  = SkFontStyle::kNormal_Width;
101 static constexpr SkFontStyle::Slant  kFontSlant  = SkFontStyle::kUpright_Slant;
102 
103 // Note: initialization is not thread safe
fontMgr()104 sk_sp<SkFontMgr> fontMgr() {
105     static bool init = false;
106     static sk_sp<SkFontMgr> fontMgr = nullptr;
107     if (!init) {
108 #if defined(SK_FONTMGR_FONTCONFIG_AVAILABLE) && defined(SK_TYPEFACE_FACTORY_FREETYPE)
109         fontMgr = SkFontMgr_New_FontConfig(nullptr, SkFontScanner_Make_FreeType());
110 #elif defined(SK_FONTMGR_CORETEXT_AVAILABLE)
111         fontMgr = SkFontMgr_New_CoreText(nullptr);
112 #elif defined(SK_FONTMGR_DIRECTWRITE_AVAILABLE)
113         fontMgr = SkFontMgr_New_DirectWrite();
114 #endif
115         init = true;
116     }
117     return fontMgr;
118 }
119 
120 struct EditorLayer : public sk_app::Window::Layer {
EditorLayer__anon8032d36e0111::EditorLayer121     EditorLayer() {
122         fEditor.setFontMgr(fontMgr());
123     }
124 
125     SkString fPath;
126     sk_app::Window* fParent = nullptr;
127     // TODO(halcanary): implement a cross-platform clipboard interface.
128     std::vector<char> fClipboard;
129     Editor fEditor;
130     Editor::TextPosition fTextPos{0, 0};
131     Editor::TextPosition fMarkPos;
132     int fPos = 0;  // window pixel position in file
133     int fWidth = 0;  // window width
134     int fHeight = 0;  // window height
135     int fMargin = 10;
136     size_t fTypefaceIndex = 0;
137     float fFontSize = kFontSize;
138     bool fShiftDown = false;
139     bool fBlink = false;
140     bool fMouseDown = false;
141 
setFont__anon8032d36e0111::EditorLayer142     void setFont() {
143         fEditor.setFont(SkFont(fontMgr()->matchFamilyStyle(kTypefaces[fTypefaceIndex],
144                                SkFontStyle(kFontWeight, kFontWidth, kFontSlant)), fFontSize));
145     }
146 
147 
loadFile__anon8032d36e0111::EditorLayer148     void loadFile(const char* path) {
149         if (sk_sp<SkData> data = SkData::MakeFromFileName(path)) {
150             fPath = path;
151             fEditor.insert(Editor::TextPosition{0, 0},
152                            (const char*)data->data(), data->size());
153         } else {
154             fPath  = "output.txt";
155         }
156     }
157 
onPaint__anon8032d36e0111::EditorLayer158     void onPaint(SkSurface* surface) override {
159         SkCanvas* canvas = surface->getCanvas();
160         SkAutoCanvasRestore acr(canvas, true);
161         canvas->clipRect({0, 0, (float)fWidth, (float)fHeight});
162         canvas->translate(fMargin, (float)(fMargin - fPos));
163         Editor::PaintOpts options;
164         options.fCursor = fTextPos;
165         options.fCursorColor = {1, 0, 0, fBlink ? 0.0f : 1.0f};
166         options.fBackgroundColor = SkColor4f{0.8f, 0.8f, 0.8f, 1};
167         options.fCursorColor = {1, 0, 0, fBlink ? 0.0f : 1.0f};
168         if (fMarkPos != Editor::TextPosition()) {
169             options.fSelectionBegin = fMarkPos;
170             options.fSelectionEnd = fTextPos;
171         }
172         #ifdef SK_EDITOR_DEBUG_OUT
173         {
174             Timer timer("shaping");
175             fEditor.paint(nullptr, options);
176         }
177         Timer timer("painting");
178         #endif  // SK_EDITOR_DEBUG_OUT
179         fEditor.paint(canvas, options);
180     }
181 
onResize__anon8032d36e0111::EditorLayer182     void onResize(int width, int height) override {
183         if (SkISize{fWidth, fHeight} != SkISize{width, height}) {
184             fHeight = height;
185             if (width != fWidth) {
186                 fWidth = width;
187                 fEditor.setWidth(fWidth - 2 * fMargin);
188             }
189             this->inval();
190         }
191     }
192 
onAttach__anon8032d36e0111::EditorLayer193     void onAttach(sk_app::Window* w) override { fParent = w; }
194 
scroll__anon8032d36e0111::EditorLayer195     bool scroll(int delta) {
196         int maxPos = std::max(0, fEditor.getHeight() + 2 * fMargin - fHeight / 2);
197         int newpos = std::max(0, std::min(fPos + delta, maxPos));
198         if (newpos != fPos) {
199             fPos = newpos;
200             this->inval();
201         }
202         return true;
203     }
204 
inval__anon8032d36e0111::EditorLayer205     void inval() { if (fParent) { fParent->inval(); } }
206 
onMouseWheel__anon8032d36e0111::EditorLayer207     bool onMouseWheel(float delta, int, int, skui::ModifierKey) override {
208         this->scroll(-(int)(delta * fEditor.font().getSpacing()));
209         return true;
210     }
211 
onMouse__anon8032d36e0111::EditorLayer212     bool onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers) override {
213         bool mouseDown = skui::InputState::kDown == state;
214         if (mouseDown) {
215             fMouseDown = true;
216         } else if (skui::InputState::kUp == state) {
217             fMouseDown = false;
218         }
219         bool shiftOrDrag = sknonstd::Any(modifiers & skui::ModifierKey::kShift) || !mouseDown;
220         if (fMouseDown) {
221             return this->move(fEditor.getPosition({x - fMargin, y + fPos - fMargin}), shiftOrDrag);
222         }
223         return false;
224     }
225 
onChar__anon8032d36e0111::EditorLayer226     bool onChar(SkUnichar c, skui::ModifierKey modi) override {
227         using sknonstd::Any;
228         modi &= ~skui::ModifierKey::kFirstPress;
229         if (!Any(modi & (skui::ModifierKey::kControl |
230                          skui::ModifierKey::kOption  |
231                          skui::ModifierKey::kCommand))) {
232             if (((unsigned)c < 0x7F && (unsigned)c >= 0x20) || c == '\n') {
233                 char ch = (char)c;
234                 fEditor.insert(fTextPos, &ch, 1);
235                 #ifdef SK_EDITOR_DEBUG_OUT
236                 SkDebugf("insert: %X'%c'\n", (unsigned)c, ch);
237                 #endif  // SK_EDITOR_DEBUG_OUT
238                 return this->moveCursor(Editor::Movement::kRight);
239             }
240         }
241         static constexpr skui::ModifierKey kCommandOrControl = skui::ModifierKey::kCommand |
242                                                                skui::ModifierKey::kControl;
243         if (Any(modi & kCommandOrControl) && !Any(modi & ~kCommandOrControl)) {
244             switch (c) {
245                 case 'p':
246                     for (StringView str : fEditor.text()) {
247                         SkDebugf(">>  '%.*s'\n", (int)str.size, str.data);
248                     }
249                     return true;
250                 case 's':
251                     {
252                         std::ofstream out(fPath.c_str());
253                         size_t count = fEditor.lineCount();
254                         for (size_t i = 0; i < count; ++i) {
255                             if (i != 0) {
256                                 out << '\n';
257                             }
258                             StringView str = fEditor.line(i);
259                             out.write(str.data, str.size);
260                         }
261                     }
262                     return true;
263                 case 'c':
264                     if (fMarkPos != Editor::TextPosition()) {
265                         fClipboard.resize(fEditor.copy(fMarkPos, fTextPos, nullptr));
266                         fEditor.copy(fMarkPos, fTextPos, fClipboard.data());
267                         return true;
268                     }
269                     return false;
270                 case 'x':
271                     if (fMarkPos != Editor::TextPosition()) {
272                         fClipboard.resize(fEditor.copy(fMarkPos, fTextPos, nullptr));
273                         fEditor.copy(fMarkPos, fTextPos, fClipboard.data());
274                         (void)this->move(fEditor.remove(fMarkPos, fTextPos), false);
275                         this->inval();
276                         return true;
277                     }
278                     return false;
279                 case 'v':
280                     if (fClipboard.size()) {
281                         fEditor.insert(fTextPos, fClipboard.data(), fClipboard.size());
282                         this->inval();
283                         return true;
284                     }
285                     return false;
286                 case '0':
287                     fTypefaceIndex = (fTypefaceIndex + 1) % kTypefaceCount;
288                     this->setFont();
289                     return true;
290                 case '=':
291                 case '+':
292                     fFontSize = fFontSize + 1;
293                     this->setFont();
294                     return true;
295                 case '-':
296                 case '_':
297                     if (fFontSize > 1) {
298                         fFontSize = fFontSize - 1;
299                         this->setFont();
300                     }
301             }
302         }
303         #ifdef SK_EDITOR_DEBUG_OUT
304         debug_on_char(c, modifiers);
305         #endif  // SK_EDITOR_DEBUG_OUT
306         return false;
307     }
308 
moveCursor__anon8032d36e0111::EditorLayer309     bool moveCursor(Editor::Movement m, bool shift = false) {
310         return this->move(fEditor.move(m, fTextPos), shift);
311     }
312 
move__anon8032d36e0111::EditorLayer313     bool move(Editor::TextPosition pos, bool shift) {
314         if (pos == fTextPos || pos == Editor::TextPosition()) {
315             if (!shift) {
316                 fMarkPos = Editor::TextPosition();
317             }
318             return false;
319         }
320         if (shift != fShiftDown) {
321             fMarkPos = shift ? fTextPos : Editor::TextPosition();
322             fShiftDown = shift;
323         }
324         fTextPos = pos;
325 
326         // scroll if needed.
327         SkIRect cursor = fEditor.getLocation(fTextPos).roundOut();
328         if (fPos < cursor.bottom() - fHeight + 2 * fMargin) {
329             fPos = cursor.bottom() - fHeight + 2 * fMargin;
330         } else if (cursor.top() < fPos) {
331             fPos = cursor.top();
332         }
333         this->inval();
334         return true;
335     }
336 
onKey__anon8032d36e0111::EditorLayer337     bool onKey(skui::Key key,
338                skui::InputState state,
339                skui::ModifierKey modifiers) override {
340         if (state != skui::InputState::kDown) {
341             return false;  // ignore keyup
342         }
343         // ignore other modifiers.
344         using sknonstd::Any;
345         skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
346                                               skui::ModifierKey::kOption  |
347                                               skui::ModifierKey::kCommand);
348         bool shift = Any(modifiers & (skui::ModifierKey::kShift));
349         if (!Any(ctrlAltCmd)) {
350             // no modifiers
351             switch (key) {
352                 case skui::Key::kPageDown:
353                     return this->scroll(fHeight * 4 / 5);
354                 case skui::Key::kPageUp:
355                     return this->scroll(-fHeight * 4 / 5);
356                 case skui::Key::kLeft:
357                 case skui::Key::kRight:
358                 case skui::Key::kUp:
359                 case skui::Key::kDown:
360                 case skui::Key::kHome:
361                 case skui::Key::kEnd:
362                     return this->moveCursor(convert(key), shift);
363                 case skui::Key::kDelete:
364                     if (fMarkPos != Editor::TextPosition()) {
365                         (void)this->move(fEditor.remove(fMarkPos, fTextPos), false);
366                     } else {
367                         auto pos = fEditor.move(Editor::Movement::kRight, fTextPos);
368                         (void)this->move(fEditor.remove(fTextPos, pos), false);
369                     }
370                     this->inval();
371                     return true;
372                 case skui::Key::kBack:
373                     if (fMarkPos != Editor::TextPosition()) {
374                         (void)this->move(fEditor.remove(fMarkPos, fTextPos), false);
375                     } else {
376                         auto pos = fEditor.move(Editor::Movement::kLeft, fTextPos);
377                         (void)this->move(fEditor.remove(fTextPos, pos), false);
378                     }
379                     this->inval();
380                     return true;
381                 case skui::Key::kOK:
382                     return this->onChar('\n', modifiers);
383                 default:
384                     break;
385             }
386         } else if (sknonstd::Any(ctrlAltCmd & (skui::ModifierKey::kControl |
387                                                skui::ModifierKey::kCommand))) {
388             switch (key) {
389                 case skui::Key::kLeft:
390                     return this->moveCursor(Editor::Movement::kWordLeft, shift);
391                 case skui::Key::kRight:
392                     return this->moveCursor(Editor::Movement::kWordRight, shift);
393                 default:
394                     break;
395             }
396         }
397         #ifdef SK_EDITOR_DEBUG_OUT
398         debug_on_key(key, state, modifiers);
399         #endif  // SK_EDITOR_DEBUG_OUT
400         return false;
401     }
402 };
403 
404 #ifdef SK_VULKAN
405 static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kVulkan_BackendType;
406 #elif SK_METAL
407 static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kMetal_BackendType;
408 #elif SK_GL
409 static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kNativeGL_BackendType;
410 #else
411 static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kRaster_BackendType;
412 #endif
413 
414 struct EditorApplication : public sk_app::Application {
415     std::unique_ptr<sk_app::Window> fWindow;
416     EditorLayer fLayer;
417     double fNextTime = -DBL_MAX;
418 
EditorApplication__anon8032d36e0111::EditorApplication419     EditorApplication(std::unique_ptr<sk_app::Window> win) : fWindow(std::move(win)) {}
420 
init__anon8032d36e0111::EditorApplication421     bool init(const char* path) {
422         fWindow->attach(kBackendType);
423 
424         fLayer.loadFile(path);
425         fLayer.setFont();
426 
427         fWindow->pushLayer(&fLayer);
428         fWindow->setTitle(SkStringPrintf("Editor: \"%s\"", fLayer.fPath.c_str()).c_str());
429         fLayer.onResize(fWindow->width(), fWindow->height());
430         fLayer.fEditor.paint(nullptr, Editor::PaintOpts());
431 
432         fWindow->show();
433         return true;
434     }
~EditorApplication__anon8032d36e0111::EditorApplication435     ~EditorApplication() override { fWindow->detach(); }
436 
onIdle__anon8032d36e0111::EditorApplication437     void onIdle() override {
438         double now = SkTime::GetNSecs();
439         if (now >= fNextTime) {
440             constexpr double kHalfPeriodNanoSeconds = 0.5 * 1e9;
441             fNextTime = now + kHalfPeriodNanoSeconds;
442             fLayer.fBlink = !fLayer.fBlink;
443             fWindow->inval();
444         }
445     }
446 };
447 }  // namespace
448 
Create(int argc,char ** argv,void * dat)449 sk_app::Application* sk_app::Application::Create(int argc, char** argv, void* dat) {
450     std::unique_ptr<sk_app::Window> win(sk_app::Windows::CreateNativeWindow(dat));
451     if (!win) {
452         SK_ABORT("CreateNativeWindow failed.");
453     }
454     std::unique_ptr<EditorApplication> app(new EditorApplication(std::move(win)));
455     (void)app->init(argc > 1 ? argv[1] : nullptr);
456     return app.release();
457 }
458