Flutter Engine
The Flutter Engine
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
73static 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;
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) {
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) {
351 return this->scroll(fHeight * 4 / 5);
353 return this->scroll(-fHeight * 4 / 5);
354 case skui::Key::kLeft:
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);
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);
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
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
Definition: FontMgrTest.cpp:50
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 SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
int32_t SkUnichar
Definition: SkTypes.h:175
static const SkScalar Y
Definition: StrokeBench.cpp:55
static const SkScalar X
Definition: StrokeBench.cpp:54
#define Z
void clipRect(const SkRect &rect, SkClipOp op, bool doAntiAlias)
Definition: SkCanvas.cpp:1361
void translate(SkScalar dx, SkScalar dy)
Definition: SkCanvas.cpp:1278
static sk_sp< SkData > MakeFromFileName(const char path[])
Definition: SkData.cpp:148
Definition: SkFont.h:35
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
Text text() const
Definition: editor.h:57
void setWidth(int w)
Definition: editor.cpp:68
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
virtual void onPaint(SkSurface *)
Definition: Window.h:124
static Window * CreateNativeWindow(void *platformData)
@ kRaster_BackendType
Definition: Window.h:90
int height() const
Definition: Window.cpp:130
void inval()
Definition: Window.cpp:195
int width() const
Definition: Window.cpp:123
static Editor::Movement convert(skui::Key key)
@ kUp
Definition: embedder.h:973
@ kDown
Definition: embedder.h:980
VkSurfaceKHR surface
Definition: main.cc:49
sk_sp< SkFontMgr > fontMgr
Definition: examples.cpp:32
struct MyStruct s
AtkStateType state
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
char ** argv
Definition: library.h:9
T __attribute__((ext_vector_type(N))) V
double y
double x
static bool init()
Span< const char > StringView
Definition: stringview.h:16
double GetNSecs()
Definition: SkTime.cpp:17
static uint32_t Hash(uint32_t key)
Definition: hashmap_test.cc:65
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
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
std::enable_if_t< sknonstd::is_bitmask_enum< E >::value, bool > constexpr Any(E e)
Definition: SkBitmaskEnum.h:16
InputState
Definition: InputState.h:6
ModifierKey
Definition: ModifierKey.h:9
Key
Definition: Key.h:6
@ kBack
(CLR)
@ kHome
the home key - added to match android
@ kEnd
the red key
@ kOK
the center key
Definition: ref_ptr.h:256
SkScalar w
int32_t height
int32_t width
#define M(PROC, DITHER)
Definition: SkRect.h:32
constexpr int32_t top() const
Definition: SkRect.h:120
constexpr int32_t bottom() const
Definition: SkRect.h:134
Definition: SkSize.h:16
void roundOut(SkIRect *dst) const
Definition: SkRect.h:1241
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63