implement dynamic text wrapping based on pixel width
This commit is contained in:
parent
0a073243ad
commit
dbf0af2cb2
@ -204,8 +204,14 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
|
||||
nameRenderer->drawText(model.speaker, nameX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
|
||||
}
|
||||
|
||||
const std::string wrappedBody = wrapText(model.visibleText, 56);
|
||||
bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
// const std::string wrappedBody = wrapText(model.visibleText, 56);
|
||||
// bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
|
||||
const float bodyTextScale = 1.0f;
|
||||
const float bodyMaxWidthPx = textboxRect.w - 48.0f;
|
||||
|
||||
const std::string wrappedBody = wrapTextToWidth(model.visibleText, *bodyRenderer, bodyMaxWidthPx, bodyTextScale);
|
||||
bodyRenderer->drawText(wrappedBody, bodyX, bodyY, bodyTextScale, false, { 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
|
||||
lastChoiceRects.clear();
|
||||
if (model.mode == PresentationMode::Choice) {
|
||||
@ -249,11 +255,21 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
|
||||
? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f}
|
||||
: std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f };
|
||||
|
||||
const float choiceTextScale = 1.0f;
|
||||
const float choiceMaxWidthPx = rect.w - 28.0f;
|
||||
|
||||
const std::string wrappedChoiceText = wrapTextToWidth(
|
||||
model.choices[i].text,
|
||||
*choiceRenderer,
|
||||
choiceMaxWidthPx,
|
||||
choiceTextScale
|
||||
);
|
||||
|
||||
choiceRenderer->drawText(
|
||||
wrapText(model.choices[i].text, 52),
|
||||
wrappedChoiceText,
|
||||
rect.x + 14.0f,
|
||||
rect.y + 9.0f,
|
||||
1.0f,
|
||||
choiceTextScale,
|
||||
false,
|
||||
isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
|
||||
);
|
||||
@ -502,11 +518,21 @@ void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel&
|
||||
{ 1.0f, 0.88f, 0.45f, 1.0f }
|
||||
);
|
||||
}
|
||||
const float subtitleTextScale = 1.0f;
|
||||
const float subtitleMaxWidthPx = subtitleRect.w - 48.0f;
|
||||
|
||||
const std::string wrappedSubtitle = wrapTextToWidth(
|
||||
model.visibleText,
|
||||
*cutsceneRenderer,
|
||||
subtitleMaxWidthPx,
|
||||
subtitleTextScale
|
||||
);
|
||||
|
||||
cutsceneRenderer->drawText(
|
||||
wrapText(model.visibleText, 62),
|
||||
wrappedSubtitle,
|
||||
subtitleRect.x + 24.0f,
|
||||
subtitleRect.y + 30.0f,
|
||||
1.0f,
|
||||
subtitleTextScale,
|
||||
false,
|
||||
{ 1.0f, 1.0f, 1.0f, 1.0f }
|
||||
);
|
||||
@ -671,6 +697,71 @@ std::string DialogueOverlay::wrapText(const std::string& input, size_t maxLineLe
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string DialogueOverlay::wrapTextToWidth(const std::string& input, const TextRenderer& textRenderer, float maxWidthPx, float scale)
|
||||
{
|
||||
if (input.empty() || maxWidthPx <= 1.0f) {
|
||||
return input;
|
||||
}
|
||||
|
||||
std::string output;
|
||||
std::string currentLine;
|
||||
std::string currentWord;
|
||||
|
||||
auto flushLine = [&]() {
|
||||
if (!currentLine.empty()) {
|
||||
if (!output.empty()) {
|
||||
output.push_back('\n');
|
||||
}
|
||||
output += currentLine;
|
||||
currentLine.clear();
|
||||
}
|
||||
};
|
||||
|
||||
auto pushWord = [&](const std::string& word) {
|
||||
if (word.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentLine.empty()) {
|
||||
currentLine = word;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string candidate = currentLine + " " + word;
|
||||
if (textRenderer.measureTextWidth(candidate, scale) <= maxWidthPx) {
|
||||
currentLine = candidate;
|
||||
}
|
||||
else {
|
||||
flushLine();
|
||||
currentLine = word;
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
const char ch = input[i];
|
||||
|
||||
if (ch == '\n') {
|
||||
pushWord(currentWord);
|
||||
currentWord.clear();
|
||||
flushLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == ' ' || ch == '\t' || ch == '\r') {
|
||||
pushWord(currentWord);
|
||||
currentWord.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
currentWord.push_back(ch);
|
||||
}
|
||||
|
||||
pushWord(currentWord);
|
||||
flushLine();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool DialogueOverlay::rectContains(const UiRect& rect, float x, float y) {
|
||||
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
|
||||
}
|
||||
|
||||
@ -99,6 +99,7 @@ private:
|
||||
void drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const;
|
||||
|
||||
static std::string wrapText(const std::string& input, size_t maxLineLength);
|
||||
static std::string wrapTextToWidth(const std::string& input, const TextRenderer& textRenderer, float maxWidthPx, float scale);
|
||||
static bool rectContains(const UiRect& rect, float x, float y);
|
||||
|
||||
static float lerpFloat(float a, float b, float t);
|
||||
|
||||
@ -347,6 +347,38 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
||||
return true;
|
||||
}
|
||||
|
||||
float TextRenderer::measureTextWidth(const std::string& text, float scale) const
|
||||
{
|
||||
if (text.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float penX = 0.0f;
|
||||
float maxLineWidth = 0.0f;
|
||||
|
||||
size_t textPos = 0;
|
||||
while (textPos < text.size()) {
|
||||
uint32_t cp = nextUtf8Codepoint(text, textPos);
|
||||
|
||||
if (cp == '\n') {
|
||||
maxLineWidth = max(maxLineWidth, penX);
|
||||
penX = 0.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = glyphs.find(cp);
|
||||
if (it == glyphs.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GlyphInfo& g = it->second;
|
||||
penX += static_cast<float>(g.advance >> 6) * scale;
|
||||
}
|
||||
|
||||
maxLineWidth = max(maxLineWidth, penX);
|
||||
return maxLineWidth;
|
||||
}
|
||||
|
||||
void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color)
|
||||
{
|
||||
if (!r || text.empty() || !atlasTexture) return;
|
||||
|
||||
@ -29,6 +29,8 @@ public:
|
||||
bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
|
||||
void drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color = { 1.f,1.f,1.f,1.f });
|
||||
|
||||
float measureTextWidth(const std::string& text, float scale = 1.0f) const;
|
||||
|
||||
// Clear cached meshes (call on window resize / DPI change)
|
||||
void ClearCache();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user