33 constexpr float kWidth = 0.2f,
39 p.moveTo(kWidth/2, 0);
40 p.lineTo(kWidth/2, kHeight);
41 p.moveTo(0 , kHeight);
42 p.lineTo(kWidth , kHeight);
50 const char* utf8_ptr = str.
c_str() + index;
57 return utf8_ptr - str.
c_str();
66 const char* utf8_ptr = str.
c_str() + index -
i;
79 std::unique_ptr<skottie::TextPropertyHandle>&&
prop,
80 std::vector<std::unique_ptr<skottie::TextPropertyHandle>>&& deps)
82 , fDependentProps(
std::move(deps))
83 , fCursorPath(make_cursor_path())
84 , fCursorBounds(fCursorPath.computeTightBounds())
92 auto txt = fTextProp->get();
98 fCursorIndex =
txt.fText.size();
101 fTimeBase = std::chrono::steady_clock::now();
105 if (enabled != fEnabled) {
110std::tuple<size_t, size_t> TextEditor::currentSelection()
const {
116size_t TextEditor::closestGlyph(
const SkPoint& pt)
const {
118 size_t min_index = 0;
120 for (
size_t i = 0;
i < fGlyphData.size(); ++
i) {
121 const auto dist = (fGlyphData[
i].fDevBounds.center() - pt).
length();
122 if (dist < min_distance) {
131void TextEditor::drawCursor(
SkCanvas* canvas,
const TextInfo& tinfo)
const {
132 constexpr double kCursorHz = 2;
133 const auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
134 std::chrono::steady_clock::now() - fTimeBase).count();
135 const long cycle =
static_cast<long>(
static_cast<double>(
now_ms) * 0.001 * kCursorHz);
141 auto txt_prop = fTextProp->get();
143 const auto glyph_index = [&]() ->
size_t {
148 const auto prev_index =
prev_utf8(txt_prop.fText, fCursorIndex);
149 for (
size_t i = 0;
i < tinfo.fGlyphs.size(); ++
i) {
150 if (tinfo.fGlyphs[
i].fCluster >= prev_index) {
155 return tinfo.fGlyphs.size() - 1;
165 const auto cscale = txt_prop.fTextSize * tinfo.fScale,
166 cxpos = (fCursorIndex ? tinfo.fGlyphs[glyph_index].fAdvance : 0)
167 - fCursorBounds.
width() * cscale * 0.5f,
168 cypos = - fCursorBounds.
height() * cscale;
176 const auto inner_width = cscale * fCursorWeight * 0.05f,
177 outer_width = inner_width * 3 / 2;
180 p.setAntiAlias(
true);
185 canvas->
concat(tinfo.fGlyphs[glyph_index].fMatrix);
188 p.setStrokeWidth(outer_width);
191 p.setStrokeWidth(inner_width);
196 for (
const auto& dep : fDependentProps) {
197 auto txt_prop = dep->get();
198 txt_prop.fText =
txt;
203void TextEditor::insertChar(
SkUnichar c) {
204 auto txt = fTextProp->get();
205 const auto initial_size =
txt.fText.size();
207 txt.fText.insertUnichar(fCursorIndex, c);
208 fCursorIndex +=
txt.fText.size() - initial_size;
211 this->updateDeps(
txt.fText);
214void TextEditor::deleteChars(
size_t offset,
size_t count) {
215 auto txt = fTextProp->get();
219 this->updateDeps(
txt.fText);
224bool TextEditor::deleteSelection() {
225 const auto [glyph_sel_start, glyph_sel_end] = this->currentSelection();
226 if (glyph_sel_start == glyph_sel_end) {
230 const auto utf8_sel_start = fGlyphData[glyph_sel_start].fCluster,
231 utf8_sel_end = fGlyphData[glyph_sel_end ].fCluster;
232 SkASSERT(utf8_sel_start < utf8_sel_end);
234 this->deleteChars(utf8_sel_start, utf8_sel_end - utf8_sel_start);
242 const auto [sel_start, sel_end] = this->currentSelection();
246 for (
size_t i = 0;
i < tinfo.
fGlyphs.size(); ++
i) {
247 const auto& ginfo = tinfo.
fGlyphs[
i];
250 canvas->
concat(ginfo.fMatrix);
253 fGlyphData.push_back({
258 if (i < sel_start || i >= sel_end) {
262 static constexpr SkColor4f kSelectionColor{0, 0, 1, 0.4f};
267 if (sel_start == sel_end) {
268 this->drawCursor(canvas, tinfo);
274 if (!fEnabled || fGlyphData.empty()) {
282 const auto closest = this->closestGlyph({
x,
y});
283 fSelection = {closest, closest};
290 const auto closest = this->closestGlyph({
x,
y});
304 if (!fEnabled || fGlyphData.empty()) {
308 const auto& txt_str = fTextProp->get().fText;
316 if (fCursorIndex < txt_str.size()) {
317 fCursorIndex =
next_utf8(txt_str, fCursorIndex);
321 if (fCursorIndex > 0) {
322 fCursorIndex =
prev_utf8(txt_str, fCursorIndex);
326 if (!this->deleteSelection() && fCursorIndex > 0) {
328 const auto del_index =
prev_utf8(txt_str, fCursorIndex),
329 del_count = fCursorIndex - del_index;
331 this->deleteChars(del_index, del_count);
336 this->deleteSelection();
342 fTimeBase = std::chrono::steady_clock::now();
constexpr SkColor SK_ColorBLACK
constexpr SkColor SK_ColorWHITE
sk_sp< T > sk_ref_sp(T *obj)
void drawRect(const SkRect &rect, const SkPaint &paint)
SkM44 getLocalToDevice() const
void drawPath(const SkPath &path, const SkPaint &paint)
void concat(const SkMatrix &matrix)
static SkMatrix Scale(SkScalar sx, SkScalar sy)
static SkMatrix Translate(SkScalar dx, SkScalar dy)
@ kStroke_Style
set to stroke geometry
SkPath makeTransform(const SkMatrix &m, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
const char * c_str() const
bool onCharInput(SkUnichar c)
TextEditor(std::unique_ptr< skottie::TextPropertyHandle > &&, std::vector< std::unique_ptr< skottie::TextPropertyHandle > > &&)
bool onMouseInput(SkScalar x, SkScalar y, skui::InputState state, skui::ModifierKey)
void onDecorate(SkCanvas *, const TextInfo &) override
static const char * prev_utf8(const char *p, const char *begin)
static const char * next_utf8(const char *p, const char *end)
static float max(float r, float g, float b)
static float min(float r, float g, float b)
constexpr unsigned kMaxBytesInUTF8Sequence
SK_SPI SkUnichar NextUTF8(const char **ptr, const char *end)
const myers::Point & get< 1 >(const myers::Segment &s)
const myers::Point & get< 0 >(const myers::Segment &s)
static SkScalar prop(SkScalar radius, SkScalar newSize, SkScalar oldSize)
constexpr float height() const
constexpr float width() const
SkSpan< const GlyphInfo > fGlyphs