1 // Copyright 2021 Google LLC.
2 #include "experimental/sktext/editor/Editor.h"
3 #include "experimental/sktext/src/Paint.h"
4
5 using namespace skia::text;
6
7 namespace skia {
8 namespace editor {
9
Make(std::u16string text,SkSize size)10 std::unique_ptr<Editor> Editor::Make(std::u16string text, SkSize size) {
11 return std::make_unique<Editor>(text, size);
12 }
13
Editor(std::u16string text,SkSize size)14 Editor::Editor(std::u16string text, SkSize size)
15 : fDefaultPositionType(PositionType::kGraphemeCluster)
16 , fInsertMode(true) {
17
18 fParent = nullptr;
19 fCursor = Cursor::Make();
20 fMouse = std::make_unique<Mouse>();
21 {
22 SkPaint foreground; foreground.setColor(DEFAULT_TEXT_FOREGROUND);
23 SkPaint background; background.setColor(DEFAULT_TEXT_BACKGROUND);
24 static FontBlock textBlock(text.size(), sk_make_sp<TrivialFontChain>("Roboto", 40, SkFontStyle::Normal()));
25 static DecoratedBlock textDecor(text.size(), foreground, background);
26 auto textSize = SkSize::Make(size.width(), size.height() - DEFAULT_STATUS_HEIGHT);
27 fEditableText = std::make_unique<EditableText>(
28 text, SkPoint::Make(0, 0), textSize,
29 SkSpan<FontBlock>(&textBlock, 1), SkSpan<DecoratedBlock>(&textDecor, 1),
30 DEFAULT_TEXT_DIRECTION, DEFAULT_TEXT_ALIGN);
31 }
32 {
33 SkPaint foreground; foreground.setColor(DEFAULT_STATUS_FOREGROUND);
34 SkPaint background; background.setColor(DEFAULT_STATUS_BACKGROUND);
35 std::u16string status = u"This is the status line";
36 static FontBlock statusBlock(status.size(), sk_make_sp<TrivialFontChain>("Roboto", 20, SkFontStyle::Normal()));
37 static DecoratedBlock statusDecor(status.size(), foreground, background);
38 auto statusPoint = SkPoint::Make(0, size.height() - DEFAULT_STATUS_HEIGHT);
39 fStatus = std::make_unique<DynamicText>(
40 status, statusPoint, SkSize::Make(size.width(), SK_ScalarInfinity),
41 SkSpan<FontBlock>(&statusBlock, 1), SkSpan<DecoratedBlock>(&statusDecor, 1),
42 DEFAULT_TEXT_DIRECTION, TextAlign::kCenter);
43 }
44 // Place the cursor at the end of the output text
45 // (which is the end of the text for LTR and the beginning of the text for RTL
46 // or possibly something in the middle for a combination of LTR & RTL)
47 // In order to get that position we look for a position outside of the text
48 // and that will give us the last glyph on the line
49 auto endOfText = fEditableText->lastElement(fDefaultPositionType);
50 //fEditableText->recalculateBoundaries(endOfText);
51 fCursor->place(endOfText.fBoundaries);
52 }
53
update()54 void Editor::update() {
55
56 if (fEditableText->isValid()) {
57 return;
58 }
59
60 // Update the (shift it to point at the grapheme edge)
61 auto position = fEditableText->adjustedPosition(fDefaultPositionType, fCursor->getCenterPosition());
62 //fEditableText->recalculateBoundaries(position);
63 fCursor->place(position.fBoundaries);
64
65 // TODO: Update the mouse
66 fMouse->clearTouchInfo();
67 }
68
69 // Moving the cursor by the output grapheme clusters (shifting to another line if necessary)
70 // We don't want to move by the input text indexes because then we will have to take in account LTR/RTL
moveCursor(skui::Key key)71 bool Editor::moveCursor(skui::Key key) {
72 auto cursorPosition = fCursor->getCenterPosition();
73 auto position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
74
75 if (key == skui::Key::kLeft) {
76 position = fEditableText->previousElement(position);
77 } else if (key == skui::Key::kRight) {
78 position = fEditableText->nextElement(position);
79 } else if (key == skui::Key::kHome) {
80 position = fEditableText->firstElement(PositionType::kGraphemeCluster);
81 } else if (key == skui::Key::kEnd) {
82 position = fEditableText->lastElement(PositionType::kGraphemeCluster);
83 } else if (key == skui::Key::kUp) {
84 // Move one line up (if possible)
85 if (position.fLineIndex == 0) {
86 return false;
87 }
88 auto prevLine = fEditableText->getLine(position.fLineIndex - 1);
89 cursorPosition.offset(0, - prevLine.fBounds.height());
90 position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
91 } else if (key == skui::Key::kDown) {
92 // Move one line down (if possible)
93 if (position.fLineIndex == fEditableText->lineCount() - 1) {
94 return false;
95 }
96 auto nextLine = fEditableText->getLine(position.fLineIndex + 1);
97 cursorPosition.offset(0, nextLine.fBounds.height());
98 position = fEditableText->adjustedPosition(PositionType::kGraphemeCluster, cursorPosition);
99 }
100
101 // Place the cursor at the new position
102 //fEditableText->recalculateBoundaries(position);
103 fCursor->place(position.fBoundaries);
104 this->invalidate();
105
106 return true;
107 }
108
onPaint(SkSurface * surface)109 void Editor::onPaint(SkSurface* surface) {
110 SkCanvas* canvas = surface->getCanvas();
111 SkAutoCanvasRestore acr(canvas, true);
112 canvas->clipRect({0, 0, (float)fWidth, (float)fHeight});
113 canvas->drawColor(SK_ColorWHITE);
114 this->paint(canvas);
115 }
116
onResize(int width,int height)117 void Editor::onResize(int width, int height) {
118 if (SkISize{fWidth, fHeight} != SkISize{width, height}) {
119 fHeight = height;
120 if (width != fWidth) {
121 fWidth = width;
122 }
123 this->invalidate();
124 }
125 }
126
onChar(SkUnichar c,skui::ModifierKey modi)127 bool Editor::onChar(SkUnichar c, skui::ModifierKey modi) {
128 using sknonstd::Any;
129
130 modi &= ~skui::ModifierKey::kFirstPress;
131 if (!Any(modi & (skui::ModifierKey::kControl |
132 skui::ModifierKey::kOption |
133 skui::ModifierKey::kCommand))) {
134 if (((unsigned)c < 0x7F && (unsigned)c >= 0x20) || c == 0x000A) {
135 insertCodepoint(c);
136 return true;
137 }
138 }
139 static constexpr skui::ModifierKey kCommandOrControl =
140 skui::ModifierKey::kCommand | skui::ModifierKey::kControl;
141 if (Any(modi & kCommandOrControl) && !Any(modi & ~kCommandOrControl)) {
142 return false;
143 }
144 return false;
145 }
146
deleteElement(skui::Key key)147 bool Editor::deleteElement(skui::Key key) {
148
149 if (fEditableText->isEmpty()) {
150 return false;
151 }
152
153 auto cursorPosition = fCursor->getCenterPosition();
154 auto position = fEditableText->adjustedPosition(fDefaultPositionType, cursorPosition);
155 TextRange textRange = position.fTextRange;
156
157 // IMPORTANT: We assume that a single element (grapheme cluster) does not cross the run boundaries;
158 // It's not exactly true but we are going to enforce in by breaking the grapheme by the run boundaries
159 if (key == skui::Key::kBack) {
160 // TODO: Make sure previous element moves smoothly over the line break
161 position = fEditableText->previousElement(position);
162 textRange = position.fTextRange;
163 fCursor->place(position.fBoundaries);
164 } else {
165 // The cursor stays the the same place
166 }
167
168 fEditableText->removeElement(textRange);
169
170 // Find the grapheme the cursor points to
171 position = fEditableText->adjustedPosition(fDefaultPositionType, SkPoint::Make(position.fBoundaries.fLeft, position.fBoundaries.fTop));
172 fCursor->place(position.fBoundaries);
173 this->invalidate();
174
175 return true;
176 }
177
insertCodepoint(SkUnichar unichar)178 bool Editor::insertCodepoint(SkUnichar unichar) {
179 auto cursorPosition = fCursor->getCenterPosition();
180 auto position = fEditableText->adjustedPosition(fDefaultPositionType, cursorPosition);
181
182 if (fInsertMode) {
183 fEditableText->insertElement(unichar, position.fTextRange.fStart);
184 } else {
185 fEditableText->replaceElement(unichar, position.fTextRange);
186 }
187
188 this->update();
189
190 // Find the element the cursor points to
191 position = fEditableText->adjustedPosition(fDefaultPositionType, cursorPosition);
192
193 // Move the cursor to the next element
194 position = fEditableText->nextElement(position);
195 //fEditableText->recalculateBoundaries(position);
196 fCursor->place(position.fBoundaries);
197
198 this->invalidate();
199
200 return true;
201 }
202
onKey(skui::Key key,skui::InputState state,skui::ModifierKey modifiers)203 bool Editor::onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) {
204
205 if (state != skui::InputState::kDown) {
206 return false;
207 }
208 using sknonstd::Any;
209 skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
210 skui::ModifierKey::kOption |
211 skui::ModifierKey::kCommand);
212 //bool shift = Any(modifiers & (skui::ModifierKey::kShift));
213 if (!Any(ctrlAltCmd)) {
214 // no modifiers
215 switch (key) {
216 case skui::Key::kLeft:
217 case skui::Key::kRight:
218 case skui::Key::kUp:
219 case skui::Key::kDown:
220 case skui::Key::kHome:
221 case skui::Key::kEnd:
222 this->moveCursor(key);
223 break;
224 case skui::Key::kDelete:
225 case skui::Key::kBack:
226 this->deleteElement(key);
227 return true;
228 case skui::Key::kOK:
229 return this->onChar(0x000A, modifiers);
230 default:
231 break;
232 }
233 }
234 return false;
235 }
236
onMouse(int x,int y,skui::InputState state,skui::ModifierKey modifiers)237 bool Editor::onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers) {
238
239 if (!fEditableText->contains(x, y)) {
240 // We only support mouse on an editable area
241 }
242 if (skui::InputState::kDown == state) {
243 auto position = fEditableText->adjustedPosition(fDefaultPositionType, SkPoint::Make(x, y));
244 if (fMouse->isDoubleClick(SkPoint::Make(x, y))) {
245 // Select the element
246 fEditableText->select(position.fTextRange, position.fBoundaries);
247 position.fBoundaries.fLeft = position.fBoundaries.fRight - DEFAULT_CURSOR_WIDTH;
248 // Clear mouse
249 fMouse->up();
250 } else {
251 // Clear selection
252 fMouse->down();
253 fEditableText->clearSelection();
254 }
255
256 fCursor->place(position.fBoundaries);
257 this->invalidate();
258 return true;
259 }
260 fMouse->up();
261 return false;
262 }
263
paint(SkCanvas * canvas)264 void Editor::paint(SkCanvas* canvas) {
265
266 fEditableText->paint(canvas);
267 fCursor->paint(canvas);
268
269 SkPaint background; background.setColor(DEFAULT_STATUS_BACKGROUND);
270 canvas->drawRect(SkRect::MakeXYWH(0, fHeight - DEFAULT_STATUS_HEIGHT, fWidth, DEFAULT_STATUS_HEIGHT), background);
271 fStatus->paint(canvas);
272 }
273
MakeDemo(SkScalar width,SkScalar height)274 std::unique_ptr<Editor> Editor::MakeDemo(SkScalar width, SkScalar height) {
275
276 std::u16string text0 = u"In a hole in the ground there lived a hobbit. Not a nasty, dirty, "
277 "wet hole full of worms and oozy smells.\nThis was a hobbit-hole and "
278 "that means good food, a warm hearth, and all the comforts of home.";
279
280 return Editor::Make(text0, SkSize::Make(width, height));
281 }
282 } // namespace editor
283 } // namespace skia
284