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