summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2024-02-09 14:40:35 +0100
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2024-02-09 17:14:25 +0100
commita97602889753f596e9738104bc0f9b6ab424f7a7 (patch)
tree31008b27c0e73fc362848b3b66af673cc6721c68
parentb5e416f8cd52f69ff1edd832ee10bac550544ef6 (diff)
downloadmana-a97602889753f596e9738104bc0f9b6ab424f7a7.tar.gz
mana-a97602889753f596e9738104bc0f9b6ab424f7a7.tar.bz2
mana-a97602889753f596e9738104bc0f9b6ab424f7a7.tar.xz
mana-a97602889753f596e9738104bc0f9b6ab424f7a7.zip
Optimized BrowserBox
* Introduced a LayoutContext that conveniently allows for relayouting all rows, or just a single one when it is added. BrowserBox::addRow no longer relayouts all the rows. * BrowserLink and LinePart are now merged into a new TextRow struct, so they can be conveniently dropped when the row limit has been reached. * Removed "opaque" option, which was enabled by default but disabled for all BrowserBox instances. * Removed "always update" option, and instead start delaying relayouting automatically when there are a lot of rows (> 100 currently). * Update window now also has text wrapping enabled. Closes #50
-rw-r--r--src/gui/helpwindow.cpp1
-rw-r--r--src/gui/popupmenu.cpp1
-rw-r--r--src/gui/setup_colors.cpp4
-rw-r--r--src/gui/socialwindow.cpp1
-rw-r--r--src/gui/updaterwindow.cpp4
-rw-r--r--src/gui/widgets/browserbox.cpp606
-rw-r--r--src/gui/widgets/browserbox.h54
-rw-r--r--src/gui/widgets/chattab.cpp6
-rw-r--r--src/gui/widgets/itemlinkhandler.h1
9 files changed, 327 insertions, 351 deletions
diff --git a/src/gui/helpwindow.cpp b/src/gui/helpwindow.cpp
index 35e65bd6..3a528f27 100644
--- a/src/gui/helpwindow.cpp
+++ b/src/gui/helpwindow.cpp
@@ -47,7 +47,6 @@ HelpWindow::HelpWindow():
setDefaultSize(500, 400, ImageRect::CENTER);
mBrowserBox = new BrowserBox;
- mBrowserBox->setOpaque(false);
mScrollArea = new ScrollArea(mBrowserBox);
auto *okButton = new Button(_("Close"), "close", this);
diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp
index d7a34005..4814937e 100644
--- a/src/gui/popupmenu.cpp
+++ b/src/gui/popupmenu.cpp
@@ -60,7 +60,6 @@ PopupMenu::PopupMenu():
mBrowserBox = new BrowserBox;
mBrowserBox->setPosition(4, 4);
mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
- mBrowserBox->setOpaque(false);
mBrowserBox->setLinkHandler(this);
add(mBrowserBox);
}
diff --git a/src/gui/setup_colors.cpp b/src/gui/setup_colors.cpp
index bf86a93b..9862ee11 100644
--- a/src/gui/setup_colors.cpp
+++ b/src/gui/setup_colors.cpp
@@ -60,10 +60,6 @@ Setup_Colors::Setup_Colors() :
mTextPreview = new TextPreview(rawmsg);
mPreview = new BrowserBox(BrowserBox::AUTO_WRAP);
- mPreview->setOpaque(false);
-
- // don't do anything with links
- mPreview->setLinkHandler(nullptr);
mPreviewBox = new ScrollArea(mPreview);
mPreviewBox->setHeight(20);
diff --git a/src/gui/socialwindow.cpp b/src/gui/socialwindow.cpp
index f7af65c2..5a1e8989 100644
--- a/src/gui/socialwindow.cpp
+++ b/src/gui/socialwindow.cpp
@@ -259,7 +259,6 @@ public:
mBrowserBox = new BrowserBox;
mBrowserBox->setPosition(4, 4);
mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND);
- mBrowserBox->setOpaque(false);
mBrowserBox->setLinkHandler(this);
if (Net::getGuildHandler()->isSupported())
diff --git a/src/gui/updaterwindow.cpp b/src/gui/updaterwindow.cpp
index b26119e6..728b2464 100644
--- a/src/gui/updaterwindow.cpp
+++ b/src/gui/updaterwindow.cpp
@@ -131,7 +131,7 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost,
setMinWidth(320);
setMinHeight(240);
- mBrowserBox = new BrowserBox;
+ mBrowserBox = new BrowserBox(BrowserBox::AUTO_WRAP);
mScrollArea = new ScrollArea(mBrowserBox);
mLabel = new Label(_("Connecting..."));
mProgressBar = new ProgressBar(0.0, 310, 20);
@@ -139,7 +139,7 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost,
mPlayButton = new Button(_("Play"), "play", this);
mProgressBar->setSmoothProgress(false);
- mBrowserBox->setOpaque(false);
+ mScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
mPlayButton->setEnabled(false);
place(0, 0, mScrollArea, 5, 3).setPadding(3);
diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp
index 7b5d40bb..d12562f1 100644
--- a/src/gui/widgets/browserbox.cpp
+++ b/src/gui/widgets/browserbox.cpp
@@ -35,467 +35,449 @@
#include <algorithm>
-BrowserBox::BrowserBox(unsigned int mode, bool opaque):
- mMode(mode),
- mOpaque(opaque)
+struct LayoutContext
{
- setFocusable(true);
- addMouseListener(this);
-}
+ LayoutContext(const gcn::Font *font);
-BrowserBox::~BrowserBox()
-{
-}
+ int y = 0;
+ const gcn::Font *font;
+ const int fontHeight;
+ const int minusWidth;
+ const int tildeWidth;
+ int lineHeight;
+ gcn::Color selColor;
+ const gcn::Color textColor;
+};
-void BrowserBox::setLinkHandler(LinkHandler *linkHandler)
+LayoutContext::LayoutContext(const gcn::Font *font)
+ : font(font)
+ , fontHeight(font->getHeight())
+ , minusWidth(font->getWidth("-"))
+ , tildeWidth(font->getWidth("~"))
+ , lineHeight(fontHeight)
+ , selColor(Theme::getThemeColor(Theme::TEXT))
+ , textColor(Theme::getThemeColor(Theme::TEXT))
{
- mLinkHandler = linkHandler;
+ if (auto *trueTypeFont = dynamic_cast<const TrueTypeFont*>(font))
+ lineHeight = trueTypeFont->getLineHeight();
}
-void BrowserBox::setOpaque(bool opaque)
-{
- mOpaque = opaque;
-}
-void BrowserBox::setHighlightMode(unsigned int highMode)
+BrowserBox::BrowserBox(unsigned int mode):
+ mMode(mode)
{
- mHighMode = highMode;
+ setFocusable(true);
+ addMouseListener(this);
}
-void BrowserBox::disableLinksAndUserColors()
+BrowserBox::~BrowserBox()
{
- mUseLinksAndUserColors = false;
}
void BrowserBox::addRow(const std::string &row)
{
- std::string newRow;
-
- gcn::Font *font = getFont();
- const int fontHeight = font->getHeight();
-
- int lineHeight = fontHeight;
- if (auto *ttf = dynamic_cast<TrueTypeFont*>(font))
- lineHeight = ttf->getLineHeight();
+ TextRow &newRow = mTextRows.emplace_back();
// Use links and user defined colors
if (mUseLinksAndUserColors)
{
- BrowserLink bLink;
std::string tmp = row;
- std::string::size_type idx1, idx2, idx3;
// Check for links in format "@@link|Caption@@"
- idx1 = tmp.find("@@");
+ auto idx1 = tmp.find("@@");
while (idx1 != std::string::npos)
{
- idx2 = tmp.find("|", idx1);
- idx3 = tmp.find("@@", idx2);
+ const auto idx2 = tmp.find("|", idx1);
+ const auto idx3 = tmp.find("@@", idx2);
if (idx2 == std::string::npos || idx3 == std::string::npos)
break;
- bLink.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2));
- bLink.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1));
- bLink.y1 = static_cast<int>(mTextRows.size()) * lineHeight;
- bLink.y2 = bLink.y1 + fontHeight;
- newRow += tmp.substr(0, idx1);
+ BrowserLink &link = newRow.links.emplace_back();
+ link.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2));
+ link.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1));
- std::string tmp2 = newRow;
- idx1 = tmp2.find("##");
- while (idx1 != std::string::npos)
- {
- tmp2.erase(idx1, 3);
- idx1 = tmp2.find("##");
- }
- bLink.x1 = font->getWidth(tmp2) - 1;
- bLink.x2 = bLink.x1 + font->getWidth(bLink.caption) + 1;
-
- mLinks.push_back(bLink);
-
- newRow += "##<" + bLink.caption;
+ newRow.text += tmp.substr(0, idx1);
+ newRow.text += "##<" + link.caption;
tmp.erase(0, idx3 + 2);
if (!tmp.empty())
{
- newRow += "##>";
+ newRow.text += "##>";
}
idx1 = tmp.find("@@");
}
- newRow += tmp;
+ newRow.text += tmp;
}
// Don't use links and user defined colors
else
{
- newRow = row;
+ newRow.text = row;
}
- mTextRows.push_back(newRow);
+ // Layout the newly added row
+ LayoutContext context(getFont());
+ context.y = getHeight();
+ layoutTextRow(newRow, context);
+
+ // Auto size mode
+ if (mMode == AUTO_SIZE && newRow.width > getWidth())
+ setWidth(newRow.width);
- //discard older rows when a row limit has been set
- if (mMaxRows > 0)
+ // Discard older rows when a row limit has been set
+ // (this might invalidate the newRow reference)
+ int removedHeight = 0;
+ while (mMaxRows > 0 && mTextRows.size() > mMaxRows)
{
- while (mTextRows.size() > mMaxRows)
+ removedHeight += mTextRows.front().height;
+ mTextRows.pop_front();
+ }
+ if (removedHeight > 0)
+ {
+ for (auto &row : mTextRows)
{
- mTextRows.pop_front();
- for (unsigned int i = 0; i < mLinks.size(); i++)
+ for (auto &part : row.parts)
{
- mLinks[i].y1 -= lineHeight;
- mLinks[i].y2 -= lineHeight;
+ part.y -= removedHeight;
+ }
- if (mLinks[i].y1 < 0)
- mLinks.erase(mLinks.begin() + i);
+ for (auto &link : row.links)
+ {
+ link.y1 -= removedHeight;
+ link.y2 -= removedHeight;
}
}
}
- // Auto size mode
- if (mMode == AUTO_SIZE)
- {
- std::string plain = newRow;
- std::string::size_type index;
- while ((index = plain.find("##")) != std::string::npos)
- plain.erase(index, 3);
-
- // Adjust the BrowserBox size
- int w = font->getWidth(plain);
- if (w > getWidth())
- setWidth(w);
- }
-
- mUpdateTime = 0;
- maybeRelayoutText();
+ setHeight(context.y - removedHeight);
}
void BrowserBox::clearRows()
{
mTextRows.clear();
- mLinks.clear();
- setWidth(0);
- setHeight(0);
- mSelectedLink = -1;
+ setSize(0, 0);
+ mHoveredLink.reset();
maybeRelayoutText();
}
-struct MouseOverLink
-{
- MouseOverLink(int x, int y)
- : mX(x), mY(y)
- {}
-
- bool operator() (BrowserLink &link) const
- {
- return (mX >= link.x1 && mX < link.x2 &&
- mY >= link.y1 && mY < link.y2);
- }
-
- int mX, mY;
-};
-
void BrowserBox::mousePressed(gcn::MouseEvent &event)
{
if (!mLinkHandler)
return;
- auto i = find_if(mLinks.begin(), mLinks.end(),
- MouseOverLink(event.getX(), event.getY()));
+ updateHoveredLink(event.getX(), event.getY());
- if (i != mLinks.end())
- mLinkHandler->handleLink(i->link);
+ if (mHoveredLink)
+ mLinkHandler->handleLink(mHoveredLink->link);
}
void BrowserBox::mouseMoved(gcn::MouseEvent &event)
{
- auto i = find_if(mLinks.begin(), mLinks.end(),
- MouseOverLink(event.getX(), event.getY()));
-
- mSelectedLink = (i != mLinks.end())
- ? static_cast<int>(i - mLinks.begin()) : -1;
+ updateHoveredLink(event.getX(), event.getY());
}
void BrowserBox::draw(gcn::Graphics *graphics)
{
const gcn::ClipRectangle &cr = graphics->getCurrentClipArea();
- mYStart = cr.y - cr.yOffset;
- int yEnd = mYStart + cr.height;
- if (mYStart < 0)
- mYStart = 0;
+ int yStart = cr.y - cr.yOffset;
+ int yEnd = yStart + cr.height;
+ if (yStart < 0)
+ yStart = 0;
if (getWidth() != mLastLayoutWidth)
maybeRelayoutText();
- if (mOpaque)
+ if (mHoveredLink)
{
- graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND));
- graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight()));
- }
+ auto &link = *mHoveredLink;
- if (mSelectedLink >= 0 && (unsigned) mSelectedLink < mLinks.size())
- {
- if ((mHighMode & BACKGROUND))
+ if (mHighlightMode & BACKGROUND)
{
graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT));
graphics->fillRectangle(gcn::Rectangle(
- mLinks[mSelectedLink].x1,
- mLinks[mSelectedLink].y1,
- mLinks[mSelectedLink].x2 - mLinks[mSelectedLink].x1,
- mLinks[mSelectedLink].y2 - mLinks[mSelectedLink].y1
+ link.x1,
+ link.y1,
+ link.x2 - link.x1,
+ link.y2 - link.y1
));
}
- if ((mHighMode & UNDERLINE))
+ if (mHighlightMode & UNDERLINE)
{
graphics->setColor(Theme::getThemeColor(Theme::HYPERLINK));
graphics->drawLine(
- mLinks[mSelectedLink].x1,
- mLinks[mSelectedLink].y2,
- mLinks[mSelectedLink].x2,
- mLinks[mSelectedLink].y2);
+ link.x1,
+ link.y2,
+ link.x2,
+ link.y2);
}
}
- for (const auto &part : mLineParts)
+ for (const auto &row : mTextRows)
{
- if (part.y + 50 < mYStart)
- continue;
- if (part.y > yEnd)
- break;
+ for (const auto &part : row.parts)
+ {
+ if (part.y + 50 < yStart)
+ continue;
+ if (part.y > yEnd)
+ return;
- // Use the correct font
- graphics->setFont(getFont());
+ // Use the correct font
+ graphics->setFont(getFont());
- // Handle text shadows
- if (mShadows)
- {
- graphics->setColor(Theme::getThemeColor(Theme::SHADOW,
- part.color.a / 2));
+ // Handle text shadows
+ if (mShadows)
+ {
+ graphics->setColor(Theme::getThemeColor(Theme::SHADOW,
+ part.color.a / 2));
+
+ if (mOutline)
+ graphics->drawText(part.text, part.x + 2, part.y + 2);
+ else
+ graphics->drawText(part.text, part.x + 1, part.y + 1);
+ }
if (mOutline)
- graphics->drawText(part.text, part.x + 2, part.y + 2);
- else
- graphics->drawText(part.text, part.x + 1, part.y + 1);
- }
+ {
+ // Text outline
+ graphics->setColor(Theme::getThemeColor(Theme::OUTLINE,
+ part.color.a / 4));
+ graphics->drawText(part.text, part.x + 1, part.y);
+ graphics->drawText(part.text, part.x - 1, part.y);
+ graphics->drawText(part.text, part.x, part.y + 1);
+ graphics->drawText(part.text, part.x, part.y - 1);
+ }
- if (mOutline)
- {
- // Text outline
- graphics->setColor(Theme::getThemeColor(Theme::OUTLINE,
- part.color.a / 4));
- graphics->drawText(part.text, part.x + 1, part.y);
- graphics->drawText(part.text, part.x - 1, part.y);
- graphics->drawText(part.text, part.x, part.y + 1);
- graphics->drawText(part.text, part.x, part.y - 1);
+ // the main text
+ graphics->setColor(part.color);
+ graphics->drawText(part.text, part.x, part.y);
}
-
- // the main text
- graphics->setColor(part.color);
- graphics->drawText(part.text, part.x, part.y);
}
}
/**
- * Relayouts all text rows.
+ * Relayouts all text rows and returns the new height of the BrowserBox.
*/
void BrowserBox::relayoutText()
{
- int y = 0;
- unsigned link = 0;
- const gcn::Font *font = getFont();
+ LayoutContext context(getFont());
- const int fontHeight = font->getHeight();
- const int minusWidth = font->getWidth("-");
- const int tildeWidth = font->getWidth("~");
+ for (auto &row : mTextRows)
+ layoutTextRow(row, context);
- int lineHeight = fontHeight;
- if (auto *trueTypeFont = dynamic_cast<const TrueTypeFont*>(font))
- lineHeight = trueTypeFont->getLineHeight();
+ mLastLayoutWidth = getWidth();
+ mLastLayoutTime = tick_time;
+ setHeight(context.y);
+}
- gcn::Color selColor = Theme::getThemeColor(Theme::TEXT);
- const gcn::Color &textColor = Theme::getThemeColor(Theme::TEXT);
+/**
+ * Layers out the given \a row of text starting at the given \a context position.
+ * @return the context position for the next row.
+ */
+void BrowserBox::layoutTextRow(TextRow &row, LayoutContext &context)
+{
+ const int startY = context.y;
+ row.parts.clear();
- mLineParts.clear();
+ unsigned linkIndex = 0;
+ bool wrapped = false;
+ int x = 0;
- for (const auto &row : mTextRows)
+ // Check for separator lines
+ if (row.text.find("---", 0) == 0)
{
- bool wrapped = false;
- int x = 0;
-
- // Check for separator lines
- if (row.find("---", 0) == 0)
+ for (x = 0; x < getWidth(); x += context.minusWidth - 1)
{
- for (x = 0; x < getWidth(); x++)
- {
- mLineParts.push_back(LinePart { x, y, selColor, "-" });
- x += minusWidth - 2;
- }
-
- y += lineHeight;
- continue;
+ row.parts.push_back(LinePart { x, context.y, context.selColor, "-" });
}
- gcn::Color prevColor = selColor;
+ context.y += row.height;
- // TODO: Check if we must take texture size limits into account here
- // TODO: Check if some of the O(n) calls can be removed
- for (std::string::size_type start = 0, end = std::string::npos;
- start != std::string::npos;
- start = end, end = std::string::npos)
+ row.width = getWidth();
+ row.height = context.y - startY;
+ return;
+ }
+
+ gcn::Color prevColor = context.selColor;
+
+ // TODO: Check if we must take texture size limits into account here
+ // TODO: Check if some of the O(n) calls can be removed
+ for (std::string::size_type start = 0, end = std::string::npos;
+ start != std::string::npos;
+ start = end, end = std::string::npos)
+ {
+ // Wrapped line continuation shall be indented
+ if (wrapped)
{
- // Wrapped line continuation shall be indented
- if (wrapped)
- {
- y += lineHeight;
- x = 15;
- wrapped = false;
- }
+ context.y += context.lineHeight;
+ x = 15;
+ wrapped = false;
+ }
- // "Tokenize" the string at control sequences
- if (mUseLinksAndUserColors)
- end = row.find("##", start + 1);
+ // "Tokenize" the string at control sequences
+ if (mUseLinksAndUserColors)
+ end = row.text.find("##", start + 1);
- if (mUseLinksAndUserColors ||
- (!mUseLinksAndUserColors && (start == 0)))
+ if (mUseLinksAndUserColors ||
+ (!mUseLinksAndUserColors && (start == 0)))
+ {
+ // Check for color change in format "##x", x = [L,P,0..9]
+ if (row.text.find("##", start) == start && row.text.size() > start + 2)
{
- // Check for color change in format "##x", x = [L,P,0..9]
- if (row.find("##", start) == start && row.size() > start + 2)
- {
- const char c = row.at(start + 2);
+ const char c = row.text.at(start + 2);
- bool valid;
- const gcn::Color col = Theme::getThemeColor(c, valid);
+ bool valid;
+ const gcn::Color col = Theme::getThemeColor(c, valid);
- if (c == '>')
- {
- selColor = prevColor;
- }
- else if (c == '<')
- {
- prevColor = selColor;
- selColor = col;
- }
- else if (valid)
- {
- selColor = col;
- }
- else
+ if (c == '>')
+ {
+ context.selColor = prevColor;
+ }
+ else if (c == '<')
+ {
+ prevColor = context.selColor;
+ context.selColor = col;
+ }
+ else if (valid)
+ {
+ context.selColor = col;
+ }
+ else
+ {
+ switch (c)
{
- switch (c)
- {
- case '1': selColor = RED; break;
- case '2': selColor = GREEN; break;
- case '3': selColor = BLUE; break;
- case '4': selColor = ORANGE; break;
- case '5': selColor = YELLOW; break;
- case '6': selColor = PINK; break;
- case '7': selColor = PURPLE; break;
- case '8': selColor = GRAY; break;
- case '9': selColor = BROWN; break;
- case '0':
- default:
- selColor = textColor;
- }
+ case '1': context.selColor = RED; break;
+ case '2': context.selColor = GREEN; break;
+ case '3': context.selColor = BLUE; break;
+ case '4': context.selColor = ORANGE; break;
+ case '5': context.selColor = YELLOW; break;
+ case '6': context.selColor = PINK; break;
+ case '7': context.selColor = PURPLE; break;
+ case '8': context.selColor = GRAY; break;
+ case '9': context.selColor = BROWN; break;
+ case '0':
+ default:
+ context.selColor = context.textColor;
}
+ }
- // Update the position of the links
- if (c == '<' && link < mLinks.size())
- {
- const int size =
- font->getWidth(mLinks[link].caption) + 1;
-
- mLinks[link].x1 = x;
- mLinks[link].y1 = y;
- mLinks[link].x2 = mLinks[link].x1 + size;
- mLinks[link].y2 = y + fontHeight - 1;
- link++;
- }
- start += 3;
+ // Update the position of the links
+ if (c == '<' && linkIndex < row.links.size())
+ {
+ auto &link = row.links[linkIndex];
+ const int size = context.font->getWidth(link.caption) + 1;
+
+ link.x1 = x;
+ link.y1 = context.y;
+ link.x2 = link.x1 + size;
+ link.y2 = context.y + context.fontHeight - 1;
- if (start == row.size())
- break;
+ linkIndex++;
}
+ start += 3;
+
+ if (start == row.text.size())
+ break;
}
+ }
- if (start >= row.length())
- break;
+ if (start >= row.text.length())
+ break;
- std::string::size_type len =
- end == std::string::npos ? end : end - start;
+ std::string::size_type len =
+ end == std::string::npos ? end : end - start;
- std::string part = row.substr(start, len);
+ std::string part = row.text.substr(start, len);
+
+ // Auto wrap mode
+ if (mMode == AUTO_WRAP && getWidth() > 0
+ && context.font->getWidth(part) > 0
+ && (x + context.font->getWidth(part) + 10) > getWidth())
+ {
+ bool forced = false;
- // Auto wrap mode
- if (mMode == AUTO_WRAP && getWidth() > 0
- && font->getWidth(part) > 0
- && (x + font->getWidth(part) + 10) > getWidth())
+ /* FIXME: This code layout makes it easy to crash remote
+ clients by talking garbage. Forged long utf-8 characters
+ will cause either a buffer underflow in substr or an
+ infinite loop in the main loop. */
+ do
{
- bool forced = false;
+ if (!forced)
+ end = row.text.rfind(' ', end);
- /* FIXME: This code layout makes it easy to crash remote
- clients by talking garbage. Forged long utf-8 characters
- will cause either a buffer underflow in substr or an
- infinite loop in the main loop. */
- do
+ // Check if we have to (stupidly) force-wrap
+ if (end == std::string::npos || end <= start)
{
- if (!forced)
- end = row.rfind(' ', end);
-
- // Check if we have to (stupidly) force-wrap
- if (end == std::string::npos || end <= start)
- {
- forced = true;
- end = row.size();
- x += tildeWidth; // Account for the wrap-notifier
- continue;
- }
-
- // Skip to the start of the current character
- while ((row[end] & 192) == 128)
- end--;
- end--; // And then to the last byte of the previous one
-
- part = row.substr(start, end - start + 1);
+ forced = true;
+ end = row.text.size();
+ x += context.tildeWidth; // Account for the wrap-notifier
+ continue;
}
- while (end > start && font->getWidth(part) > 0
- && (x + font->getWidth(part) + 10) > getWidth());
- if (forced)
- {
- x -= tildeWidth; // Remove the wrap-notifier accounting
- mLineParts.push_back(LinePart { getWidth() - tildeWidth,
- y, selColor, "~" });
- end++; // Skip to the next character
- }
- else
- {
- end += 2; // Skip to after the space
- }
+ // Skip to the start of the current character
+ while ((row.text[end] & 192) == 128)
+ end--;
+ end--; // And then to the last byte of the previous one
- wrapped = true;
+ part = row.text.substr(start, end - start + 1);
}
+ while (end > start && context.font->getWidth(part) > 0
+ && (x + context.font->getWidth(part) + 10) > getWidth());
- mLineParts.push_back(LinePart { x, y, selColor, part });
-
- const int partWidth = font->getWidth(part);
- if (mMode == AUTO_WRAP && partWidth == 0)
- break;
+ if (forced)
+ {
+ x -= context.tildeWidth; // Remove the wrap-notifier accounting
+ row.parts.push_back(LinePart { getWidth() - context.tildeWidth,
+ context.y, context.selColor, "~" });
+ end++; // Skip to the next character
+ }
+ else
+ {
+ end += 2; // Skip to after the space
+ }
- x += partWidth;
+ wrapped = true;
}
- y += lineHeight;
+ row.parts.push_back(LinePart { x, context.y, context.selColor, part });
+
+ const int partWidth = context.font->getWidth(part);
+ row.width = std::max(row.width, x + partWidth);
+
+ if (mMode == AUTO_WRAP && partWidth == 0)
+ break;
+
+ x += partWidth;
}
- mLastLayoutWidth = getWidth();
- setHeight(y);
+ context.y += context.lineHeight;
+ row.height = context.y - startY;
}
-void BrowserBox::maybeRelayoutText()
+void BrowserBox::updateHoveredLink(int x, int y)
{
- if (mAlwaysUpdate || !mUpdateTime || std::abs(mUpdateTime - tick_time) > 10
- || mTextRows.size() < 3)
+ mHoveredLink.reset();
+
+ for (const auto &row : mTextRows)
{
- relayoutText();
- mUpdateTime = tick_time;
+ for (const auto &link : row.links)
+ {
+ if (link.contains(x, y))
+ {
+ mHoveredLink = link;
+ return;
+ }
+ }
}
}
+
+void BrowserBox::maybeRelayoutText()
+{
+ // Reduce relayouting frequency when there is a lot of text
+ if (mTextRows.size() > 100)
+ if (mLastLayoutTime && std::abs(mLastLayoutTime - tick_time) < 10)
+ return;
+
+ relayoutText();
+}
diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h
index 83cdef7c..11b391e0 100644
--- a/src/gui/widgets/browserbox.h
+++ b/src/gui/widgets/browserbox.h
@@ -27,15 +27,22 @@
#include <guichan/widget.hpp>
#include <deque>
+#include <optional>
#include <vector>
class LinkHandler;
+struct LayoutContext;
struct BrowserLink
{
- int x1, x2, y1, y2; /**< Where link is placed */
+ int x1 = 0, x2 = 0, y1 = 0, y2 = 0; /**< Where link is placed */
std::string link;
std::string caption;
+
+ bool contains(int x, int y) const
+ {
+ return x >= x1 && x < x2 && y >= y1 && y < y2;
+ }
};
struct LinePart
@@ -46,6 +53,15 @@ struct LinePart
std::string text;
};
+struct TextRow
+{
+ std::string text;
+ std::vector<LinePart> parts;
+ std::vector<BrowserLink> links;
+ int width = 0;
+ int height = 0;
+};
+
/**
* A simple browser box able to handle links and forward events to the
* parent conteiner.
@@ -54,24 +70,18 @@ class BrowserBox : public gcn::Widget,
public gcn::MouseListener
{
public:
- BrowserBox(unsigned int mode = AUTO_SIZE, bool opaque = true);
-
+ BrowserBox(unsigned int mode = AUTO_SIZE);
~BrowserBox() override;
/**
* Sets the handler for links.
*/
- void setLinkHandler(LinkHandler *linkHandler);
-
- /**
- * Sets the BrowserBox opacity.
- */
- void setOpaque(bool opaque);
+ void setLinkHandler(LinkHandler *handler) { mLinkHandler = handler; }
/**
* Sets the Highlight mode for links.
*/
- void setHighlightMode(unsigned int highMode);
+ void setHighlightMode(unsigned int mode) { mHighlightMode = mode; }
/**
* Sets whether the font will use a shadow for text.
@@ -86,12 +96,12 @@ class BrowserBox : public gcn::Widget,
/**
* Sets the maximum numbers of rows in the browser box. 0 = no limit.
*/
- void setMaxRow(unsigned max) {mMaxRows = max; }
+ void setMaxRows(unsigned maxRows) { mMaxRows = maxRows; }
/**
* Disable links & user defined colors to be used in chat input.
*/
- void disableLinksAndUserColors();
+ void disableLinksAndUserColors() { mUseLinksAndUserColors = false; }
/**
* Adds a text row to the browser.
@@ -158,31 +168,23 @@ class BrowserBox : public gcn::Widget,
BACKGROUND = 2
};
- void setAlwaysUpdate(bool n)
- { mAlwaysUpdate = n; }
-
private:
void relayoutText();
- int layoutTextRow(const std::string &row, int y);
-
- std::deque<std::string> mTextRows;
+ void layoutTextRow(TextRow &row, LayoutContext &context);
+ void updateHoveredLink(int x, int y);
- std::vector<LinePart> mLineParts;
- std::vector<BrowserLink> mLinks;
+ std::deque<TextRow> mTextRows;
LinkHandler *mLinkHandler = nullptr;
unsigned int mMode;
- unsigned int mHighMode = UNDERLINE | BACKGROUND;
+ unsigned int mHighlightMode = UNDERLINE | BACKGROUND;
bool mShadows = false;
bool mOutline = false;
- bool mOpaque;
bool mUseLinksAndUserColors = true;
- int mSelectedLink = -1;
+ std::optional<BrowserLink> mHoveredLink;
unsigned int mMaxRows = 0;
int mLastLayoutWidth = 0;
- int mYStart = 0;
- int mUpdateTime = -1;
- bool mAlwaysUpdate = true;
+ int mLastLayoutTime = -1;
};
#endif
diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp
index 1979ecbd..6687198e 100644
--- a/src/gui/widgets/chattab.cpp
+++ b/src/gui/widgets/chattab.cpp
@@ -49,15 +49,13 @@
#define MAX_WORD_SIZE 50
-ChatTab::ChatTab(const std::string &name) : Tab()
+ChatTab::ChatTab(const std::string &name)
{
setCaption(name);
mTextOutput = new BrowserBox(BrowserBox::AUTO_WRAP);
- mTextOutput->setOpaque(false);
- mTextOutput->setMaxRow((int) config.getIntValue("ChatLogLength"));
+ mTextOutput->setMaxRows((int) config.getIntValue("ChatLogLength"));
mTextOutput->setLinkHandler(chatWindow->mItemLinkHandler);
- mTextOutput->setAlwaysUpdate(false);
mScrollArea = new ScrollArea(mTextOutput);
mScrollArea->setScrollPolicy(gcn::ScrollArea::SHOW_NEVER,
diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h
index dd9eeedc..d0f00b25 100644
--- a/src/gui/widgets/itemlinkhandler.h
+++ b/src/gui/widgets/itemlinkhandler.h
@@ -31,6 +31,7 @@ class ItemLinkHandler : public LinkHandler
public:
ItemLinkHandler();
~ItemLinkHandler() override;
+
void handleLink(const std::string &link) override;
private: