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