xref: /aosp_15_r20/external/skia/modules/skottie/utils/TextEditor.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2022 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/utils/TextEditor.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkM44.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPoint.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSpan.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkUTF.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "tools/skui/InputState.h"
22*c8dee2aaSAndroid Build Coastguard Worker 
23*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
24*c8dee2aaSAndroid Build Coastguard Worker #include <limits>
25*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
26*c8dee2aaSAndroid Build Coastguard Worker 
27*c8dee2aaSAndroid Build Coastguard Worker namespace skottie_utils {
28*c8dee2aaSAndroid Build Coastguard Worker 
29*c8dee2aaSAndroid Build Coastguard Worker namespace {
30*c8dee2aaSAndroid Build Coastguard Worker 
make_cursor_path()31*c8dee2aaSAndroid Build Coastguard Worker SkPath make_cursor_path() {
32*c8dee2aaSAndroid Build Coastguard Worker     // Normalized values, relative to text/font size.
33*c8dee2aaSAndroid Build Coastguard Worker     constexpr float kWidth  = 0.2f,
34*c8dee2aaSAndroid Build Coastguard Worker                     kHeight = 0.75f;
35*c8dee2aaSAndroid Build Coastguard Worker 
36*c8dee2aaSAndroid Build Coastguard Worker     SkPath p;
37*c8dee2aaSAndroid Build Coastguard Worker 
38*c8dee2aaSAndroid Build Coastguard Worker     p.lineTo(kWidth  , 0);
39*c8dee2aaSAndroid Build Coastguard Worker     p.moveTo(kWidth/2, 0);
40*c8dee2aaSAndroid Build Coastguard Worker     p.lineTo(kWidth/2, kHeight);
41*c8dee2aaSAndroid Build Coastguard Worker     p.moveTo(0       , kHeight);
42*c8dee2aaSAndroid Build Coastguard Worker     p.lineTo(kWidth  , kHeight);
43*c8dee2aaSAndroid Build Coastguard Worker 
44*c8dee2aaSAndroid Build Coastguard Worker     return p;
45*c8dee2aaSAndroid Build Coastguard Worker }
46*c8dee2aaSAndroid Build Coastguard Worker 
next_utf8(const SkString & str,size_t index)47*c8dee2aaSAndroid Build Coastguard Worker size_t next_utf8(const SkString& str, size_t index) {
48*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(index < str.size());
49*c8dee2aaSAndroid Build Coastguard Worker 
50*c8dee2aaSAndroid Build Coastguard Worker     const char* utf8_ptr = str.c_str() + index;
51*c8dee2aaSAndroid Build Coastguard Worker 
52*c8dee2aaSAndroid Build Coastguard Worker     if (SkUTF::NextUTF8(&utf8_ptr, str.c_str() + str.size()) < 0){
53*c8dee2aaSAndroid Build Coastguard Worker         // Invalid UTF sequence.
54*c8dee2aaSAndroid Build Coastguard Worker         return index;
55*c8dee2aaSAndroid Build Coastguard Worker     }
56*c8dee2aaSAndroid Build Coastguard Worker 
57*c8dee2aaSAndroid Build Coastguard Worker     return utf8_ptr - str.c_str();
58*c8dee2aaSAndroid Build Coastguard Worker }
59*c8dee2aaSAndroid Build Coastguard Worker 
prev_utf8(const SkString & str,size_t index)60*c8dee2aaSAndroid Build Coastguard Worker size_t prev_utf8(const SkString& str, size_t index) {
61*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(index > 0);
62*c8dee2aaSAndroid Build Coastguard Worker 
63*c8dee2aaSAndroid Build Coastguard Worker     // Find the previous utf8 index by probing the preceding 4 offsets.  Utf8 leading bytes are
64*c8dee2aaSAndroid Build Coastguard Worker     // always distinct from continuation bytes, so only one of these probes will succeed.
65*c8dee2aaSAndroid Build Coastguard Worker     for (unsigned i = 1; i <= SkUTF::kMaxBytesInUTF8Sequence && i <= index; ++i) {
66*c8dee2aaSAndroid Build Coastguard Worker         const char* utf8_ptr = str.c_str() + index - i;
67*c8dee2aaSAndroid Build Coastguard Worker         if (SkUTF::NextUTF8(&utf8_ptr, str.c_str() + str.size()) >= 0) {
68*c8dee2aaSAndroid Build Coastguard Worker             return index - i;
69*c8dee2aaSAndroid Build Coastguard Worker         }
70*c8dee2aaSAndroid Build Coastguard Worker     }
71*c8dee2aaSAndroid Build Coastguard Worker 
72*c8dee2aaSAndroid Build Coastguard Worker     // Invalid UTF sequence.
73*c8dee2aaSAndroid Build Coastguard Worker     return index;
74*c8dee2aaSAndroid Build Coastguard Worker }
75*c8dee2aaSAndroid Build Coastguard Worker 
76*c8dee2aaSAndroid Build Coastguard Worker } // namespace
77*c8dee2aaSAndroid Build Coastguard Worker 
TextEditor(std::unique_ptr<skottie::TextPropertyHandle> && prop,std::vector<std::unique_ptr<skottie::TextPropertyHandle>> && deps)78*c8dee2aaSAndroid Build Coastguard Worker TextEditor::TextEditor(
79*c8dee2aaSAndroid Build Coastguard Worker         std::unique_ptr<skottie::TextPropertyHandle>&& prop,
80*c8dee2aaSAndroid Build Coastguard Worker         std::vector<std::unique_ptr<skottie::TextPropertyHandle>>&& deps)
81*c8dee2aaSAndroid Build Coastguard Worker     : fTextProp(std::move(prop))
82*c8dee2aaSAndroid Build Coastguard Worker     , fDependentProps(std::move(deps))
83*c8dee2aaSAndroid Build Coastguard Worker     , fCursorPath(make_cursor_path())
84*c8dee2aaSAndroid Build Coastguard Worker     , fCursorBounds(fCursorPath.computeTightBounds())
85*c8dee2aaSAndroid Build Coastguard Worker {}
86*c8dee2aaSAndroid Build Coastguard Worker 
87*c8dee2aaSAndroid Build Coastguard Worker TextEditor::~TextEditor() = default;
88*c8dee2aaSAndroid Build Coastguard Worker 
toggleEnabled()89*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::toggleEnabled() {
90*c8dee2aaSAndroid Build Coastguard Worker     fEnabled = !fEnabled;
91*c8dee2aaSAndroid Build Coastguard Worker 
92*c8dee2aaSAndroid Build Coastguard Worker     auto txt = fTextProp->get();
93*c8dee2aaSAndroid Build Coastguard Worker     txt.fDecorator = fEnabled ? sk_ref_sp(this) : nullptr;
94*c8dee2aaSAndroid Build Coastguard Worker     fTextProp->set(txt);
95*c8dee2aaSAndroid Build Coastguard Worker 
96*c8dee2aaSAndroid Build Coastguard Worker     if (fEnabled) {
97*c8dee2aaSAndroid Build Coastguard Worker         // Always reset the cursor position to the end.
98*c8dee2aaSAndroid Build Coastguard Worker         fCursorIndex = txt.fText.size();
99*c8dee2aaSAndroid Build Coastguard Worker     }
100*c8dee2aaSAndroid Build Coastguard Worker 
101*c8dee2aaSAndroid Build Coastguard Worker     fTimeBase = std::chrono::steady_clock::now();
102*c8dee2aaSAndroid Build Coastguard Worker }
103*c8dee2aaSAndroid Build Coastguard Worker 
setEnabled(bool enabled)104*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::setEnabled(bool enabled) {
105*c8dee2aaSAndroid Build Coastguard Worker     if (enabled != fEnabled) {
106*c8dee2aaSAndroid Build Coastguard Worker         this->toggleEnabled();
107*c8dee2aaSAndroid Build Coastguard Worker     }
108*c8dee2aaSAndroid Build Coastguard Worker }
109*c8dee2aaSAndroid Build Coastguard Worker 
currentSelection() const110*c8dee2aaSAndroid Build Coastguard Worker std::tuple<size_t, size_t> TextEditor::currentSelection() const {
111*c8dee2aaSAndroid Build Coastguard Worker     // Selection can be inverted.
112*c8dee2aaSAndroid Build Coastguard Worker     return std::make_tuple(std::min(std::get<0>(fSelection), std::get<1>(fSelection)),
113*c8dee2aaSAndroid Build Coastguard Worker                            std::max(std::get<0>(fSelection), std::get<1>(fSelection)));
114*c8dee2aaSAndroid Build Coastguard Worker }
115*c8dee2aaSAndroid Build Coastguard Worker 
closestGlyph(const SkPoint & pt) const116*c8dee2aaSAndroid Build Coastguard Worker size_t TextEditor::closestGlyph(const SkPoint& pt) const {
117*c8dee2aaSAndroid Build Coastguard Worker     float  min_distance = std::numeric_limits<float>::max();
118*c8dee2aaSAndroid Build Coastguard Worker     size_t min_index    = 0;
119*c8dee2aaSAndroid Build Coastguard Worker 
120*c8dee2aaSAndroid Build Coastguard Worker     for (size_t i = 0; i < fGlyphData.size(); ++i) {
121*c8dee2aaSAndroid Build Coastguard Worker         const auto dist = (fGlyphData[i].fDevBounds.center() - pt).length();
122*c8dee2aaSAndroid Build Coastguard Worker         if (dist < min_distance) {
123*c8dee2aaSAndroid Build Coastguard Worker             min_distance = dist;
124*c8dee2aaSAndroid Build Coastguard Worker             min_index = i;
125*c8dee2aaSAndroid Build Coastguard Worker         }
126*c8dee2aaSAndroid Build Coastguard Worker     }
127*c8dee2aaSAndroid Build Coastguard Worker 
128*c8dee2aaSAndroid Build Coastguard Worker     return min_index;
129*c8dee2aaSAndroid Build Coastguard Worker }
130*c8dee2aaSAndroid Build Coastguard Worker 
drawCursor(SkCanvas * canvas,const TextInfo & tinfo) const131*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::drawCursor(SkCanvas* canvas, const TextInfo& tinfo) const {
132*c8dee2aaSAndroid Build Coastguard Worker     constexpr double kCursorHz = 2;
133*c8dee2aaSAndroid Build Coastguard Worker     const auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
134*c8dee2aaSAndroid Build Coastguard Worker                             std::chrono::steady_clock::now() - fTimeBase).count();
135*c8dee2aaSAndroid Build Coastguard Worker     const long cycle = static_cast<long>(static_cast<double>(now_ms) * 0.001 * kCursorHz);
136*c8dee2aaSAndroid Build Coastguard Worker     if (cycle & 1) {
137*c8dee2aaSAndroid Build Coastguard Worker         // blink
138*c8dee2aaSAndroid Build Coastguard Worker         return;
139*c8dee2aaSAndroid Build Coastguard Worker     }
140*c8dee2aaSAndroid Build Coastguard Worker 
141*c8dee2aaSAndroid Build Coastguard Worker     auto txt_prop  = fTextProp->get();
142*c8dee2aaSAndroid Build Coastguard Worker 
143*c8dee2aaSAndroid Build Coastguard Worker     const auto glyph_index = [&]() -> size_t {
144*c8dee2aaSAndroid Build Coastguard Worker         if (!fCursorIndex) {
145*c8dee2aaSAndroid Build Coastguard Worker             return 0;
146*c8dee2aaSAndroid Build Coastguard Worker         }
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker         const auto prev_index = prev_utf8(txt_prop.fText, fCursorIndex);
149*c8dee2aaSAndroid Build Coastguard Worker         for (size_t i = 0; i < tinfo.fGlyphs.size(); ++i) {
150*c8dee2aaSAndroid Build Coastguard Worker             if (tinfo.fGlyphs[i].fCluster >= prev_index) {
151*c8dee2aaSAndroid Build Coastguard Worker                 return i;
152*c8dee2aaSAndroid Build Coastguard Worker             }
153*c8dee2aaSAndroid Build Coastguard Worker         }
154*c8dee2aaSAndroid Build Coastguard Worker 
155*c8dee2aaSAndroid Build Coastguard Worker         return tinfo.fGlyphs.size() - 1;
156*c8dee2aaSAndroid Build Coastguard Worker     }();
157*c8dee2aaSAndroid Build Coastguard Worker 
158*c8dee2aaSAndroid Build Coastguard Worker     // Cursor index mapping:
159*c8dee2aaSAndroid Build Coastguard Worker     //   0 -> before the first char
160*c8dee2aaSAndroid Build Coastguard Worker     //   1 -> after the first char
161*c8dee2aaSAndroid Build Coastguard Worker     //   2 -> after the second char
162*c8dee2aaSAndroid Build Coastguard Worker     //   ...
163*c8dee2aaSAndroid Build Coastguard Worker     // The cursor is bottom-aligned to the baseline (y = 0), and horizontally centered to the right
164*c8dee2aaSAndroid Build Coastguard Worker     // of the glyph advance.
165*c8dee2aaSAndroid Build Coastguard Worker     const auto cscale = txt_prop.fTextSize * tinfo.fScale,
166*c8dee2aaSAndroid Build Coastguard Worker                 cxpos = (fCursorIndex ? tinfo.fGlyphs[glyph_index].fAdvance : 0)
167*c8dee2aaSAndroid Build Coastguard Worker                          - fCursorBounds.width() * cscale * 0.5f,
168*c8dee2aaSAndroid Build Coastguard Worker                 cypos = - fCursorBounds.height() * cscale;
169*c8dee2aaSAndroid Build Coastguard Worker     const auto cpath  = fCursorPath.makeTransform(SkMatrix::Translate(cxpos, cypos) *
170*c8dee2aaSAndroid Build Coastguard Worker                                                   SkMatrix::Scale(cscale, cscale));
171*c8dee2aaSAndroid Build Coastguard Worker 
172*c8dee2aaSAndroid Build Coastguard Worker     // We stroke the cursor twice, with different colors, to ensure reasonable contrast
173*c8dee2aaSAndroid Build Coastguard Worker     // regardless of background.
174*c8dee2aaSAndroid Build Coastguard Worker     // The default inner stroke width is .5px for a font size of 10, and scales proportionally.
175*c8dee2aaSAndroid Build Coastguard Worker     // The outer stroke width is slightly larger.
176*c8dee2aaSAndroid Build Coastguard Worker     const auto inner_width = cscale * fCursorWeight * 0.05f,
177*c8dee2aaSAndroid Build Coastguard Worker                outer_width = inner_width * 3 / 2;
178*c8dee2aaSAndroid Build Coastguard Worker 
179*c8dee2aaSAndroid Build Coastguard Worker     SkPaint p;
180*c8dee2aaSAndroid Build Coastguard Worker     p.setAntiAlias(true);
181*c8dee2aaSAndroid Build Coastguard Worker     p.setStyle(SkPaint::kStroke_Style);
182*c8dee2aaSAndroid Build Coastguard Worker     p.setStrokeCap(SkPaint::kRound_Cap);
183*c8dee2aaSAndroid Build Coastguard Worker 
184*c8dee2aaSAndroid Build Coastguard Worker     SkAutoCanvasRestore acr(canvas, true);
185*c8dee2aaSAndroid Build Coastguard Worker     canvas->concat(tinfo.fGlyphs[glyph_index].fMatrix);
186*c8dee2aaSAndroid Build Coastguard Worker 
187*c8dee2aaSAndroid Build Coastguard Worker     p.setColor(SK_ColorWHITE);
188*c8dee2aaSAndroid Build Coastguard Worker     p.setStrokeWidth(outer_width);
189*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawPath(cpath, p);
190*c8dee2aaSAndroid Build Coastguard Worker     p.setColor(SK_ColorBLACK);
191*c8dee2aaSAndroid Build Coastguard Worker     p.setStrokeWidth(inner_width);
192*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawPath(cpath, p);
193*c8dee2aaSAndroid Build Coastguard Worker }
194*c8dee2aaSAndroid Build Coastguard Worker 
updateDeps(const SkString & txt)195*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::updateDeps(const SkString& txt) {
196*c8dee2aaSAndroid Build Coastguard Worker     for (const auto& dep : fDependentProps) {
197*c8dee2aaSAndroid Build Coastguard Worker         auto txt_prop = dep->get();
198*c8dee2aaSAndroid Build Coastguard Worker         txt_prop.fText = txt;
199*c8dee2aaSAndroid Build Coastguard Worker         dep->set(txt_prop);
200*c8dee2aaSAndroid Build Coastguard Worker     }
201*c8dee2aaSAndroid Build Coastguard Worker }
202*c8dee2aaSAndroid Build Coastguard Worker 
insertChar(SkUnichar c)203*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::insertChar(SkUnichar c) {
204*c8dee2aaSAndroid Build Coastguard Worker     auto txt = fTextProp->get();
205*c8dee2aaSAndroid Build Coastguard Worker     const auto initial_size = txt.fText.size();
206*c8dee2aaSAndroid Build Coastguard Worker 
207*c8dee2aaSAndroid Build Coastguard Worker     txt.fText.insertUnichar(fCursorIndex, c);
208*c8dee2aaSAndroid Build Coastguard Worker     fCursorIndex += txt.fText.size() - initial_size;
209*c8dee2aaSAndroid Build Coastguard Worker 
210*c8dee2aaSAndroid Build Coastguard Worker     fTextProp->set(txt);
211*c8dee2aaSAndroid Build Coastguard Worker     this->updateDeps(txt.fText);
212*c8dee2aaSAndroid Build Coastguard Worker }
213*c8dee2aaSAndroid Build Coastguard Worker 
deleteChars(size_t offset,size_t count)214*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::deleteChars(size_t offset, size_t count) {
215*c8dee2aaSAndroid Build Coastguard Worker     auto txt = fTextProp->get();
216*c8dee2aaSAndroid Build Coastguard Worker 
217*c8dee2aaSAndroid Build Coastguard Worker     txt.fText.remove(offset, count);
218*c8dee2aaSAndroid Build Coastguard Worker     fTextProp->set(txt);
219*c8dee2aaSAndroid Build Coastguard Worker     this->updateDeps(txt.fText);
220*c8dee2aaSAndroid Build Coastguard Worker 
221*c8dee2aaSAndroid Build Coastguard Worker     fCursorIndex = offset;
222*c8dee2aaSAndroid Build Coastguard Worker }
223*c8dee2aaSAndroid Build Coastguard Worker 
deleteSelection()224*c8dee2aaSAndroid Build Coastguard Worker bool TextEditor::deleteSelection() {
225*c8dee2aaSAndroid Build Coastguard Worker     const auto [glyph_sel_start, glyph_sel_end] = this->currentSelection();
226*c8dee2aaSAndroid Build Coastguard Worker     if (glyph_sel_start == glyph_sel_end) {
227*c8dee2aaSAndroid Build Coastguard Worker         return false;
228*c8dee2aaSAndroid Build Coastguard Worker     }
229*c8dee2aaSAndroid Build Coastguard Worker 
230*c8dee2aaSAndroid Build Coastguard Worker     const auto utf8_sel_start = fGlyphData[glyph_sel_start].fCluster,
231*c8dee2aaSAndroid Build Coastguard Worker                utf8_sel_end   = fGlyphData[glyph_sel_end  ].fCluster;
232*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(utf8_sel_start < utf8_sel_end);
233*c8dee2aaSAndroid Build Coastguard Worker 
234*c8dee2aaSAndroid Build Coastguard Worker     this->deleteChars(utf8_sel_start, utf8_sel_end - utf8_sel_start);
235*c8dee2aaSAndroid Build Coastguard Worker 
236*c8dee2aaSAndroid Build Coastguard Worker     fSelection = {0,0};
237*c8dee2aaSAndroid Build Coastguard Worker 
238*c8dee2aaSAndroid Build Coastguard Worker     return true;
239*c8dee2aaSAndroid Build Coastguard Worker }
240*c8dee2aaSAndroid Build Coastguard Worker 
onDecorate(SkCanvas * canvas,const TextInfo & tinfo)241*c8dee2aaSAndroid Build Coastguard Worker void TextEditor::onDecorate(SkCanvas* canvas, const TextInfo& tinfo) {
242*c8dee2aaSAndroid Build Coastguard Worker     const auto [sel_start, sel_end] = this->currentSelection();
243*c8dee2aaSAndroid Build Coastguard Worker 
244*c8dee2aaSAndroid Build Coastguard Worker     fGlyphData.clear();
245*c8dee2aaSAndroid Build Coastguard Worker 
246*c8dee2aaSAndroid Build Coastguard Worker     for (size_t i = 0; i < tinfo.fGlyphs.size(); ++i) {
247*c8dee2aaSAndroid Build Coastguard Worker         const auto& ginfo = tinfo.fGlyphs[i];
248*c8dee2aaSAndroid Build Coastguard Worker 
249*c8dee2aaSAndroid Build Coastguard Worker         SkAutoCanvasRestore acr(canvas, true);
250*c8dee2aaSAndroid Build Coastguard Worker         canvas->concat(ginfo.fMatrix);
251*c8dee2aaSAndroid Build Coastguard Worker 
252*c8dee2aaSAndroid Build Coastguard Worker         // Stash some glyph info, for later use.
253*c8dee2aaSAndroid Build Coastguard Worker         fGlyphData.push_back({
254*c8dee2aaSAndroid Build Coastguard Worker             canvas->getLocalToDevice().asM33().mapRect(ginfo.fBounds),
255*c8dee2aaSAndroid Build Coastguard Worker             ginfo.fCluster
256*c8dee2aaSAndroid Build Coastguard Worker         });
257*c8dee2aaSAndroid Build Coastguard Worker 
258*c8dee2aaSAndroid Build Coastguard Worker         if (i < sel_start || i >= sel_end) {
259*c8dee2aaSAndroid Build Coastguard Worker             continue;
260*c8dee2aaSAndroid Build Coastguard Worker         }
261*c8dee2aaSAndroid Build Coastguard Worker 
262*c8dee2aaSAndroid Build Coastguard Worker         static constexpr SkColor4f kSelectionColor{0, 0, 1, 0.4f};
263*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawRect(ginfo.fBounds, SkPaint(kSelectionColor));
264*c8dee2aaSAndroid Build Coastguard Worker     }
265*c8dee2aaSAndroid Build Coastguard Worker 
266*c8dee2aaSAndroid Build Coastguard Worker     // Only draw the cursor when there's no active selection.
267*c8dee2aaSAndroid Build Coastguard Worker     if (sel_start == sel_end) {
268*c8dee2aaSAndroid Build Coastguard Worker         this->drawCursor(canvas, tinfo);
269*c8dee2aaSAndroid Build Coastguard Worker     }
270*c8dee2aaSAndroid Build Coastguard Worker }
271*c8dee2aaSAndroid Build Coastguard Worker 
onMouseInput(SkScalar x,SkScalar y,skui::InputState state,skui::ModifierKey)272*c8dee2aaSAndroid Build Coastguard Worker bool TextEditor::onMouseInput(SkScalar x, SkScalar y, skui::InputState state,
273*c8dee2aaSAndroid Build Coastguard Worker                                      skui::ModifierKey) {
274*c8dee2aaSAndroid Build Coastguard Worker     if (!fEnabled || fGlyphData.empty()) {
275*c8dee2aaSAndroid Build Coastguard Worker         return false;
276*c8dee2aaSAndroid Build Coastguard Worker     }
277*c8dee2aaSAndroid Build Coastguard Worker 
278*c8dee2aaSAndroid Build Coastguard Worker     switch (state) {
279*c8dee2aaSAndroid Build Coastguard Worker     case skui::InputState::kDown: {
280*c8dee2aaSAndroid Build Coastguard Worker         fMouseDown = true;
281*c8dee2aaSAndroid Build Coastguard Worker 
282*c8dee2aaSAndroid Build Coastguard Worker         const auto closest = this->closestGlyph({x, y});
283*c8dee2aaSAndroid Build Coastguard Worker         fSelection = {closest, closest};
284*c8dee2aaSAndroid Build Coastguard Worker     }   break;
285*c8dee2aaSAndroid Build Coastguard Worker     case skui::InputState::kUp:
286*c8dee2aaSAndroid Build Coastguard Worker         fMouseDown = false;
287*c8dee2aaSAndroid Build Coastguard Worker         break;
288*c8dee2aaSAndroid Build Coastguard Worker     case skui::InputState::kMove:
289*c8dee2aaSAndroid Build Coastguard Worker         if (fMouseDown) {
290*c8dee2aaSAndroid Build Coastguard Worker             const auto closest = this->closestGlyph({x, y});
291*c8dee2aaSAndroid Build Coastguard Worker             std::get<1>(fSelection) = closest < std::get<0>(fSelection)
292*c8dee2aaSAndroid Build Coastguard Worker                                             ? closest
293*c8dee2aaSAndroid Build Coastguard Worker                                             : closest + 1;
294*c8dee2aaSAndroid Build Coastguard Worker         }
295*c8dee2aaSAndroid Build Coastguard Worker         break;
296*c8dee2aaSAndroid Build Coastguard Worker     default:
297*c8dee2aaSAndroid Build Coastguard Worker         break;
298*c8dee2aaSAndroid Build Coastguard Worker     }
299*c8dee2aaSAndroid Build Coastguard Worker 
300*c8dee2aaSAndroid Build Coastguard Worker     return true;
301*c8dee2aaSAndroid Build Coastguard Worker }
302*c8dee2aaSAndroid Build Coastguard Worker 
onCharInput(SkUnichar c)303*c8dee2aaSAndroid Build Coastguard Worker bool TextEditor::onCharInput(SkUnichar c) {
304*c8dee2aaSAndroid Build Coastguard Worker     if (!fEnabled || fGlyphData.empty()) {
305*c8dee2aaSAndroid Build Coastguard Worker         return false;
306*c8dee2aaSAndroid Build Coastguard Worker     }
307*c8dee2aaSAndroid Build Coastguard Worker 
308*c8dee2aaSAndroid Build Coastguard Worker     const auto& txt_str = fTextProp->get().fText;
309*c8dee2aaSAndroid Build Coastguard Worker 
310*c8dee2aaSAndroid Build Coastguard Worker     // Natural editor bindings are currently intercepted by Viewer, so we use these instead.
311*c8dee2aaSAndroid Build Coastguard Worker     switch (c) {
312*c8dee2aaSAndroid Build Coastguard Worker     case '|':     // commit changes and exit editing mode
313*c8dee2aaSAndroid Build Coastguard Worker         this->toggleEnabled();
314*c8dee2aaSAndroid Build Coastguard Worker         break;
315*c8dee2aaSAndroid Build Coastguard Worker     case ']': {   // move right
316*c8dee2aaSAndroid Build Coastguard Worker         if (fCursorIndex < txt_str.size()) {
317*c8dee2aaSAndroid Build Coastguard Worker             fCursorIndex = next_utf8(txt_str, fCursorIndex);
318*c8dee2aaSAndroid Build Coastguard Worker         }
319*c8dee2aaSAndroid Build Coastguard Worker     } break;
320*c8dee2aaSAndroid Build Coastguard Worker     case '[':     // move left
321*c8dee2aaSAndroid Build Coastguard Worker         if (fCursorIndex > 0) {
322*c8dee2aaSAndroid Build Coastguard Worker             fCursorIndex = prev_utf8(txt_str, fCursorIndex);
323*c8dee2aaSAndroid Build Coastguard Worker         }
324*c8dee2aaSAndroid Build Coastguard Worker         break;
325*c8dee2aaSAndroid Build Coastguard Worker     case '\\': {  // delete
326*c8dee2aaSAndroid Build Coastguard Worker         if (!this->deleteSelection() && fCursorIndex > 0) {
327*c8dee2aaSAndroid Build Coastguard Worker             // Delete preceding char.
328*c8dee2aaSAndroid Build Coastguard Worker             const auto del_index = prev_utf8(txt_str, fCursorIndex),
329*c8dee2aaSAndroid Build Coastguard Worker                        del_count = fCursorIndex - del_index;
330*c8dee2aaSAndroid Build Coastguard Worker 
331*c8dee2aaSAndroid Build Coastguard Worker             this->deleteChars(del_index, del_count);
332*c8dee2aaSAndroid Build Coastguard Worker         }
333*c8dee2aaSAndroid Build Coastguard Worker     }   break;
334*c8dee2aaSAndroid Build Coastguard Worker     default:
335*c8dee2aaSAndroid Build Coastguard Worker         // Delete any selection on insert.
336*c8dee2aaSAndroid Build Coastguard Worker         this->deleteSelection();
337*c8dee2aaSAndroid Build Coastguard Worker         this->insertChar(c);
338*c8dee2aaSAndroid Build Coastguard Worker         break;
339*c8dee2aaSAndroid Build Coastguard Worker     }
340*c8dee2aaSAndroid Build Coastguard Worker 
341*c8dee2aaSAndroid Build Coastguard Worker     // Reset the cursor blink timer on input.
342*c8dee2aaSAndroid Build Coastguard Worker     fTimeBase = std::chrono::steady_clock::now();
343*c8dee2aaSAndroid Build Coastguard Worker 
344*c8dee2aaSAndroid Build Coastguard Worker     return true;
345*c8dee2aaSAndroid Build Coastguard Worker }
346*c8dee2aaSAndroid Build Coastguard Worker 
347*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skottie_utils
348