Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
editor_application.cpp
Go to the documentation of this file.
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
10#include "src/base/SkTime.h"
11
13#include "tools/sk_app/Window.h"
15
17
18#if defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
20#endif
21
22#if defined(SK_FONTMGR_CORETEXT_AVAILABLE)
24#endif
25
26#if defined(SK_FONTMGR_DIRECTWRITE_AVAILABLE)
28#endif
29
30#include <cfloat>
31#include <fstream>
32#include <memory>
33
36
37#ifdef SK_EDITOR_DEBUG_OUT
38static 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
51static 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
59static 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
68static 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
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}
84namespace {
85
86struct Timer {
87 double fTime;
88 const char* fDesc;
89 Timer(const char* desc = "") : fTime(SkTime::GetNSecs()), fDesc(desc) {}
90 ~Timer() { SkDebugf("%s: %5d μs\n", fDesc, (int)((SkTime::GetNSecs() - fTime) * 1e-3)); }
91};
92
93static constexpr float kFontSize = 18;
94static const char* kTypefaces[3] = {"sans-serif", "serif", "monospace"};
95static constexpr size_t kTypefaceCount = std::size(kTypefaces);
96
98static constexpr SkFontStyle::Width kFontWidth = SkFontStyle::kNormal_Width;
99static constexpr SkFontStyle::Slant kFontSlant = SkFontStyle::kUpright_Slant;
100
101// Note: initialization is not thread safe
103 static bool init = false;
104 static sk_sp<SkFontMgr> fontMgr = nullptr;
105 if (!init) {
106#if defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
108#elif defined(SK_FONTMGR_CORETEXT_AVAILABLE)
110#elif defined(SK_FONTMGR_DIRECTWRITE_AVAILABLE)
111 fontMgr = SkFontMgr_New_DirectWrite();
112#endif
113 init = true;
114 }
115 return fontMgr;
116}
117
118struct EditorLayer : public sk_app::Window::Layer {
119 EditorLayer() {
120 fEditor.setFontMgr(fontMgr());
121 }
122
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
140 void setFont() {
141 fEditor.setFont(SkFont(fontMgr()->matchFamilyStyle(kTypefaces[fTypefaceIndex],
142 SkFontStyle(kFontWeight, kFontWidth, kFontSlant)), fFontSize));
143 }
144
145
146 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
156 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
180 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
191 void onAttach(sk_app::Window* w) override { fParent = w; }
192
193 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
203 void inval() { if (fParent) { fParent->inval(); } }
204
205 bool onMouseWheel(float delta, int, int, skui::ModifierKey) override {
206 this->scroll(-(int)(delta * fEditor.font().getSpacing()));
207 return true;
208 }
209
210 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
224 bool onChar(SkUnichar c, skui::ModifierKey modi) override {
225 using sknonstd::Any;
226 modi &= ~skui::ModifierKey::kFirstPress;
227 if (!Any(modi & (skui::ModifierKey::kControl |
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 |
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
307 bool moveCursor(Editor::Movement m, bool shift = false) {
308 return this->move(fEditor.move(m, fTextPos), shift);
309 }
310
311 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
335 bool onKey(skui::Key key,
337 skui::ModifierKey modifiers) override {
339 return false; // ignore keyup
340 }
341 // ignore other modifiers.
342 using sknonstd::Any;
343 skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
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 |
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
403static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kVulkan_BackendType;
404#elif SK_METAL
405static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kMetal_BackendType;
406#elif SK_GL
407static constexpr sk_app::Window::BackendType kBackendType = sk_app::Window::kNativeGL_BackendType;
408#else
410#endif
411
412struct EditorApplication : public sk_app::Application {
413 std::unique_ptr<sk_app::Window> fWindow;
414 EditorLayer fLayer;
415 double fNextTime = -DBL_MAX;
416
417 EditorApplication(std::unique_ptr<sk_app::Window> win) : fWindow(std::move(win)) {}
418
419 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 }
433 ~EditorApplication() override { fWindow->detach(); }
434
435 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
447sk_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}
SkPath fPath
const char * options
int count
SkPoint pos
#define SK_ABORT(message,...)
Definition SkAssert.h:70
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
SK_API sk_sp< SkFontMgr > SkFontMgr_New_FontConfig(FcConfig *fc)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_CoreText(CTFontCollectionRef)
SK_API SkString static SkString SkStringPrintf()
Definition SkString.h:287
int32_t SkUnichar
Definition SkTypes.h:175
static const SkScalar Y
static const SkScalar X
#define Z
void clipRect(const SkRect &rect, SkClipOp op, bool doAntiAlias)
void translate(SkScalar dx, SkScalar dy)
static sk_sp< SkData > MakeFromFileName(const char path[])
Definition SkData.cpp:148
SkScalar getSpacing() const
Definition SkFont.h:494
int getHeight() const
Definition editor.h:30
void paint(SkCanvas *canvas, PaintOpts)
Definition editor.cpp:435
SkRect getLocation(TextPosition)
Definition editor.cpp:131
void setFont(SkFont font)
Definition editor.cpp:54
StringView line(size_t i) const
Definition editor.h:89
size_t copy(TextPosition pos1, TextPosition pos2, char *dst=nullptr) const
Definition editor.cpp:219
TextPosition move(Editor::Movement move, Editor::TextPosition pos) const
Definition editor.cpp:287
TextPosition insert(TextPosition, const char *utf8Text, size_t byteLen)
Definition editor.cpp:153
TextPosition getPosition(SkIPoint)
Definition editor.cpp:77
TextPosition remove(TextPosition, TextPosition)
Definition editor.cpp:181
const SkFont & font() const
Definition editor.h:36
size_t lineCount() const
Definition editor.h:88
const char * c_str() const
Definition SkString.h:133
virtual void onIdle()=0
static Application * Create(int argc, char **argv, void *platformData)
virtual bool onChar(SkUnichar c, skui::ModifierKey)
Definition Window.h:114
virtual bool onMouseWheel(float delta, int x, int y, skui::ModifierKey)
Definition Window.h:117
virtual bool onKey(skui::Key, skui::InputState, skui::ModifierKey)
Definition Window.h:115
virtual void onAttach(Window *window)
Definition Window.h:113
virtual void onResize(int width, int height)
Definition Window.h:125
virtual bool onMouse(int x, int y, skui::InputState, skui::ModifierKey)
Definition Window.h:116
static Window * CreateNativeWindow(void *platformData)
@ kRaster_BackendType
Definition Window.h:90
int height() const
Definition Window.cpp:130
int width() const
Definition Window.cpp:123
void onPaint()
Definition Window.cpp:81
static Editor::Movement convert(skui::Key key)
VkSurfaceKHR surface
Definition main.cc:49
sk_sp< SkFontMgr > fontMgr
Definition examples.cpp:32
struct MyStruct s
AtkStateType state
std::u16string text
T __attribute__((ext_vector_type(N))) V
double y
double x
Span< const char > StringView
Definition stringview.h:16
double GetNSecs()
Definition SkTime.cpp:17
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switches.h:41
std::enable_if_t< sknonstd::is_bitmask_enum< E >::value, bool > constexpr Any(E e)
InputState
Definition InputState.h:6
ModifierKey
Definition ModifierKey.h:9
Key
Definition Key.h:6
Definition ref_ptr.h:256
init(device_serial, adb_binary)
Definition _adb_path.py:12
SkScalar w
int32_t height
int32_t width
#define M(PROC, DITHER)
constexpr int32_t top() const
Definition SkRect.h:120
constexpr int32_t bottom() const
Definition SkRect.h:134
void roundOut(SkIRect *dst) const
Definition SkRect.h:1241