fix: Fix rendering of multiline listbox items
This commit is contained in:
parent
2c6955bd22
commit
3bc66751f7
15 changed files with 268 additions and 84 deletions
|
@ -171,6 +171,7 @@ set(RENDER_HEADERS
|
|||
src/render/texture.h
|
||||
src/render/textures.h
|
||||
src/render/textureutil.h
|
||||
src/render/textutil.h
|
||||
src/render/types.h
|
||||
src/render/walkmesh.h
|
||||
src/render/walkmeshes.h
|
||||
|
@ -204,6 +205,7 @@ set(RENDER_SOURCES
|
|||
src/render/texture.cpp
|
||||
src/render/textures.cpp
|
||||
src/render/textureutil.cpp
|
||||
src/render/textutil.cpp
|
||||
src/render/walkmesh.cpp
|
||||
src/render/walkmeshes.cpp
|
||||
src/render/window.cpp)
|
||||
|
|
|
@ -52,6 +52,9 @@ void QuickOrCustom::load() {
|
|||
if (_version == GameVersion::KotOR) {
|
||||
setControlDiscardColor("LBL_RBG", glm::vec3(0.0f, 0.0f, 0.082353f));
|
||||
}
|
||||
|
||||
auto &lbDesc = getControl<ListBox>("LB_DESC");
|
||||
lbDesc.setProtoMatchContent(true);
|
||||
}
|
||||
|
||||
void QuickOrCustom::onClick(const string &control) {
|
||||
|
@ -79,7 +82,7 @@ void QuickOrCustom::onFocusChanged(const string &control, bool focus) {
|
|||
ListBox::Item item;
|
||||
item.text = text;
|
||||
|
||||
ListBox &lbDesc = getControl<ListBox>("LB_DESC");
|
||||
auto &lbDesc = getControl<ListBox>("LB_DESC");
|
||||
lbDesc.clearItems();
|
||||
lbDesc.addItem(move(item));
|
||||
}
|
||||
|
|
|
@ -53,6 +53,8 @@ void ComputerGUI::load() {
|
|||
|
||||
void ComputerGUI::configureMessage() {
|
||||
auto &message = getControl<ListBox>(kControlTagMessage);
|
||||
message.setProtoMatchContent(true);
|
||||
|
||||
Control &protoItem = message.protoItem();
|
||||
protoItem.setHilightColor(getHilightColor(_version));
|
||||
protoItem.setTextColor(getBaseColor(_version));
|
||||
|
@ -60,6 +62,8 @@ void ComputerGUI::configureMessage() {
|
|||
|
||||
void ComputerGUI::configureReplies() {
|
||||
auto &replies = getControl<ListBox>(kControlTagReplies);
|
||||
replies.setProtoMatchContent(true);
|
||||
|
||||
Control &protoItem = replies.protoItem();
|
||||
protoItem.setHilightColor(getHilightColor(_version));
|
||||
protoItem.setTextColor(getBaseColor(_version));
|
||||
|
|
|
@ -145,6 +145,8 @@ void DialogGUI::configureMessage() {
|
|||
|
||||
void DialogGUI::configureReplies() {
|
||||
auto &replies = getControl<ListBox>(kControlTagReplies);
|
||||
replies.setProtoMatchContent(true);
|
||||
|
||||
Control &protoItem = replies.protoItem();
|
||||
protoItem.setHilightColor(getHilightColor(_version));
|
||||
protoItem.setTextColor(getBaseColor(_version));
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "../../render/shaders.h"
|
||||
#include "../../render/stateutil.h"
|
||||
#include "../../render/textures.h"
|
||||
#include "../../render/textutil.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
#include "../gui.h"
|
||||
|
@ -165,6 +166,15 @@ void Control::loadText(const GffStruct &gffs) {
|
|||
|
||||
_text.color = gffs.getVector("COLOR");
|
||||
_text.align = static_cast<TextAlign>(gffs.getInt("ALIGNMENT", static_cast<int>(TextAlign::CenterCenter)));
|
||||
|
||||
updateTextLines();
|
||||
}
|
||||
|
||||
void Control::updateTextLines() {
|
||||
_textLines.clear();
|
||||
if (_text.font && !_text.text.empty()) {
|
||||
_textLines = breakText(_text.text, *_text.font, _extent.width);
|
||||
}
|
||||
}
|
||||
|
||||
void Control::loadHilight(const GffStruct &gffs) {
|
||||
|
@ -213,7 +223,7 @@ void Control::update(float dt) {
|
|||
}
|
||||
}
|
||||
|
||||
void Control::render(const glm::ivec2 &offset, const string &textOverride) const {
|
||||
void Control::render(const glm::ivec2 &offset, const vector<string> &text) const {
|
||||
if (!_visible) return;
|
||||
|
||||
glm::ivec2 size(_extent.width, _extent.height);
|
||||
|
@ -223,8 +233,7 @@ void Control::render(const glm::ivec2 &offset, const string &textOverride) const
|
|||
} else if (_border) {
|
||||
drawBorder(*_border, offset, size);
|
||||
}
|
||||
if (!textOverride.empty() || !_text.text.empty()) {
|
||||
string text(!textOverride.empty() ? textOverride : _text.text);
|
||||
if (!text.empty()) {
|
||||
drawText(text, offset, size);
|
||||
}
|
||||
}
|
||||
|
@ -411,56 +420,22 @@ const glm::vec3 &Control::getBorderColor() const {
|
|||
return (_focus && _hilight) ? _hilight->color : _border->color;
|
||||
}
|
||||
|
||||
void Control::drawText(const string &text, const glm::ivec2 &offset, const glm::ivec2 &size) const {
|
||||
void Control::drawText(const vector<string> &lines, const glm::ivec2 &offset, const glm::ivec2 &size) const {
|
||||
glm::ivec2 position;
|
||||
TextGravity gravity;
|
||||
glm::vec3 color((_focus && _hilight) ? _hilight->color : _text.color);
|
||||
|
||||
vector<string> lines(breakText(text, size.x));
|
||||
getTextPosition(position, static_cast<int>(lines.size()), size, gravity);
|
||||
|
||||
glm::vec3 color((_focus && _hilight) ? _hilight->color : _text.color);
|
||||
|
||||
for (auto &line : lines) {
|
||||
glm::mat4 transform(glm::translate(glm::mat4(1.0f), glm::vec3(position.x + offset.x, position.y + offset.y, 0.0f)));
|
||||
glm::mat4 transform(1.0f);
|
||||
transform = glm::translate(transform, glm::vec3(position.x + offset.x, position.y + offset.y, 0.0f));
|
||||
|
||||
_text.font->render(line, transform, color, gravity);
|
||||
position.y += static_cast<int>(_text.font->height());
|
||||
}
|
||||
}
|
||||
|
||||
vector<string> Control::breakText(const string &text, int maxWidth) const {
|
||||
vector<string> fixedLines;
|
||||
boost::split(fixedLines, text, boost::is_any_of("\n"));
|
||||
|
||||
vector<string> result;
|
||||
|
||||
for (auto &fixedLine : fixedLines) {
|
||||
vector<string> tokens;
|
||||
boost::split(tokens, fixedLine, boost::is_space(), boost::token_compress_on);
|
||||
|
||||
string buffer;
|
||||
for (auto &token : tokens) {
|
||||
string test(buffer);
|
||||
if (!test.empty()) {
|
||||
test += " ";
|
||||
}
|
||||
test += token;
|
||||
|
||||
if (_text.font->measure(test) < maxWidth) {
|
||||
buffer = move(test);
|
||||
} else {
|
||||
result.push_back(buffer);
|
||||
buffer = token;
|
||||
}
|
||||
}
|
||||
|
||||
if (!buffer.empty()) {
|
||||
boost::trim_right(buffer);
|
||||
result.push_back(move(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
return move(result);
|
||||
}
|
||||
|
||||
void Control::getTextPosition(glm::ivec2 &position, int lineCount, const glm::ivec2 &size, TextGravity &gravity) const {
|
||||
// Gravity
|
||||
switch (_text.align) {
|
||||
|
@ -571,6 +546,12 @@ void Control::setFocusable(bool focusable) {
|
|||
_focusable = focusable;
|
||||
}
|
||||
|
||||
void Control::setHeight(int height) {
|
||||
_extent.height = height;
|
||||
updateTransform();
|
||||
updateTextLines();
|
||||
}
|
||||
|
||||
void Control::setVisible(bool visible) {
|
||||
_visible = visible;
|
||||
}
|
||||
|
@ -586,6 +567,7 @@ void Control::setFocus(bool focus) {
|
|||
void Control::setExtent(const Extent &extent) {
|
||||
_extent = extent;
|
||||
updateTransform();
|
||||
updateTextLines();
|
||||
}
|
||||
|
||||
void Control::setBorder(const Border &border) {
|
||||
|
@ -638,14 +620,17 @@ void Control::setHilightColor(const glm::vec3 &color) {
|
|||
|
||||
void Control::setText(const Text &text) {
|
||||
_text = text;
|
||||
updateTextLines();
|
||||
}
|
||||
|
||||
void Control::setTextMessage(const string &text) {
|
||||
_text.text = text;
|
||||
updateTextLines();
|
||||
}
|
||||
|
||||
void Control::setTextFont(const shared_ptr<Font> &font) {
|
||||
_text.font = font;
|
||||
updateTextLines();
|
||||
}
|
||||
|
||||
void Control::setTextColor(const glm::vec3 &color) {
|
||||
|
|
|
@ -118,6 +118,7 @@ public:
|
|||
const Border &hilight() const;
|
||||
const std::string &tag() const;
|
||||
const Text &text() const;
|
||||
const std::vector<std::string> &textLines() const { return _textLines; }
|
||||
|
||||
void setBorder(const Border &border);
|
||||
void setBorderFill(const std::string &resRef);
|
||||
|
@ -129,6 +130,7 @@ public:
|
|||
virtual void setExtent(const Extent &extent);
|
||||
virtual void setFocus(bool focus);
|
||||
void setFocusable(bool focusable);
|
||||
void setHeight(int height);
|
||||
void setHilight(const Border &hilight);
|
||||
void setHilightColor(const glm::vec3 &color);
|
||||
void setPadding(int padding);
|
||||
|
@ -150,7 +152,7 @@ public:
|
|||
|
||||
// Rendering
|
||||
|
||||
virtual void render(const glm::ivec2 &offset, const std::string &textOverride = "") const;
|
||||
virtual void render(const glm::ivec2 &offset, const std::vector<std::string> &text) const;
|
||||
void render3D(const glm::ivec2 &offset) const;
|
||||
|
||||
// END Rendering
|
||||
|
@ -176,11 +178,12 @@ protected:
|
|||
glm::vec3 _discardColor { false };
|
||||
glm::vec3 _borderColorOverride { 1.0f };
|
||||
bool _useBorderColorOverride { false };
|
||||
std::vector<std::string> _textLines;
|
||||
|
||||
Control(GUI *, ControlType type);
|
||||
|
||||
void drawBorder(const Border &border, const glm::ivec2 &offset, const glm::ivec2 &size) const;
|
||||
void drawText(const std::string &text, const glm::ivec2 &offset, const glm::ivec2 &size) const;
|
||||
void drawText(const std::vector<std::string> &lines, const glm::ivec2 &offset, const glm::ivec2 &size) const;
|
||||
|
||||
virtual const glm::vec3 &getBorderColor() const;
|
||||
|
||||
|
@ -190,12 +193,13 @@ private:
|
|||
Control(const Control &) = delete;
|
||||
Control &operator=(const Control &) = delete;
|
||||
|
||||
void updateTransform();
|
||||
void loadExtent(const resource::GffStruct &gffs);
|
||||
void loadBorder(const resource::GffStruct &gffs);
|
||||
void loadText(const resource::GffStruct &gffs);
|
||||
void loadHilight(const resource::GffStruct &gffs);
|
||||
std::vector<std::string> breakText(const std::string &text, int maxWidth) const;
|
||||
|
||||
void updateTransform();
|
||||
void updateTextLines();
|
||||
|
||||
void getTextPosition(glm::ivec2 &position, int lineCount, const glm::ivec2 &size, render::TextGravity &gravity) const;
|
||||
};
|
||||
|
|
|
@ -43,7 +43,7 @@ void ImageButton::load(const GffStruct &gffs) {
|
|||
|
||||
void ImageButton::render(
|
||||
const glm::ivec2 &offset,
|
||||
const string &textOverride,
|
||||
const vector<string> &text,
|
||||
const string &iconText,
|
||||
const shared_ptr<Texture> &iconTexture,
|
||||
const shared_ptr<Texture> &iconFrame) const {
|
||||
|
@ -63,8 +63,7 @@ void ImageButton::render(
|
|||
|
||||
drawIcon(offset, iconText, iconTexture, iconFrame);
|
||||
|
||||
if (!textOverride.empty() || !_text.text.empty()) {
|
||||
string text(!textOverride.empty() ? textOverride : _text.text);
|
||||
if (!text.empty()) {
|
||||
drawText(text, borderOffset, size);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
|
||||
void render(
|
||||
const glm::ivec2 &offset,
|
||||
const std::string &textOverride,
|
||||
const std::vector<std::string> &text,
|
||||
const std::string &iconText,
|
||||
const std::shared_ptr<render::Texture> &iconTexture,
|
||||
const std::shared_ptr<render::Texture> &iconFrame) const;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../../common/log.h"
|
||||
#include "../../render/mesh/quad.h"
|
||||
#include "../../render/shaders.h"
|
||||
#include "../../render/textutil.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
#include "../gui.h"
|
||||
|
@ -43,26 +44,19 @@ ListBox::ListBox(GUI *gui) : Control(gui, ControlType::ListBox) {
|
|||
_clickable = true;
|
||||
}
|
||||
|
||||
void ListBox::updateItems() {
|
||||
if (!_protoItem) return;
|
||||
|
||||
_slotCount = _extent.height / (_protoItem->extent().height + _padding);
|
||||
|
||||
if (_scrollBar) {
|
||||
_scrollBar->setVisible(_items.size() > _slotCount);
|
||||
}
|
||||
}
|
||||
|
||||
void ListBox::clearItems() {
|
||||
_items.clear();
|
||||
_itemOffset = 0;
|
||||
_hilightedIndex = -1;
|
||||
updateItems();
|
||||
}
|
||||
|
||||
void ListBox::addItem(Item item) {
|
||||
if (!_protoItem) return;
|
||||
|
||||
item._textLines = breakText(item.text, *_protoItem->text().font, _protoItem->extent().width);
|
||||
_items.push_back(move(item));
|
||||
updateItems();
|
||||
|
||||
updateItemSlots();
|
||||
}
|
||||
|
||||
void ListBox::clearSelection() {
|
||||
|
@ -77,7 +71,6 @@ void ListBox::load(const GffStruct &gffs) {
|
|||
ControlType type = _protoItemType == ControlType::Invalid ? getType(*protoItem) : _protoItemType;
|
||||
_protoItem = of(_gui, type, getTag(*protoItem));
|
||||
_protoItem->load(*protoItem);
|
||||
updateItems();
|
||||
}
|
||||
shared_ptr<GffStruct> scrollBar(gffs.getStruct("SCROLLBAR"));
|
||||
if (scrollBar) {
|
||||
|
@ -97,23 +90,66 @@ int ListBox::getItemIndex(int y) const {
|
|||
if (!_protoItem) return -1;
|
||||
|
||||
const Control::Extent &protoExtent = _protoItem->extent();
|
||||
int idx = (y - protoExtent.top) / (protoExtent.height + _padding) + _itemOffset;
|
||||
if (protoExtent.height == 0) return -1;
|
||||
|
||||
return idx >= 0 && idx < _items.size() ? idx : -1;
|
||||
float itemy = protoExtent.top;
|
||||
if (y < itemy) return -1;
|
||||
|
||||
for (size_t i = _itemOffset; i < _items.size(); ++i) {
|
||||
const Item &item = _items[i];
|
||||
if (_protoMatchContent) {
|
||||
itemy += item._textLines.size() * _protoItem->text().font->height();
|
||||
} else {
|
||||
itemy += protoExtent.height;
|
||||
}
|
||||
itemy += _padding;
|
||||
if (y < itemy) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ListBox::handleMouseWheel(int x, int y) {
|
||||
if (y < 0) {
|
||||
if (_items.size() - _itemOffset > _slotCount) _itemOffset++;
|
||||
if (_items.size() - _itemOffset > _slotCount) {
|
||||
_itemOffset++;
|
||||
updateItemSlots();
|
||||
}
|
||||
return true;
|
||||
} else if (y > 0) {
|
||||
if (_itemOffset > 0) _itemOffset--;
|
||||
if (_itemOffset > 0) {
|
||||
_itemOffset--;
|
||||
updateItemSlots();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ListBox::updateItemSlots() {
|
||||
_slotCount = 0;
|
||||
|
||||
// Increase the number of slots until they no longer fit vertically
|
||||
float y = 0.0f;
|
||||
for (size_t i = _itemOffset; i < _items.size(); ++i) {
|
||||
if (_protoMatchContent) {
|
||||
y += _items[i]._textLines.size() * _protoItem->text().font->height();
|
||||
} else {
|
||||
y += _protoItem->extent().height;
|
||||
}
|
||||
y += _padding;
|
||||
|
||||
if (y > _extent.height) break;
|
||||
|
||||
++_slotCount;
|
||||
}
|
||||
|
||||
if (_scrollBar) {
|
||||
_scrollBar->setVisible(_items.size() > _slotCount);
|
||||
}
|
||||
}
|
||||
|
||||
bool ListBox::handleClick(int x, int y) {
|
||||
int itemIdx = getItemIndex(y);
|
||||
if (itemIdx == -1) return false;
|
||||
|
@ -130,37 +166,44 @@ bool ListBox::handleClick(int x, int y) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void ListBox::render(const glm::ivec2 &offset, const string &textOverride) const {
|
||||
void ListBox::render(const glm::ivec2 &offset, const vector<string> &text) const {
|
||||
if (!_visible) return;
|
||||
|
||||
Control::render(offset, textOverride);
|
||||
Control::render(offset, text);
|
||||
|
||||
if (!_protoItem) return;
|
||||
|
||||
glm::vec2 itemOffset(offset);
|
||||
const Control::Extent &protoExtent = _protoItem->extent();
|
||||
|
||||
for (int i = 0; i < _slotCount; ++i) {
|
||||
int itemIdx = i + _itemOffset;
|
||||
if (itemIdx >= _items.size()) break;
|
||||
|
||||
const Item &item = _items[itemIdx];
|
||||
if (_protoMatchContent) {
|
||||
_protoItem->setHeight(item._textLines.size() * (_protoItem->text().font->height() + _padding));
|
||||
}
|
||||
_protoItem->setFocus(_hilightedIndex == itemIdx);
|
||||
|
||||
|
||||
auto imageButton = dynamic_pointer_cast<ImageButton>(_protoItem);
|
||||
if (imageButton) {
|
||||
imageButton->render(itemOffset, _items[itemIdx].text, _items[itemIdx].iconText, _items[itemIdx].iconTexture, _items[itemIdx].iconFrame);
|
||||
imageButton->render(itemOffset, item._textLines, item.iconText, item.iconTexture, item.iconFrame);
|
||||
} else {
|
||||
_protoItem->render(itemOffset, _items[itemIdx].text);
|
||||
_protoItem->render(itemOffset, item._textLines);
|
||||
}
|
||||
|
||||
itemOffset.y += protoExtent.height + _padding;
|
||||
if (_protoMatchContent) {
|
||||
itemOffset.y += item._textLines.size() * (_protoItem->text().font->height() + _padding);
|
||||
} else {
|
||||
itemOffset.y += _protoItem->extent().height + _padding;
|
||||
}
|
||||
}
|
||||
|
||||
if (_scrollBar) {
|
||||
auto &scrollBar = static_cast<ScrollBar &>(*_scrollBar);
|
||||
scrollBar.setCanScrollUp(_itemOffset > 0);
|
||||
scrollBar.setCanScrollDown(_items.size() - _itemOffset > _slotCount);
|
||||
scrollBar.render(offset, textOverride);
|
||||
scrollBar.render(offset, vector<string>());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +229,7 @@ void ListBox::setFocus(bool focus) {
|
|||
|
||||
void ListBox::setExtent(const Extent &extent) {
|
||||
Control::setExtent(extent);
|
||||
updateItems();
|
||||
updateItemSlots();
|
||||
}
|
||||
|
||||
void ListBox::setProtoItemType(ControlType type) {
|
||||
|
@ -197,6 +240,10 @@ void ListBox::setSelectionMode(SelectionMode mode) {
|
|||
_mode = mode;
|
||||
}
|
||||
|
||||
void ListBox::setProtoMatchContent(bool match) {
|
||||
_protoMatchContent = match;
|
||||
}
|
||||
|
||||
const ListBox::Item &ListBox::getItemAt(int index) const {
|
||||
return _items[index];
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ public:
|
|||
std::string iconText;
|
||||
std::shared_ptr<render::Texture> iconTexture;
|
||||
std::shared_ptr<render::Texture> iconFrame;
|
||||
|
||||
std::vector<std::string> _textLines;
|
||||
};
|
||||
|
||||
ListBox(GUI *gui);
|
||||
|
@ -53,13 +55,14 @@ public:
|
|||
bool handleMouseMotion(int x, int y) override;
|
||||
bool handleMouseWheel(int x, int y) override;
|
||||
bool handleClick(int x, int y) override;
|
||||
void render(const glm::ivec2 &offset, const std::string &textOverride) const override;
|
||||
void render(const glm::ivec2 &offset, const std::vector<std::string> &text) const override;
|
||||
void stretch(float x, float y, int mask) override;
|
||||
|
||||
void setFocus(bool focus) override;
|
||||
void setExtent(const Extent &extent) override;
|
||||
void setProtoItemType(ControlType type);
|
||||
void setSelectionMode(SelectionMode mode);
|
||||
void setProtoMatchContent(bool match);
|
||||
|
||||
const Item &getItemAt(int index) const;
|
||||
|
||||
|
@ -78,8 +81,10 @@ private:
|
|||
int _itemOffset { 0 };
|
||||
int _hilightedIndex { -1 };
|
||||
int _itemMargin { 0 };
|
||||
bool _protoMatchContent { false }; /**< proto item height must match its content */
|
||||
|
||||
void updateItemSlots();
|
||||
|
||||
void updateItems();
|
||||
int getItemIndex(int y) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ void ScrollBar::load(const GffStruct &gffs) {
|
|||
}
|
||||
}
|
||||
|
||||
void ScrollBar::render(const glm::ivec2 &offset, const string &textOverride) const {
|
||||
void ScrollBar::render(const glm::ivec2 &offset, const vector<string> &text) const {
|
||||
if (!_dir.image) return;
|
||||
|
||||
setActiveTextureUnit(0);
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
ScrollBar(GUI *gui);
|
||||
|
||||
void load(const resource::GffStruct &gffs) override;
|
||||
void render(const glm::ivec2 &offset, const std::string &textOverride) const override;
|
||||
void render(const glm::ivec2 &offset, const std::vector<std::string> &text) const override;
|
||||
|
||||
void setCanScrollUp(bool scroll);
|
||||
void setCanScrollDown(bool scroll);
|
||||
|
|
|
@ -284,10 +284,10 @@ void GUI::update(float dt) {
|
|||
|
||||
void GUI::render() const {
|
||||
if (_background) drawBackground();
|
||||
if (_rootControl) _rootControl->render(_rootOffset);
|
||||
if (_rootControl) _rootControl->render(_rootOffset, _rootControl->textLines());
|
||||
|
||||
for (auto &control : _controls) {
|
||||
control->render(_controlOffset);
|
||||
control->render(_controlOffset, control->textLines());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
96
src/render/textutil.cpp
Normal file
96
src/render/textutil.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "textutil.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "font.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
vector<string> breakText(const string &text, Font &font, int maxWidth) {
|
||||
vector<string> result;
|
||||
string line;
|
||||
ostringstream tokenBuffer;
|
||||
|
||||
for (char ch : text) {
|
||||
switch (ch) {
|
||||
case '\n': {
|
||||
string token(tokenBuffer.str());
|
||||
if (!token.empty()) {
|
||||
tokenBuffer.str("");
|
||||
if (!line.empty()) {
|
||||
line += " ";
|
||||
}
|
||||
line += token;
|
||||
}
|
||||
if (!line.empty()) {
|
||||
result.push_back(line);
|
||||
line = "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ' ': {
|
||||
string token(tokenBuffer.str());
|
||||
if (!token.empty()) {
|
||||
tokenBuffer.str("");
|
||||
|
||||
string test(line);
|
||||
if (!test.empty()) {
|
||||
test += " ";
|
||||
}
|
||||
test += token;
|
||||
|
||||
if (font.measure(test) <= maxWidth) {
|
||||
line = move(test);
|
||||
} else {
|
||||
result.push_back(line);
|
||||
line = move(token);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
tokenBuffer.put(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string token(tokenBuffer.str());
|
||||
if (!token.empty()) {
|
||||
if (!line.empty()) {
|
||||
line += " ";
|
||||
}
|
||||
line += token;
|
||||
}
|
||||
if (!line.empty()) {
|
||||
result.push_back(move(line));
|
||||
}
|
||||
|
||||
return move(result);
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
37
src/render/textutil.h
Normal file
37
src/render/textutil.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
class Font;
|
||||
|
||||
/**
|
||||
* Breaks the specified text into multiple strings to not exceed the maximum
|
||||
* width, when rendering using the specified font.
|
||||
*/
|
||||
std::vector<std::string> breakText(const std::string &text, Font &font, int maxWidth);
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
Loading…
Reference in a new issue