Skip to content

Commit

Permalink
Client|Widgets: Use rich text formatting in the LogWidget
Browse files Browse the repository at this point in the history
Style markup in the log entries is now taken into use in the
LogWidget. Font::RichFormat is used in FontLineWrapping and
GLTextComposer. Lines can also be indented according to the indentation
markers in the rich text.

Todo: de::Font needs to know which colors to use for the rich palette
  • Loading branch information
skyjake committed May 23, 2013
1 parent a01c580 commit 486f9aa
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 31 deletions.
2 changes: 1 addition & 1 deletion doomsday/client/data/defaultstyle.pack/fonts.dei
Expand Up @@ -18,7 +18,7 @@ group {

font default {
family: Lucida Grande
size: 18pt
size: 16pt
weight: normal
style: normal
}
Expand Down
5 changes: 5 additions & 0 deletions doomsday/client/include/ui/widgets/fontlinewrapping.h
Expand Up @@ -27,6 +27,8 @@
* Line wrapper that uses a particular font and calculates widths in pixels.
* Height is still in lines, though. The wrapper cannot be used before the
* font has been defined.
*
* Supports indentation of lines, as marked in the RichFormat.
*/
class FontLineWrapping : public de::shell::ILineWrapping
{
Expand All @@ -39,6 +41,7 @@ class FontLineWrapping : public de::shell::ILineWrapping
bool isEmpty() const;
void clear();
void wrapTextToWidth(de::String const &text, int maxWidth);
void wrapTextToWidth(de::String const &text, de::Font::RichFormat const &format, int maxWidth);

de::String const &text() const;
de::shell::WrappedLine line(int index) const;
Expand All @@ -55,6 +58,8 @@ class FontLineWrapping : public de::shell::ILineWrapping

de::Vector2i charTopLeftInPixels(int line, int charIndex);

int lineIndent(int index) const;

private:
DENG2_PRIVATE(d)
};
Expand Down
1 change: 1 addition & 0 deletions doomsday/client/include/ui/widgets/gltextcomposer.h
Expand Up @@ -60,6 +60,7 @@ class GLTextComposer
void setWrapping(FontLineWrapping const &wrappedLines);

void setText(de::String const &text);
void setText(de::String const &text, de::Font::RichFormat const &format);

/**
* Makes sure all the lines are allocated on the atlas. After this all the
Expand Down
Expand Up @@ -32,7 +32,7 @@ class StyledLogSinkFormatter : public de::LogSink::IFormatter
{
// This will form a single long line. The line wrapper will
// then determine how to wrap it onto the available width.
return Lines() << entry.asText(de::LogEntry::Styled);
return Lines() << entry.asText(de::LogEntry::Styled | de::LogEntry::OmitLevel);
}
};

Expand Down
71 changes: 49 additions & 22 deletions doomsday/client/src/ui/widgets/fontlinewrapping.cpp
Expand Up @@ -27,13 +27,17 @@ DENG2_PIMPL_NOREF(FontLineWrapping)
struct Line {
WrappedLine line;
int width;
int indent;

Line(WrappedLine const &ln = WrappedLine(Rangei()), int w = 0) : line(ln), width(w) {}
Line(WrappedLine const &ln = WrappedLine(Rangei()), int w = 0, int ind = 0)
: line(ln), width(w), indent(ind) {}
};
QList<Line> lines;
String text;
Font::RichFormat format;
int indent;

Instance() : font(0) {}
Instance() : font(0), indent(0) {}

String rangeText(Rangei const &range) const
{
Expand All @@ -44,7 +48,7 @@ DENG2_PIMPL_NOREF(FontLineWrapping)
{
if(font)
{
return font->measure(rangeText(range)).width();
return font->measure(rangeText(range), format.subRange(range)).width();
}
return 0;
}
Expand All @@ -53,14 +57,28 @@ DENG2_PIMPL_NOREF(FontLineWrapping)
{
if(font)
{
return font->advanceWidth(rangeText(range));
return font->advanceWidth(rangeText(range), format.subRange(range));
}
return 0;
}

void appendLine(Rangei const &range)
{
lines.append(Line(WrappedLine(range), rangeVisibleWidth(range)));
lines.append(Line(WrappedLine(range), rangeVisibleWidth(range), indent));

// Check for possible indent for following lines.
Font::RichFormat rich = format.subRange(range);
Font::RichFormat::Iterator iter(rich);
int newIndent = indent;
while(iter.hasNext())
{
iter.next();
if(iter.markIndent())
{
newIndent = indent + rangeAdvanceWidth(Rangei(range.start, iter.range().start));
}
}
indent = newIndent;
}
};

Expand Down Expand Up @@ -91,29 +109,41 @@ void FontLineWrapping::clear()

void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth)
{
/**
* @note This is quite similar to MonospaceLineWrapping::wrapTextToWidth().
* Perhaps a generic method could be abstracted from these two.
*/
wrapTextToWidth(text, Font::RichFormat::fromPlainText(text), maxWidth);
}

