Experimental text editor: factor out Editor class.
Change-Id: Ie3ab5566b66f8255097001d015b39ca650cc0f20 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/210920 Commit-Queue: Hal Canary <halcanary@google.com> Commit-Queue: Ben Wagner <bungeman@google.com> Auto-Submit: Hal Canary <halcanary@google.com> Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
parent
378be8dcc0
commit
50d650d3ef
2
BUILD.gn
2
BUILD.gn
@ -2463,6 +2463,8 @@ if (skia_enable_tools) {
|
||||
is_shared_library = is_android
|
||||
sources = [
|
||||
"experimental/editor/editor_application.cpp",
|
||||
"experimental/editor/editor.cpp",
|
||||
"experimental/editor/editor.h",
|
||||
]
|
||||
deps = [
|
||||
":sk_app",
|
||||
|
97
experimental/editor/editor.cpp
Normal file
97
experimental/editor/editor.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#include "editor.h"
|
||||
|
||||
#include "include/core/SkExecutor.h"
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "modules/skshaper/include/SkShaper.h"
|
||||
|
||||
using namespace editor;
|
||||
|
||||
static sk_sp<const SkTextBlob> shape(const SkString& text, const SkFont& font,
|
||||
const SkShaper* shaper, float width, int* height) {
|
||||
SkASSERT(height);
|
||||
SkTextBlobBuilderRunHandler textBlobBuilder(text.c_str(), {0, 0});
|
||||
shaper->shape(text.c_str(), text.size(), font, true, width, &textBlobBuilder);
|
||||
float h = std::max(textBlobBuilder.endPoint().y(), font.getSpacing());
|
||||
*height = (int)ceilf(h);
|
||||
return textBlobBuilder.makeBlob();
|
||||
}
|
||||
|
||||
// Kind of like Python's readlines(), but without any allocation.
|
||||
// Calls f() on each line.
|
||||
// F is [](const char*, size_t) -> void
|
||||
template <typename F>
|
||||
static void readlines(const void* data, size_t size, F f) {
|
||||
const char* start = (const char*)data;
|
||||
const char* end = start + size;
|
||||
const char* ptr = start;
|
||||
while (ptr < end) {
|
||||
while (*ptr++ != '\n' && ptr < end) {}
|
||||
size_t len = ptr - start;
|
||||
f(start, len);
|
||||
start = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::setText(const char* data, size_t length) {
|
||||
std::vector<Editor::TextLine> lines;
|
||||
if (data && length) {
|
||||
readlines(data, length, [&lines](const char* p, size_t s) {
|
||||
if (s > 0 && p[s - 1] == '\n') { --s; } // rstrip()
|
||||
lines.push_back(Editor::TextLine(SkString(p, s)));
|
||||
});
|
||||
}
|
||||
fLines = lines;
|
||||
}
|
||||
|
||||
void Editor::paint(SkCanvas* c) {
|
||||
SkPaint background;
|
||||
background.setBlendMode(SkBlendMode::kSrc);
|
||||
background.setColor4f(fBackgroundColor, nullptr);
|
||||
c->drawPaint(background);
|
||||
int y = fMargin;
|
||||
SkPaint p;
|
||||
p.setColor4f(fForegroundColor, nullptr);
|
||||
SkPaint diff;
|
||||
diff.setColor(SK_ColorWHITE);
|
||||
diff.setBlendMode(SkBlendMode::kDifference);
|
||||
float left = (float)fMargin;
|
||||
float right = (float)(fWidth - fMargin);
|
||||
for (const TextLine& line : fLines) {
|
||||
if (line.fBlob) {
|
||||
c->drawTextBlob(line.fBlob.get(), left, (float)y, p);
|
||||
}
|
||||
if (line.fSelected) {
|
||||
c->drawRect(SkRect{left, (float)y, right, (float)(y + line.fHeight)}, diff);
|
||||
}
|
||||
y += line.fHeight;
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::setWidth(int w) {
|
||||
fWidth = w;
|
||||
float width = (float)(fWidth - 2 * fMargin);
|
||||
#ifdef SK_EDITOR_GO_FAST
|
||||
SkSemaphore semaphore;
|
||||
std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool(100);
|
||||
for (TextLine& line : fLines) {
|
||||
executor->add([&]() {
|
||||
line.fBlob = shape(line.fText, fFont, shaper.get(), width, &line.fHeight);
|
||||
semaphore.signal();
|
||||
});
|
||||
}
|
||||
for (const TextLine& l : fLines) { semaphore.wait(); }
|
||||
#else
|
||||
auto shaper = SkShaper::Make();
|
||||
for (TextLine& line : fLines) {
|
||||
line.fBlob = shape(line.fText, fFont, shaper.get(), width, &line.fHeight);
|
||||
}
|
||||
#endif
|
||||
float h = 2.0f * fMargin;
|
||||
for (TextLine& line : fLines) {
|
||||
h += line.fHeight;
|
||||
}
|
||||
fHeight = (int)ceilf(h);
|
||||
}
|
60
experimental/editor/editor.h
Normal file
60
experimental/editor/editor.h
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
|
||||
#ifndef editor_DEFINED
|
||||
#define editor_DEFINED
|
||||
|
||||
#include "include/core/SkColor.h"
|
||||
#include "include/core/SkFont.h"
|
||||
#include "include/core/SkString.h"
|
||||
#include "include/core/SkTextBlob.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class SkCanvas;
|
||||
|
||||
// TODO: modulize this; editor::Editor becomes SkEditor ?
|
||||
|
||||
namespace editor {
|
||||
|
||||
class Editor {
|
||||
public:
|
||||
void setText(const char* text, size_t len);
|
||||
int getHeight() const { return fHeight; }
|
||||
int getMargin() const { return fMargin; }
|
||||
void paint(SkCanvas* canvas);
|
||||
void setWidth(int w); // may force re-shape
|
||||
const SkFont& font() const { return fFont; }
|
||||
|
||||
// query buffer:
|
||||
struct Str {
|
||||
const char* fPtr = nullptr;
|
||||
size_t fLen = 0;
|
||||
};
|
||||
size_t lineCount() const { return fLines.size(); }
|
||||
Str line(size_t index) const { return fLines[index].text(); }
|
||||
int lineHeight(size_t index) const { return fLines[index].fHeight; }
|
||||
|
||||
// experimental interface
|
||||
void select(unsigned lineIndex) { fLines[lineIndex].fSelected = !fLines[lineIndex].fSelected; }
|
||||
|
||||
private:
|
||||
struct TextLine {
|
||||
SkString fText;
|
||||
int fHeight = 0;
|
||||
sk_sp<const SkTextBlob> fBlob;
|
||||
bool fSelected = false; // Will allow selection of subset of text later.
|
||||
// Also will track presence of cursor.
|
||||
|
||||
TextLine(SkString s) : fText(std::move(s)) {}
|
||||
Str text() const { return Str{fText.c_str(), fText.size()}; }
|
||||
};
|
||||
std::vector<TextLine> fLines;
|
||||
int fMargin = 10;
|
||||
int fWidth = 0;
|
||||
int fHeight = 0;
|
||||
SkFont fFont{nullptr, 24};
|
||||
SkColor4f fBackgroundColor = {0.8f, 0.8f, 0.8f, 1};
|
||||
SkColor4f fForegroundColor = {0, 0, 0, 1};
|
||||
};
|
||||
} // namespace editor
|
||||
#endif // editor_DEFINED
|
@ -4,22 +4,37 @@
|
||||
// [Work In Progress] Proof of principle of a text editor written with Skia & SkShaper.
|
||||
// https://bugs.skia.org/9020
|
||||
|
||||
#include "include/core/SkExecutor.h"
|
||||
#include "include/core/SkPath.h"
|
||||
#include "include/core/SkPictureRecorder.h"
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkSurface.h"
|
||||
#include "include/core/SkTime.h"
|
||||
#include "include/effects/SkGradientShader.h"
|
||||
#include "modules/skshaper/include/SkShaper.h"
|
||||
|
||||
#include "tools/sk_app/Application.h"
|
||||
#include "tools/sk_app/CommandSet.h"
|
||||
#include "tools/sk_app/Window.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include "editor.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static const char* key_name(sk_app::Window::Key k) {
|
||||
switch (k) {
|
||||
#define M(X) case sk_app::Window::Key::k ## X: return #X
|
||||
M(NONE); M(LeftSoftKey); M(RightSoftKey); M(Home); M(Back); M(Send); M(End); M(0); M(1);
|
||||
M(2); M(3); M(4); M(5); M(6); M(7); M(8); M(9); M(Star); M(Hash); M(Up); M(Down); M(Left);
|
||||
M(Right); M(Tab); M(PageUp); M(PageDown); M(Delete); M(Escape); M(Shift); M(Ctrl);
|
||||
M(Option); M(A); M(C); M(V); M(X); M(Y); M(Z); M(OK); M(VolUp); M(VolDown); M(Power);
|
||||
M(Camera);
|
||||
#undef M
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static SkString modifiers_desc(uint32_t m) {
|
||||
SkString s;
|
||||
#define M(X) if (m & sk_app::Window::k ## X ##_ModifierKey) { s.append(" {" #X "}"); }
|
||||
M(Shift) M(Control) M(Option) M(Command) M(FirstPress)
|
||||
#undef M
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@ -30,90 +45,27 @@ struct Timer {
|
||||
~Timer() { SkDebugf("%s: %d ms\n", fDesc, (int)((SkTime::GetNSecs() - fTime) * 1e-6)); }
|
||||
};
|
||||
|
||||
struct TextLine {
|
||||
TextLine(std::string s) : fText(std::move(s)) {}
|
||||
std::string fText;
|
||||
float fHeight = 0;
|
||||
sk_sp<const SkTextBlob> fBlob;
|
||||
bool fSelected = false; // Will allow selection of subset of text later.
|
||||
// Also will track presence of cursor.
|
||||
|
||||
void shape(const SkFont& font, const SkShaper* shaper, float width) {
|
||||
SkTextBlobBuilderRunHandler textBlobBuilder(fText.c_str(), {0, 0});
|
||||
shaper->shape(fText.c_str(), fText.size(), font, true, width, &textBlobBuilder);
|
||||
fHeight = std::max(textBlobBuilder.endPoint().y(), font.getSpacing());
|
||||
fBlob = textBlobBuilder.makeBlob();
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<TextLine> read_file(const char* path) {
|
||||
std::vector<TextLine> ret;
|
||||
if (path) {
|
||||
std::ifstream stream(path);
|
||||
for (std::string line; std::getline(stream, line);) {
|
||||
ret.push_back(TextLine(line));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct EditorLayer : public sk_app::Window::Layer {
|
||||
sk_app::Window* fParent = nullptr;
|
||||
std::vector<TextLine> fLines;
|
||||
const SkShaper* fShaper = nullptr;
|
||||
int fMargin = 10;
|
||||
int fPos = 10;
|
||||
int fWidth = 0;
|
||||
int fHeight = 0;
|
||||
SkFont fFont{nullptr, 24};
|
||||
editor::Editor fEditor;
|
||||
int fPos = 0; // window pixel position in file
|
||||
int fWidth = 0; // window width
|
||||
int fHeight = 0; // window height
|
||||
|
||||
EditorLayer(std::vector<TextLine> lines, SkShaper* shaper)
|
||||
: fLines(std::move(lines)), fShaper(shaper) {}
|
||||
|
||||
void onPaint(SkSurface* surface) override {
|
||||
Timer timer("painting");
|
||||
SkColor background = SkColorSetARGB(0xFF, 0xCC, 0xCC, 0xCC);
|
||||
SkColor foreground = SkColorSetARGB(0xFF, 0x00, 0x00, 0x00);
|
||||
SkCanvas* c = surface->getCanvas();
|
||||
c->clipRect({0, 0, (float)fWidth, (float)fHeight});
|
||||
c->clear(background);
|
||||
float y = fPos;
|
||||
SkPaint p;
|
||||
p.setColor(foreground);
|
||||
SkPaint diff;
|
||||
diff.setColor(SK_ColorWHITE);
|
||||
diff.setBlendMode(SkBlendMode::kDifference);
|
||||
float width = (float)(fWidth - 2 * fMargin);
|
||||
for (const TextLine& line : fLines) {
|
||||
if (line.fBlob) {
|
||||
c->drawTextBlob(line.fBlob.get(), fMargin, y, p);
|
||||
}
|
||||
if (line.fSelected) {
|
||||
c->drawRect(SkRect{(float)fMargin, y, width, y + line.fHeight}, diff);
|
||||
}
|
||||
y += line.fHeight;
|
||||
void loadFile(const char* path) {
|
||||
if (sk_sp<SkData> data = SkData::MakeFromFileName(path)) {
|
||||
fEditor.setText((const char*)data->data(), data->size());
|
||||
}
|
||||
}
|
||||
|
||||
void reshape() {
|
||||
Timer timer("shaping");
|
||||
float width = (float)(fWidth - 2 * fMargin);
|
||||
#ifdef SK_EDITOR_GO_FAST
|
||||
SkSemaphore semaphore;
|
||||
std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool(100);
|
||||
for (TextLine& line : fLines) {
|
||||
executor->add([&]() {
|
||||
auto shaper = SkShaper::Make();
|
||||
line.shape(fFont, shaper.get(), width);
|
||||
semaphore.signal();
|
||||
});
|
||||
}
|
||||
for (const TextLine& l : fLines) { semaphore.wait(); }
|
||||
#else
|
||||
for (TextLine& line : fLines) {
|
||||
line.shape(fFont, fShaper, width);
|
||||
}
|
||||
#endif
|
||||
void onPaint(SkSurface* surface) override {
|
||||
Timer timer("painting");
|
||||
SkCanvas* c = surface->getCanvas();
|
||||
SkAutoCanvasRestore acr(c, true);
|
||||
c->clipRect({0, 0, (float)fWidth, (float)fHeight});
|
||||
c->translate(0, -(float)fPos);
|
||||
fEditor.paint(c);
|
||||
}
|
||||
|
||||
void onResize(int width, int height) override {
|
||||
@ -121,7 +73,8 @@ struct EditorLayer : public sk_app::Window::Layer {
|
||||
fHeight = height;
|
||||
if (width != fWidth) {
|
||||
fWidth = width;
|
||||
this->reshape();
|
||||
Timer timer("shaping");
|
||||
fEditor.setWidth(fWidth);
|
||||
}
|
||||
if (fParent) {
|
||||
fParent->inval();
|
||||
@ -131,63 +84,86 @@ struct EditorLayer : public sk_app::Window::Layer {
|
||||
|
||||
void onAttach(sk_app::Window* w) override { fParent = w; }
|
||||
|
||||
bool onMouseWheel(float delta, uint32_t modifiers) override {
|
||||
int newpos = std::min(fPos + (int)(delta * fFont.getSpacing()), fMargin);
|
||||
|
||||
void scroll(int delta) {
|
||||
int maxPos = std::max(0, fEditor.getHeight() - fHeight / 2);
|
||||
int newpos = std::max(0, std::min(fPos + delta, maxPos));
|
||||
if (newpos != fPos) {
|
||||
fPos = newpos;
|
||||
if (fParent) { fParent->inval(); }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onMouseWheel(float delta, uint32_t modifiers) override {
|
||||
this->scroll(-(int)(delta * fEditor.font().getSpacing()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onMouse(int x, int y, sk_app::Window::InputState state, uint32_t modifiers) override {
|
||||
if (sk_app::Window::kDown_InputState == state) {
|
||||
y -= fPos;
|
||||
y += fPos;
|
||||
y -= fEditor.getMargin();
|
||||
if (y >= 0) {
|
||||
for (TextLine& line : fLines) {
|
||||
if (y < line.fHeight) {
|
||||
line.fSelected = !line.fSelected;
|
||||
SkDebugf(" %d %d\n", x - (int)fMargin, y);
|
||||
for (size_t i = 0; i < fEditor.lineCount(); ++i) {
|
||||
int height = fEditor.lineHeight(i);
|
||||
if (y < height) {
|
||||
fEditor.select(i);
|
||||
SkDebugf("select: line:%d x:%d y:%d\n", i, x - fEditor.getMargin(), y);
|
||||
fParent->inval();
|
||||
break;
|
||||
}
|
||||
y -= line.fHeight;
|
||||
y -= height;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool onChar(SkUnichar c, uint32_t modifiers) override {
|
||||
SkString m = modifiers_desc(modifiers);
|
||||
SkDebugf("char: %c (0x%08X)%s\n", (char)(c & 0xFF), (unsigned)c, m.c_str());
|
||||
return true;
|
||||
}
|
||||
bool onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) override {
|
||||
if (state == sk_app::Window::kDown_InputState) {
|
||||
if (key == sk_app::Window::Key::kPageDown) {
|
||||
this->scroll(fHeight * 4 / 5);
|
||||
return true;
|
||||
}
|
||||
if (key == sk_app::Window::Key::kPageUp) {
|
||||
this->scroll(-fHeight * 4 / 5);
|
||||
return true;
|
||||
}
|
||||
SkString m = modifiers_desc(modifiers);
|
||||
SkDebugf("key: %s%s\n", key_name(key), m.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct EditorApplication : public sk_app::Application {
|
||||
std::unique_ptr<SkShaper> fShaper;
|
||||
std::unique_ptr<sk_app::Window> fWindow;
|
||||
std::unique_ptr<EditorLayer> fLayer;
|
||||
sk_app::CommandSet fCommandSet;
|
||||
EditorLayer fLayer;
|
||||
|
||||
EditorApplication(const char* path, void* platformData)
|
||||
: fShaper(SkShaper::Make())
|
||||
, fWindow(sk_app::Window::CreateNativeWindow(platformData))
|
||||
: fWindow(sk_app::Window::CreateNativeWindow(platformData))
|
||||
{
|
||||
fWindow->attach(sk_app::Window::kRaster_BackendType);
|
||||
fLayer.reset(new EditorLayer(read_file(path), fShaper.get()));
|
||||
fWindow->pushLayer(fLayer.get());
|
||||
fCommandSet.attach(fWindow.get());
|
||||
//sk_app::Window::BackendType backendType = sk_app::Window::kRaster_BackendType;
|
||||
sk_app::Window::BackendType backendType = sk_app::Window::kNativeGL_BackendType;
|
||||
fWindow->attach(backendType);
|
||||
fLayer.loadFile(path);
|
||||
fWindow->pushLayer(&fLayer);
|
||||
fWindow->show();
|
||||
fLayer->onResize(fWindow->width(), fWindow->height());
|
||||
fLayer.onResize(fWindow->width(), fWindow->height());
|
||||
}
|
||||
~EditorApplication() override { fWindow->detach(); }
|
||||
|
||||
void onIdle() override {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
sk_app::Application* sk_app::Application::Create(int argc, char** argv, void* dat) {
|
||||
if (argc > 1) {
|
||||
return new EditorApplication(argv[1], dat);
|
||||
}
|
||||
return new EditorApplication(nullptr, dat);
|
||||
return new EditorApplication(argc > 1 ? argv[1] : nullptr, dat);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user