some flailing at text... then a complete text-rendering rewrite! lambda-loop shininess!

This commit is contained in:
zetaPRIME 2017-03-15 10:42:24 -04:00
parent 36960effa4
commit bca1adc6eb
7 changed files with 184 additions and 43 deletions

View File

@ -31,6 +31,13 @@ using starlight::ui::Form;
using starlight::dialog::OSK;
namespace {
inline starlight::TextConfig& PreviewTC() {
static auto tc = ThemeManager::GetMetric<starlight::TextConfig>("/dialogs/OSK/preview");
return tc;
}
}
OSK::OSK(osk::InputHandler* handler) : Form(true), handler(handler) {
priority = 1000; // probably don't want all that much displaying above the keyboard
handler->parent = this;
@ -117,7 +124,7 @@ void OSK::Update(bool focused) {
preview->Refresh();
}
static auto tc = ThemeManager::GetMetric<TextConfig>("/dialogs/OSK/preview");
auto& tc = PreviewTC();
if (InputManager::Pressed(Keys::DPadUp)) {
Vector2 pt = tc.font->GetCursorPosition(preview->rect, handler->GetPreviewText(), handler->GetCursor());
string msr = "|";
@ -128,7 +135,7 @@ void OSK::Update(bool focused) {
if (InputManager::Pressed(Keys::DPadDown)) {
Vector2 pt = tc.font->GetCursorPosition(preview->rect, handler->GetPreviewText(), handler->GetCursor());
string msr = "|";
pt.y += tc.font->Measure(msr).y;
pt.y += tc.font->Measure(msr).y * 1.5f;
handler->SetCursor(tc.font->GetCursorFromPoint(preview->rect, handler->GetPreviewText(), pt));
preview->Refresh();
}
@ -152,7 +159,7 @@ void OSK::OnKey() {
void OSK::DrawPreview(DrawLayerProxy& layer) {
if (true || handler->showPreview) {
static auto tc = ThemeManager::GetMetric<TextConfig>("/dialogs/OSK/preview");
auto& tc = PreviewTC();
tc.Print(layer.rect, handler->GetPreviewText(), Vector2::zero);
Vector2 cp = tc.font->GetCursorPosition(layer.rect, handler->GetPreviewText(), handler->GetCursor());
@ -163,7 +170,7 @@ void OSK::DrawPreview(DrawLayerProxy& layer) {
void OSK::OnPreviewTap(DrawLayerProxy& layer) {
Vector2 tpos = InputManager::TouchPos() - layer.ScreenRect().pos;
static auto tc = ThemeManager::GetMetric<TextConfig>("/dialogs/OSK/preview");
auto& tc = PreviewTC();
handler->SetCursor(tc.font->GetCursorFromPoint(layer.rect, handler->GetPreviewText(), tpos + layer.rect.pos));
preview->Refresh();
}

View File

@ -1,5 +1,7 @@
#include "InputHandler.h"
#include <cmath>
#include "starlight/ui/Form.h"
using std::string;
@ -13,7 +15,7 @@ using starlight::dialog::osk::InputHandlerBuffered;
std::string& InputHandlerDirectEdit::GetPreviewText() { return *pText; }
unsigned int InputHandlerDirectEdit::GetCursor() { return cursor; }
void InputHandlerDirectEdit::SetCursor(unsigned int index) { cursor = index; if (cursor < minIndex) cursor = minIndex; auto len = pText->length(); if (cursor > len) cursor = len; }
void InputHandlerDirectEdit::SetCursor(unsigned int index) { cursor = std::max(minIndex, std::min(index, pText->length())); }
void InputHandlerDirectEdit::InputSymbol(const string& sym) {
//pText->append(sym);
@ -45,15 +47,16 @@ void InputHandlerDirectEdit::Done() {
std::string& InputHandlerBuffered::GetPreviewText() { return buffer; }
unsigned int InputHandlerBuffered::GetCursor() { return buffer.length(); }
void InputHandlerBuffered::SetCursor(unsigned int index) { }
unsigned int InputHandlerBuffered::GetCursor() { return cursor; }
void InputHandlerBuffered::SetCursor(unsigned int index) { cursor = std::max(0U, std::min(index, buffer.length())); }
void InputHandlerBuffered::InputSymbol(const string& sym) {
buffer.append(sym);
buffer.insert(cursor, sym);
cursor += sym.length();
}
void InputHandlerBuffered::Backspace() {
buffer.pop_back();
if (cursor > 0) buffer.erase(--cursor, 1);
}
void InputHandlerBuffered::Enter() {

View File

@ -65,10 +65,12 @@ namespace starlight {
public:
bool multiLine = false;
unsigned int cursor = 0;
std::function<void(const std::string&)> eOnFinalize = { };
InputHandlerBuffered(const std::string& text = "", bool multiLine = false, std::function<void(const std::string&)> onFinalize = {})
: buffer(text), multiLine(multiLine), eOnFinalize(onFinalize) { this->showPreview = true; }
: buffer(text), multiLine(multiLine), cursor(text.length()), eOnFinalize(onFinalize) { this->showPreview = true; }
~InputHandlerBuffered() override { }
std::string& GetPreviewText() override;

View File

@ -101,25 +101,22 @@ Vector2 BitmapFont::MeasureTo(std::string& msg, bool total, unsigned int end, fl
for (unsigned int i = 0; i < len; i++) {
char& c = msg[i];
if (c == ' ' || c == '\n') {
bool alb = false; // already linebreak
bool lb = (c == '\n' || pen.x + wordlen > maxWidth);
oPen = pen;
if (pen.x > longest) longest = pen.x;
if (pen.x + wordlen > maxWidth) {
pen.x = 0; pen.y += lineHeight;
alb = true;
}
pen.x += wordlen;
pen.x += space;
if (c == '\n' && !alb) {
if (pen.x > longest) longest = pen.x;
pen.x = 0; pen.y += lineHeight;
if (lb) {
if (c == '\n') pen.x += space + wordlen; // previous word
longest = std::max(pen.x - space, longest);
pen.x = (c == ' ') ? (space + wordlen) : 0;
pen.y += lineHeight;
} else {
pen.x += space + wordlen;
}
if (!total && i >= end) {
//if (c == ' ') pen.x -= space; // I think this needs to be undone too?
return Vector2(oPen.x + plen, oPen.y); // return cursor position
return Vector2(pen.x - (wordlen - plen) - space, pen.y);
}
wordlen = plen = 0;
} else {
float adv = Char(c).advX;
@ -127,23 +124,21 @@ Vector2 BitmapFont::MeasureTo(std::string& msg, bool total, unsigned int end, fl
if (i < end) plen += adv;
}
}
longest = std::max(pen.x - space, longest);
{ // oh right, this check kind of needs to happen at the end too
if (pen.x > longest) longest = pen.x;
if (pen.x + wordlen > maxWidth) {
pen.x = 0; pen.y += lineHeight;
if (pen.x + space + wordlen > maxWidth) {
pen.x = wordlen;
pen.y += lineHeight;
} else {
pen.x += wordlen + space;
}
pen.x += wordlen;
longest = std::max(pen.x - space, longest); // do I need two of these?
if (!total) {
//if (c == ' ') pen.x -= space; // I think this needs to be undone too?
return Vector2(pen.x - (wordlen - plen), pen.y); // return cursor position
}
return Vector2(pen.x - (wordlen - plen) - space, pen.y); // return cursor position
}
//if (msg.back() != '\n') pen.y += lineHeight; // trim trailing newline (todo: make this recursive...?)
pen.y += lineHeight;
return Vector2(longest, pen.y); // total size
return Vector2(longest, pen.y + lineHeight); // total size
}
unsigned int BitmapFont::PointToIndex(std::string& msg, Vector2 pt, float maxWidth) {
@ -199,7 +194,93 @@ unsigned int BitmapFont::PointToIndex(std::string& msg, Vector2 pt, float maxWid
return len;
}
void BitmapFont::ForChar(const std::string& msg, std::function<bool(CharLoopState&)> func, float maxWidth) {
struct LineStat {
unsigned int start;
unsigned int end;
float width;
};
float space = Char(' ').advX;
std::vector<LineStat> lines;
{
LineStat cl = {0, 0, 0};
float ww = 0;
unsigned int ws = 0;
unsigned int len = msg.length();
for (unsigned int i = 0; i <= len; i++) {
char c = (i == len) ? '\n' : msg[i];
char pc = (i == 0) ? '\n' : msg[i-1];
if (c == ' ' || c == '\n') {
// handle previously-accumulated word
if (cl.start != ws && cl.width + ww > maxWidth) { // don't bother wrapping the first word on a line...
// omit breaking space from the end of the line
if (ws > 0 && msg[ws-1] == ' ') cl.width -= space;
// wrap
lines.push_back(cl);
cl = {ws, i, ww}; // start at start of word, end at < this char, have width of word
ww = 0;
ws = i+1;
} else {
// add to length
cl.width += ww;
cl.end = i; // < this char
ww = 0;
ws = i+1;
}
if (c == ' ') {
// space should be present in spacing when it *is not* the line-terminator (that is, the space before a wrapped word)
// how the fuck do I do that!?
// probably a retroactive decrement on wrap event...
if (true) { // if not... this stuff ^
cl.width += space;
}
} else { // newline
// I guess there's no circumstance where a newline *shouldn't* be honored?
cl.end = i;
lines.push_back(cl);
cl = {i+1, i+1, 0};
}
} else { // non-word-ender! add to accumulated word width
ww += Char(c).advX + GetKerning(pc, c);
}
}
// don't need to manually do anything after due to the extra step
//lines.push_back(cl);
}
CharLoopState state;
state.numLines = lines.size();
state.lineNum = 0;
for (auto& cl : lines) {
state.lineStart = cl.start;
state.lineEnd = cl.end;
state.lineWidth = cl.width;
state.lineAcc = 0;
state.c = '\n';
for (unsigned int i = cl.start; i < cl.end; i++) {
char pc = state.c;
state.c = msg[i];
state.cc = &(Char(state.c));
state.i = i;
state.iline = i - cl.start;
state.lineAcc += GetKerning(pc, state.c); // I think this goes before?
if (func(state)) return; // return true to break
state.lineAcc += state.cc->advX;
}
state.lineNum++;
}
}

View File

@ -2,6 +2,7 @@
#include "starlight/_global.h"
#include <memory>
#include <functional>
#include <unordered_map>
#include <string>
@ -29,6 +30,17 @@ namespace starlight {
float advX;
};
struct CharLoopState {
unsigned int lineStart, lineEnd;
float lineWidth;
unsigned int numLines;
unsigned int lineNum;
char c;
CharInfo* cc;
float lineAcc;
unsigned int i, iline;
};
float fontSize;
float lineHeight;
float baseY;
@ -52,6 +64,8 @@ namespace starlight {
Vector2 MeasureTo(std::string& msg, bool total = true, unsigned int end = 4294967295, float maxWidth = 65536*64);
unsigned int PointToIndex(std::string& msg, Vector2 pt, float maxWidth = 65536*64);
void ForChar(const std::string& msg, std::function<bool(CharLoopState&)> func, float maxWidth = 65536*64);
static inline constexpr unsigned int KerningKey(char cl, char cr) {
return (static_cast<unsigned int>(cl) | (static_cast<unsigned int>(cr) << 8));
}

View File

@ -28,7 +28,21 @@ void FontBMF::Print(Vector2 position, std::string& text, float scale, Color colo
if (text == "") return;
if (GFXManager::PrepareForDrawing()) {
DisplayList dl = DisplayList();
PrintOp(position, text, scale, color, justification, borderColor, 2147483647, static_cast<Vector2*>(nullptr), &dl);
//PrintOp(position, text, scale, color, justification, borderColor, 2147483647, static_cast<Vector2*>(nullptr), &dl);
{
auto qn = dl.GetLastNode<DLNode_Quads>(true);
Vector2 uvScale = Vector2::one / font->txMain->txSize;
Vector2 ppen = Vector2(-font->padX, -font->padY /*- (font->lineHeight - font->baseY)*/);
font->ForChar(text, [&, this, ppen, justification, qn, scale, uvScale](auto& s){
if (s.c == ' ') return false; // skip spaces
Vector2 pen = (ppen + Vector2(s.lineAcc - s.lineWidth * justification.x, font->lineHeight * ((float)s.lineNum - (float)s.numLines * justification.y))) * scale;
auto& ci = *s.cc;
VRect crect(ci.imgX, ci.imgY, ci.width, ci.height);
qn->Add(VRect(pen, crect.size * scale), crect * uvScale);
return false;
});
}
if (borderColor && borderColor.get() != Color::transparent && font->txBorder) {
font->txBorder->Bind(borderColor.get());
dl.Run(position);
@ -42,15 +56,30 @@ void FontBMF::Print(VRect rect, std::string& text, float scale, Color color, Vec
if (text == "") return;
if (GFXManager::PrepareForDrawing()) {
if (borderColor && borderColor.get() != Color::transparent) rect = rect.Expand(-1, -1);
Vector2 pos = rect.pos + rect.size * justification;
Vector2 position = rect.pos + rect.size * justification;
DisplayList dl = DisplayList();
PrintOp(pos, text, scale, color, justification, borderColor, rect.size.x, static_cast<Vector2*>(nullptr), &dl);
//PrintOp(pos, text, scale, color, justification, borderColor, rect.size.x, static_cast<Vector2*>(nullptr), &dl);
{
auto qn = dl.GetLastNode<DLNode_Quads>(true);
Vector2 uvScale = Vector2::one / font->txMain->txSize;
Vector2 ppen = Vector2(-font->padX, -font->padY /*- (font->lineHeight - font->baseY)*/);
font->ForChar(text, [&, this, ppen, justification, qn, scale, uvScale](auto& s){
if (s.c == ' ') return false; // skip spaces
Vector2 pen = (ppen + Vector2(s.lineAcc - s.lineWidth * justification.x, font->lineHeight * ((float)s.lineNum - (float)s.numLines * justification.y))) * scale;
auto& ci = *s.cc;
VRect crect(ci.imgX, ci.imgY, ci.width, ci.height);
qn->Add(VRect(pen, crect.size * scale), crect * uvScale);
return false;
}, rect.size.x);
}
if (borderColor && borderColor.get() != Color::transparent && font->txBorder) {
font->txBorder->Bind(borderColor.get());
dl.Run(pos);
dl.Run(position);
}
font->txMain->Bind(color);
dl.Run(pos);
dl.Run(position);
}
}

View File

@ -9,6 +9,11 @@ roadmap to first release, in no particular order {
- preview where applicable
fix desync between cursor position, tap-cursor and display when a single word overflows a line
(cursor doesn't wrap until the next word!?)
fix font glyph padding to eliminate slight "crosstalk" in bordered variants
... reimplement as a per-character lambda-"loop" {
status object with stuff
precalculate line stats, then loop through each
}
polish!
InputManager::OpenKeyboard
}