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