blob: 42382879d208bef9232e276c6ed0b517debc61bf [file] [log] [blame]
// 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