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