forked from Mirror/libstarlight
some flailing at text... then a complete text-rendering rewrite! lambda-loop shininess!
This commit is contained in:
parent
36960effa4
commit
bca1adc6eb
@ -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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
pen.x += wordlen;
|
||||
if (pen.x + space + wordlen > maxWidth) {
|
||||
pen.x = wordlen;
|
||||
pen.y += lineHeight;
|
||||
} else {
|
||||
pen.x += wordlen + space;
|
||||
}
|
||||
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
|
||||
}
|
||||
if (!total) {
|
||||
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++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user