c5c9dd86b0
Change-Id: I1152f83af724634f06e1a767ef39bef2afe70b33 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/430656 Reviewed-by: Julia Lavrova <jlavrova@google.com> Commit-Queue: Julia Lavrova <jlavrova@google.com>
361 lines
13 KiB
C++
361 lines
13 KiB
C++
// Copyright 2021 Google LLC.
|
|
#include "experimental/sktext/editor/Editor.h"
|
|
#include "experimental/sktext/src/Paint.h"
|
|
|
|
namespace skia {
|
|
namespace text {
|
|
|
|
const TextDirection TEXT_DIRECTION = TextDirection::kLtr;
|
|
const TextAlign TEXT_ALIGN = TextAlign::kLeft;
|
|
const SkColor DEFAULT_FOREGROUND = SK_ColorBLACK;
|
|
const SkScalar DEFAULT_CURSOR_WIDTH = 2;
|
|
const SkColor DEFAULT_CURSOR_COLOR = SK_ColorGRAY;
|
|
const SkColor DEFAULT_SELECTION_COLOR = SK_ColorCYAN;
|
|
|
|
std::unique_ptr<Cursor> Cursor::Make() { return std::make_unique<Cursor>(); }
|
|
|
|
Cursor::Cursor() {
|
|
fLinePaint.setColor(SK_ColorGRAY);
|
|
fLinePaint.setAntiAlias(true);
|
|
|
|
fRectPaint.setColor(DEFAULT_CURSOR_COLOR);
|
|
fRectPaint.setStyle(SkPaint::kStroke_Style);
|
|
fRectPaint.setStrokeWidth(2);
|
|
fRectPaint.setAntiAlias(true);
|
|
|
|
fXY = SkPoint::Make(0, 0);
|
|
fSize = SkSize::Make(0, 0);
|
|
fBlink = true;
|
|
}
|
|
|
|
void Cursor::paint(SkCanvas* canvas, SkPoint xy) {
|
|
|
|
if (fBlink) {
|
|
canvas->drawRect(SkRect::MakeXYWH(fXY.fX + xy.fX, fXY.fY + xy.fY, DEFAULT_CURSOR_WIDTH, fSize.fHeight),
|
|
fRectPaint);
|
|
} else {
|
|
//canvas->drawLine(fXY + xy, fXY + xy + SkPoint::Make(1, fSize.fHeight), fLinePaint);
|
|
}
|
|
}
|
|
|
|
void Mouse::down() {
|
|
fMouseDown = true;
|
|
}
|
|
|
|
void Mouse::up() {
|
|
fMouseDown = false;
|
|
}
|
|
|
|
bool Mouse::isDoubleClick(SkPoint touch) {
|
|
if ((touch - fLastTouchPoint).length() > MAX_DBL_TAP_DISTANCE) {
|
|
fLastTouchPoint = touch;
|
|
fLastTouchTime = SkTime::GetMSecs();
|
|
return false;
|
|
}
|
|
double now = SkTime::GetMSecs();
|
|
if (now - fLastTouchTime > MAX_DBL_TAP_INTERVAL) {
|
|
fLastTouchPoint = touch;
|
|
fLastTouchTime = SkTime::GetMSecs();
|
|
return false;
|
|
}
|
|
|
|
clearTouchInfo();
|
|
return true;
|
|
}
|
|
|
|
void Selection::select(TextRange range, SkRect rect) {
|
|
fGlyphBoxes.clear();
|
|
fTextRanges.clear();
|
|
|
|
fGlyphBoxes.emplace_back(rect);
|
|
fTextRanges.emplace_back(range);
|
|
}
|
|
|
|
void Selection::paint(SkCanvas* canvas, SkPoint xy) {
|
|
for (auto& box : fGlyphBoxes) {
|
|
canvas->drawRect(
|
|
SkRect::MakeXYWH(box.fLeft + xy.fX, box.fTop + xy.fY, box.width(), box.height()),
|
|
fPaint);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<Editor> Editor::Make(std::u16string text, SkSize size, SkSpan<Block> fontBlocks) {
|
|
return std::unique_ptr<Editor>(new Editor(text, size, fontBlocks));
|
|
}
|
|
|
|
Editor::Editor(std::u16string text, SkSize size, SkSpan<Block> fontBlocks) {
|
|
fParent = nullptr;
|
|
fText = std::move(text);
|
|
fCursor = Cursor::Make();
|
|
fMouse = std::make_unique<Mouse>();
|
|
fSelection = std::make_unique<Selection>(DEFAULT_SELECTION_COLOR);
|
|
fUnicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
|
|
fShapedText = fUnicodeText->shape(fontBlocks, TEXT_DIRECTION);
|
|
fWrappedText = fShapedText->wrap(size.width(), size.height(), fUnicodeText->getUnicode());
|
|
fFormattedText = fWrappedText->format(TEXT_ALIGN, TEXT_DIRECTION);
|
|
|
|
auto endOfText = fFormattedText->indexToAdjustedGraphemePosition(fText.size());
|
|
auto rect = std::get<3>(endOfText);
|
|
fCursor->place(SkPoint::Make(rect.fLeft, rect.fTop),
|
|
SkSize::Make(std::max(DEFAULT_CURSOR_WIDTH, rect.width()), rect.height()));
|
|
}
|
|
|
|
void Editor::updateText(std::u16string text) {
|
|
// TODO: Update text styles
|
|
sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40);
|
|
Block block(fText.size(), fontChain);
|
|
auto fontBlocks = SkSpan<Block>(&block, 1);
|
|
|
|
// TODO: update all objects rather than recreate them
|
|
fText = std::move(text);
|
|
fUnicodeText = Text::parse(SkSpan<uint16_t>((uint16_t*)fText.data(), fText.size()));
|
|
fShapedText = fUnicodeText->shape(fontBlocks, TEXT_DIRECTION);
|
|
fWrappedText = fShapedText->wrap(fWidth, fHeight, fUnicodeText->getUnicode());
|
|
fFormattedText = fWrappedText->format(TEXT_ALIGN, TEXT_DIRECTION);
|
|
|
|
// Update the cursor
|
|
auto endOfText = fFormattedText->indexToAdjustedGraphemePosition(fText.size());
|
|
auto rect = std::get<3>(endOfText);
|
|
fCursor->place(SkPoint::Make(rect.fLeft, rect.fTop),
|
|
SkSize::Make(std::max(DEFAULT_CURSOR_WIDTH, rect.width()), rect.height()));
|
|
// TODO: Update the mouse
|
|
fMouse->clearTouchInfo();
|
|
|
|
// TODO: Update the text selection
|
|
fSelection->clear();
|
|
}
|
|
|
|
bool Editor::moveCursor(skui::Key key) {
|
|
auto cursorPosition = fCursor->getCenterPosition();
|
|
auto textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
|
|
|
|
if (key == skui::Key::kLeft) {
|
|
if (textIndex == 0) {
|
|
return false;
|
|
} else {
|
|
--textIndex;
|
|
}
|
|
} else if (key == skui::Key::kRight) {
|
|
if (textIndex >= fText.size()) {
|
|
return false;
|
|
} else {
|
|
++textIndex;
|
|
}
|
|
} else if (key == skui::Key::kHome) {
|
|
textIndex = 0;
|
|
} else if (key == skui::Key::kEnd) {
|
|
textIndex = fText.size();
|
|
} else if (key == skui::Key::kUp) {
|
|
auto currentPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
|
|
auto line = std::get<0>(currentPosition);
|
|
auto lineIndex = fFormattedText->lineIndex(line);
|
|
if (lineIndex == 0) {
|
|
return false;
|
|
}
|
|
cursorPosition.offset(0, - line->getMetrics().height());
|
|
textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
|
|
} else if (key == skui::Key::kDown) {
|
|
auto currentPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
|
|
auto line = std::get<0>(currentPosition);
|
|
auto lineIndex = fFormattedText->lineIndex(line);
|
|
if (lineIndex == fFormattedText->countLines()) {
|
|
return false;
|
|
}
|
|
cursorPosition.offset(0, line->getMetrics().height());
|
|
textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
|
|
}
|
|
auto nextPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
|
|
auto rect = std::get<3>(nextPosition);
|
|
fCursor->place(SkPoint::Make(rect.fLeft, rect.fTop), SkSize::Make(rect.width(), rect.height()));
|
|
this->invalidate();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Editor::onPaint(SkSurface* surface) {
|
|
SkCanvas* canvas = surface->getCanvas();
|
|
SkAutoCanvasRestore acr(canvas, true);
|
|
canvas->clipRect({0, 0, (float)fWidth, (float)fHeight});
|
|
canvas->drawColor(SK_ColorWHITE);
|
|
this->paint(canvas, SkPoint::Make(0, 0));
|
|
}
|
|
|
|
void Editor::onResize(int width, int height) {
|
|
if (SkISize{fWidth, fHeight} != SkISize{width, height}) {
|
|
fHeight = height;
|
|
if (width != fWidth) {
|
|
fWidth = width;
|
|
}
|
|
this->invalidate();
|
|
}
|
|
}
|
|
|
|
bool Editor::onChar(SkUnichar c, skui::ModifierKey modi) {
|
|
using sknonstd::Any;
|
|
|
|
fSelection->clear();
|
|
|
|
modi &= ~skui::ModifierKey::kFirstPress;
|
|
if (!Any(modi & (skui::ModifierKey::kControl | skui::ModifierKey::kOption |
|
|
skui::ModifierKey::kCommand))) {
|
|
if (((unsigned)c < 0x7F && (unsigned)c >= 0x20) || c == '\n') {
|
|
insertCodepoint(c);
|
|
return true;
|
|
}
|
|
}
|
|
static constexpr skui::ModifierKey kCommandOrControl =
|
|
skui::ModifierKey::kCommand | skui::ModifierKey::kControl;
|
|
if (Any(modi & kCommandOrControl) && !Any(modi & ~kCommandOrControl)) {
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Editor::deleteGrapheme(skui::Key key) {
|
|
auto cursorPosition = fCursor->getCenterPosition();
|
|
TextIndex startIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
|
|
TextIndex endIndex = startIndex;
|
|
if (key == skui::Key::kBack) {
|
|
--startIndex;
|
|
fShapedText->adjustLeft(&startIndex);
|
|
} else {
|
|
++endIndex;
|
|
fShapedText->adjustRight(&endIndex);
|
|
}
|
|
|
|
std::u16string text;
|
|
text.append(fText.substr(0, startIndex));
|
|
text.append(fText.substr(endIndex, std::u16string::npos));
|
|
updateText(text);
|
|
|
|
auto position = fFormattedText->indexToAdjustedGraphemePosition(startIndex);
|
|
SkRect cursorRect = std::get<3>(position);
|
|
fCursor->place(SkPoint::Make(cursorRect.fLeft, cursorRect.fTop), SkSize::Make(cursorRect.width(), cursorRect.height()));
|
|
|
|
this->invalidate();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Editor::insertCodepoint(SkUnichar unichar) {
|
|
auto cursorPosition = fCursor->getCenterPosition();
|
|
auto textIndex = fFormattedText->positionToAdjustedGraphemeIndex(cursorPosition);
|
|
|
|
std::u16string text;
|
|
text.append(fText.substr(0, textIndex));
|
|
text.append(1, (unsigned)unichar);
|
|
text.append(fText.substr(textIndex, std::u16string::npos));
|
|
updateText(text);
|
|
|
|
auto nextPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
|
|
SkRect cursorRect = std::get<3>(nextPosition);
|
|
fCursor->place(SkPoint::Make(cursorRect.fLeft, cursorRect.fTop), SkSize::Make(cursorRect.width(), cursorRect.height()));
|
|
|
|
this->invalidate();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Editor::onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers) {
|
|
|
|
fSelection->clear();
|
|
|
|
if (state != skui::InputState::kDown) {
|
|
return false;
|
|
}
|
|
using sknonstd::Any;
|
|
skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
|
|
skui::ModifierKey::kOption |
|
|
skui::ModifierKey::kCommand);
|
|
//bool shift = Any(modifiers & (skui::ModifierKey::kShift));
|
|
if (!Any(ctrlAltCmd)) {
|
|
// no modifiers
|
|
switch (key) {
|
|
case skui::Key::kLeft:
|
|
case skui::Key::kRight:
|
|
case skui::Key::kUp:
|
|
case skui::Key::kDown:
|
|
case skui::Key::kHome:
|
|
case skui::Key::kEnd:
|
|
this->moveCursor(key);
|
|
break;
|
|
case skui::Key::kDelete:
|
|
case skui::Key::kBack:
|
|
this->deleteGrapheme(key);
|
|
return true;
|
|
case skui::Key::kOK:
|
|
return this->onChar('\n', modifiers);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Editor::onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers) {
|
|
|
|
//bool shiftOrDrag = sknonstd::Any(modifiers & skui::ModifierKey::kShift) || (skui::InputState::kDown != state);
|
|
if (skui::InputState::kDown == state) {
|
|
SkRect cursorRect;
|
|
if (fMouse->isDoubleClick(SkPoint::Make(x, y))) {
|
|
auto textIndex = fFormattedText->positionToAdjustedGraphemeIndex(SkPoint::Make(x, y));
|
|
auto nextPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
|
|
cursorRect = std::get<3>(nextPosition);
|
|
fSelection->select(TextRange(textIndex, textIndex + 1), cursorRect);
|
|
cursorRect.fLeft = cursorRect.fRight - DEFAULT_CURSOR_WIDTH;
|
|
} else {
|
|
fMouse->down();
|
|
fSelection->clear();
|
|
auto textIndex = fFormattedText->positionToAdjustedGraphemeIndex(SkPoint::Make(x, y));
|
|
auto nextPosition = fFormattedText->indexToAdjustedGraphemePosition(textIndex);
|
|
cursorRect = std::get<3>(nextPosition);
|
|
}
|
|
|
|
fCursor->place(SkPoint::Make(cursorRect.fLeft, cursorRect.fTop), SkSize::Make(cursorRect.width(), cursorRect.height()));
|
|
this->invalidate();
|
|
return true;
|
|
}
|
|
fMouse->up();
|
|
return false;
|
|
}
|
|
|
|
void Editor::onBeginLine(TextRange, float baselineY) {}
|
|
void Editor::onEndLine(TextRange, float baselineY) {}
|
|
void Editor::onPlaceholder(TextRange, const SkRect& bounds) {}
|
|
void Editor::onGlyphRun(SkFont font,
|
|
TextRange textRange,
|
|
int glyphCount,
|
|
const uint16_t glyphs[],
|
|
const SkPoint positions[],
|
|
const SkPoint offsets[]) {
|
|
SkTextBlobBuilder builder;
|
|
const auto& blobBuffer = builder.allocRunPos(font, SkToInt(glyphCount));
|
|
sk_careful_memcpy(blobBuffer.glyphs, glyphs, glyphCount * sizeof(uint16_t));
|
|
sk_careful_memcpy(blobBuffer.points(), positions, glyphCount * sizeof(SkPoint));
|
|
SkPaint foreground;
|
|
foreground.setColor(DEFAULT_FOREGROUND);
|
|
fCanvas->drawTextBlob(builder.make(), fXY.fX, fXY.fY, foreground);
|
|
}
|
|
|
|
void Editor::paint(SkCanvas* canvas, SkPoint xy) {
|
|
fCanvas = canvas;
|
|
fXY = xy;
|
|
this->fFormattedText->visit(this);
|
|
|
|
fSelection->paint(canvas, xy);
|
|
fCursor->paint(canvas, xy);
|
|
}
|
|
|
|
std::unique_ptr<Editor> Editor::MakeDemo(SkScalar width) {
|
|
|
|
std::u16string text0 = u"In a hole in the ground there lived a hobbit. Not a nasty, dirty, "
|
|
"wet hole full of worms and oozy smells. This was a hobbit-hole and "
|
|
"that means good food, a warm hearth, and all the comforts of home.";
|
|
|
|
sk_sp<TrivialFontChain> fontChain = sk_make_sp<TrivialFontChain>("Roboto", 40);
|
|
Block block(text0.size(), fontChain);
|
|
return Editor::Make(text0, SkSize::Make(width, SK_ScalarInfinity), SkSpan<Block>(&block, 1));
|
|
}
|
|
} // namespace text
|
|
} // namespace skia
|