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