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