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