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