• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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