void FontLineWrapping::wrapTextToWidth(String const &text, Font::RichFormat const &format, int maxWidth)
{
QChar const newline('\n');

clear();

int const MIN_LINE_WIDTH = 120;

if(maxWidth <= 1 || !d->font) return;

// This is the text that we will be wrapping.
d->text = text;
d->text = text;
d->format = format;

int begin = 0;
forever
{
// How much width is available, taking indentation into account?
if(maxWidth - d->indent < MIN_LINE_WIDTH)
{
// There is no room for this indent...
d->indent = de::max(0, maxWidth - MIN_LINE_WIDTH);
}
int availWidth = maxWidth - d->indent;

// Quick check: does the remainder fit?
Rangei range(begin, text.size());
int visWidth = d->rangeVisibleWidth(range);
if(visWidth <= maxWidth)
if(visWidth <= availWidth)
{
d->lines.append(Instance::Line(WrappedLine(Rangei(begin, text.size())), visWidth));
d->lines.append(Instance::Line(WrappedLine(Rangei(begin, text.size())),
visWidth, d->indent));
break;
}

Expand All @@ -124,7 +154,7 @@ void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth)
{
++end;

if(d->rangeVisibleWidth(Rangei(begin, end)) > maxWidth)
if(d->rangeVisibleWidth(Rangei(begin, end)) > availWidth)
{
// Went too far.
wrapPosMax = --end;
Expand All @@ -134,14 +164,6 @@ void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth)

DENG2_ASSERT(end != text.size());

/*
if(end == text.size())
{
// Out of characters; time to stop.
d->appendLine(Range(begin, text.size()));
break;
}*/

// Find a good (whitespace) break point.
while(!text.at(end).isSpace())
{
Expand All @@ -161,7 +183,7 @@ void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth)
}
else
{
while(text.at(end).isSpace()) ++end;
while(end < text.size() && text.at(end).isSpace()) ++end;
d->appendLine(Rangei(begin, end));
begin = end;
}
Expand Down Expand Up @@ -257,3 +279,8 @@ Vector2i FontLineWrapping::charTopLeftInPixels(int line, int charIndex)

return cp;
}

int FontLineWrapping::lineIndent(int index) const
{
return d->lines[index].indent;
}
13 changes: 10 additions & 3 deletions doomsday/client/src/ui/widgets/gltextcomposer.cpp
Expand Up @@ -28,6 +28,7 @@ DENG2_PIMPL(GLTextComposer)
Atlas *atlas;
String text;
FontLineWrapping const *wraps;
Font::RichFormat format;
bool needRaster;

struct Line {
Expand Down Expand Up @@ -80,7 +81,7 @@ DENG2_PIMPL(GLTextComposer)

changed = true;

String const part = text.substr(span.range.start, span.range.size());
String const part = text.substr(span.range);

if(i >= lines.size())
{
Expand All @@ -90,7 +91,7 @@ DENG2_PIMPL(GLTextComposer)

Line &line = lines[i];
line.range = span.range;
line.id = atlas->alloc(font->rasterize(part));
line.id = atlas->alloc(font->rasterize(part, format.subRange(span.range)));
}

// Remove the excess lines.
Expand Down Expand Up @@ -127,8 +128,14 @@ void GLTextComposer::setWrapping(FontLineWrapping const &wrappedLines)
}

void GLTextComposer::setText(String const &text)
{
setText(text, Font::RichFormat::fromPlainText(text));
}

void GLTextComposer::setText(String const &text, Font::RichFormat const &format)
{
d->text = text;
d->format = format;
d->needRaster = true; // Force a redo of everything.
}

Expand Down Expand Up @@ -193,7 +200,7 @@ void GLTextComposer::makeVertices(Vertices &triStrip,

v.rgba = Vector4f(1, 1, 1, 1); // should be a param

Vector2f linePos = p;
Vector2f linePos = p + Vector2f(d->wraps->lineIndent(i), 0);

// Align the line.
if(lineAlign.testFlag(AlignRight))
Expand Down
14 changes: 10 additions & 4 deletions doomsday/client/src/ui/widgets/logwidget.cpp
Expand Up @@ -47,6 +47,7 @@ DENG2_PIMPL(LogWidget)
/// Cache of drawable entries.
struct CacheEntry
{
Font::RichFormat format;
FontLineWrapping wraps;
GLTextComposer composer;

Expand All @@ -72,10 +73,11 @@ DENG2_PIMPL(LogWidget)
return wraps.isEmpty();
}

void wrap(String const &text, int width)
void wrap(String const &richText, int width)
{
wraps.wrapTextToWidth(text, width);
composer.setText(wraps.text());
String plain = format.initFromStyledText(richText);
wraps.wrapTextToWidth(plain, format, width);
composer.setText(plain, format);
composer.setWrapping(wraps);
}

Expand Down Expand Up @@ -265,6 +267,10 @@ DENG2_PIMPL(LogWidget)

StyledLogSinkFormatter::Lines lines = formatter.logEntryToTextLines(entry);
DENG2_ASSERT(lines.size() == 1);

Font::RichFormat format;
format.initFromStyledText(lines[0]);

return lines[0];
}

Expand Down Expand Up @@ -447,7 +453,7 @@ DENG2_PIMPL(LogWidget)

LogWidget::LogWidget(String const &name) : GuiWidget(name), d(new Instance(this))
{
rule().setInput(Rule::Width, Const(400)); // TODO -- from rule defs
rule().setInput(Rule::Width, Const(600)); // TODO -- from rule defs
LogBuffer::appBuffer().addSink(d->sink);
}

Expand Down

0 comments on commit 486f9aa

Please sign in to comment.