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 });
|
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);
|
// const std::string wrappedBody = wrapText(model.visibleText, 56);
|
||||||
bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
|
// 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();
|
lastChoiceRects.clear();
|
||||||
if (model.mode == PresentationMode::Choice) {
|
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>{0.82f, 0.82f, 0.82f, 1.0f}
|
||||||
: std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 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(
|
choiceRenderer->drawText(
|
||||||
wrapText(model.choices[i].text, 52),
|
wrappedChoiceText,
|
||||||
rect.x + 14.0f,
|
rect.x + 14.0f,
|
||||||
rect.y + 9.0f,
|
rect.y + 9.0f,
|
||||||
1.0f,
|
choiceTextScale,
|
||||||
false,
|
false,
|
||||||
isHighlighted ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
|
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 }
|
{ 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(
|
cutsceneRenderer->drawText(
|
||||||
wrapText(model.visibleText, 62),
|
wrappedSubtitle,
|
||||||
subtitleRect.x + 24.0f,
|
subtitleRect.x + 24.0f,
|
||||||
subtitleRect.y + 30.0f,
|
subtitleRect.y + 30.0f,
|
||||||
1.0f,
|
subtitleTextScale,
|
||||||
false,
|
false,
|
||||||
{ 1.0f, 1.0f, 1.0f, 1.0f }
|
{ 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;
|
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) {
|
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;
|
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;
|
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 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 bool rectContains(const UiRect& rect, float x, float y);
|
||||||
|
|
||||||
static float lerpFloat(float a, float b, float t);
|
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;
|
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)
|
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;
|
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);
|
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 });
|
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)
|
// Clear cached meshes (call on window resize / DPI change)
|
||||||
void ClearCache();
|
void ClearCache();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user