Skip to content

Commit

Permalink
Refactor|UI|Client: Added TextDrawable; used now in DocumentWidget
Browse files Browse the repository at this point in the history
TextDrawable is a higher-level text drawing class that makes it
easy to control the whole process. It is also asynchronous:
a background thread is used to wrap the content.
  • Loading branch information
skyjake committed Aug 29, 2013
1 parent 1b164a0 commit d3d53c4
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 96 deletions.
2 changes: 2 additions & 0 deletions doomsday/client/client.pro
Expand Up @@ -384,6 +384,7 @@ DENG_HEADERS += \
include/ui/widgets/submenuitem.h \
include/ui/widgets/styledlogsinkformatter.h \
include/ui/widgets/taskbarwidget.h \
include/ui/widgets/textdrawable.h \
include/ui/widgets/togglewidget.h \
include/ui/widgets/variabletoggleitem.h \
include/ui/widgets/variabletogglewidget.h \
Expand Down Expand Up @@ -701,6 +702,7 @@ SOURCES += \
src/ui/widgets/sequentiallayout.cpp \
src/ui/widgets/styledlogsinkformatter.cpp \
src/ui/widgets/taskbarwidget.cpp \
src/ui/widgets/textdrawable.cpp \
src/ui/widgets/togglewidget.cpp \
src/ui/widgets/variabletogglewidget.cpp \
src/ui/widgets/videosettingsdialog.cpp \
Expand Down
98 changes: 98 additions & 0 deletions doomsday/client/include/ui/widgets/textdrawable.h
@@ -0,0 +1,98 @@
/** @file textdrawable.h High-level GL text drawing utility.
*
* @authors Copyright (c) 2013 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* GPL: http://www.gnu.org/licenses/gpl.html
*
* <small>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 2 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:
* http://www.gnu.org/licenses</small>
*/

#ifndef DENG_CLIENT_TEXTDRAWABLE_H
#define DENG_CLIENT_TEXTDRAWABLE_H

#include "gltextcomposer.h"
#include "fontlinewrapping.h"

/**
* High-level GL text drawing utility.
*
* The task of drawing text is rather complicated. There are several components
* involved:
*
* - de::EscapeParser parses style escape codes.
* - de::Font understands rich text formatting and performs the actual
* rasterizing of text onto bitmap images.
* - FontLineWrapping takes rich-formatted ("styled") text and wraps it onto
* multiple lines, taking into account tab stops and indentation.
* - GLTextComposer allocates the text lines from an Atlas and generates the
* triangle strips needed for actually drawing the text on the screen.
*
* TextDrawable is a high-level utility for controlling this entire process as
* easily as possible. If fine-grained control of the text is required, one can
* still use the lower level components directly.
*
* TextDrawable handles wrapping of text using a background thread, so content
* length is unlimited and the calling thread will not block. TextDrawable is
* an Asset, and will not be marked ready until the background wrapping is
* done. One is still required to call TextDrawable::update() before drawing,
* though.
*/
class TextDrawable : public GLTextComposer
{
public:
TextDrawable();

void init(de::Atlas &atlas, de::Font const &font,
de::Font::RichFormat::IStyle const *style = 0);

void deinit();

/**
* Sets the maximum width for text lines.
*
* @param maxWidth Maximum line width.
*/
void setLineWrapWidth(int maxLineWidth);

void setText(de::String const &styledText);

void setFont(de::Font const &font);

/**
* Sets the range of visible lines and releases all allocations outside
* the range.
*
* @param lineRange Range of visible lines.
*/
void setRange(de::Rangei const &lineRange);

/**
* Updates the status of the composer. This includes first checking if a
* background wrapping task has been completed.
*
* @return @c true, if the lines have changed and it is necessary to remake
* the geometry. @c false, if nothing further needs to be done.
*/
bool update();

FontLineWrapping const &wraps() const;

de::Vector2i wrappedSize() const;
de::String text() const;
de::String plainText() const;
bool isBeingWrapped() const;

private:
DENG2_PRIVATE(d)
};

#endif // DENG_CLIENT_TEXTDRAWABLE_H
137 changes: 42 additions & 95 deletions doomsday/client/src/ui/widgets/documentwidget.cpp
Expand Up @@ -18,12 +18,9 @@

#include "ui/widgets/documentwidget.h"
#include "ui/widgets/progresswidget.h"
#include "ui/widgets/gltextcomposer.h"
#include "ui/widgets/textdrawable.h"

#include <de/Font>
#include <de/Drawable>
#include <de/Task>
#include <de/TaskPool>

using namespace de;

Expand All @@ -36,26 +33,7 @@ public Font::RichFormat::IStyle
{
typedef DefaultVertexBuf VertexBuf;

/**
* Background task for wrapping text onto lines and figuring out the
* formatting/tab stops.
*/
class WrapTask : public Task
{
public:
WrapTask(Instance *inst, int toWidth) : d(inst), _width(toWidth) {}
void runTask() {
DENG2_GUARD_FOR(d->wraps, G);
d->wraps.wrapTextToWidth(d->text, d->format, _width);
d->wrapTaskCompleted();
}
private:
Instance *d;
int _width;
};

ProgressWidget *progress;
TaskPool tasks;

// Style.
ColorBank::Color normalColor;
Expand All @@ -72,9 +50,7 @@ public Font::RichFormat::IStyle
String text;

// GL objects.
Font::RichFormat format;
FontLineWrapping wraps;
GLTextComposer composer;
TextDrawable composer;
Drawable drawable;
Matrix4f modelMatrix;
GLUniform uMvpMatrix;
Expand All @@ -88,13 +64,11 @@ public Font::RichFormat::IStyle
widthPolicy(ui::Expand),
maxLineWidth(1000),
oldScrollY(0),
format(*this),
uMvpMatrix ("uMvpMatrix", GLUniform::Mat4),
uScrollMvpMatrix("uMvpMatrix", GLUniform::Mat4),
uColor ("uColor", GLUniform::Vec4)
{
updateStyle();
composer.setWrapping(wraps);

// Widget to show while lines are being wrapped.
progress = new ProgressWidget;
Expand All @@ -105,17 +79,6 @@ public Font::RichFormat::IStyle
self.add(progress);
}

~Instance()
{
// Wait until background tasks finish.
tasks.waitForDone();
}

bool isBeingWrapped() const
{
return !tasks.isDone();
}

void updateStyle()
{
Style const &st = style();
Expand All @@ -126,9 +89,7 @@ public Font::RichFormat::IStyle
accentColor = st.colors().color("document.accent");
dimAccentColor = st.colors().color("document.dimaccent");

wraps.setFont(self.font());
wraps.clear();
composer.forceUpdate();
composer.setFont(self.font());
self.requestGeometry();
}

Expand Down Expand Up @@ -160,14 +121,14 @@ public Font::RichFormat::IStyle
Font::RichFormat::Style &fontStyle,
int &colorIndex) const
{
return self.style().richStyleFormat(contentStyle, sizeFactor, fontWeight, fontStyle, colorIndex);
return style().richStyleFormat(contentStyle, sizeFactor, fontWeight, fontStyle, colorIndex);
}

void glInit()
{
atlas().audienceForReposition += this;
composer.setAtlas(atlas());
composer.setText(text, format);

composer.init(atlas(), self.font(), this);

self.setIndicatorUv(atlas().imageRectf(root().solidWhitePixel()).middle());

Expand All @@ -186,7 +147,7 @@ public Font::RichFormat::IStyle
void glDeinit()
{
atlas().audienceForReposition -= this;
composer.release();
composer.deinit();
drawable.clear();
}

Expand All @@ -196,18 +157,6 @@ public Font::RichFormat::IStyle
self.requestGeometry();
}

void beginWrapTask(int toWidth)
{
tasks.start(new WrapTask(this, toWidth));
}

void wrapTaskCompleted()
{
// This is executed in the background thread.
progress->hide();
self.setContentSize(Vector2i(wraps.width(), wraps.totalHeightInPixels()));
}

void updateGeometry()
{
// If scroll position has changed, must update text geometry.
Expand All @@ -224,34 +173,41 @@ public Font::RichFormat::IStyle
self.requestGeometry();
}

// Make sure the text has been wrapped for the current dimensions.
int wrapWidth;
if(widthPolicy == ui::Expand)
{
wrapWidth = maxLineWidth;
}
else
{
wrapWidth = self.rule().width().valuei() - self.margins().width().valuei();
}
composer.setLineWrapWidth(wrapWidth);
if(composer.update())
{
// Text has changed.
qDebug() << "composer updated";
self.requestGeometry();
}

if(!self.geometryRequested()) return;

// Background and scroll indicator.
VertexBuf::Builder verts;
self.glMakeGeometry(verts);
drawable.buffer<VertexBuf>(ID_BACKGROUND).setVertices(gl::TriangleStrip, verts,
self.isScrolling()? gl::Dynamic : gl::Static);
drawable.buffer<VertexBuf>(ID_BACKGROUND)
.setVertices(gl::TriangleStrip, verts, self.isScrolling()? gl::Dynamic : gl::Static);

uMvpMatrix = root().projMatrix2D();

if(!isBeingWrapped())
if(composer.isReady())
{
int wrapWidth;

// Make sure the text has been wrapped for the current dimensions.
if(widthPolicy == ui::Expand)
{
wrapWidth = maxLineWidth;
}
else
// Text is ready for drawing.
if(progress->isVisible())
{
wrapWidth = self.rule().width().valuei() - self.margins().width().valuei();
}

if(wraps.isEmpty() || wraps.maximumWidth() != wrapWidth)
{
beginWrapTask(wrapWidth);
return;
self.setContentSize(composer.wrappedSize());
progress->hide();
}

// Determine visible range of lines.
Expand All @@ -266,16 +222,11 @@ public Font::RichFormat::IStyle
if(visRange != composer.range())
{
composer.setRange(visRange);
composer.releaseLinesOutsideRange();
composer.update();

// Geometry from the text composer.
if(composer.isReady())
{
VertexBuf::Builder verts;
composer.makeVertices(verts, Vector2i(0, 0), ui::AlignLeft);
drawable.buffer<VertexBuf>(ID_TEXT).setVertices(gl::TriangleStrip, verts, gl::Static);
}
composer.update(); // alloc visible lines

VertexBuf::Builder verts;
composer.makeVertices(verts, Vector2i(0, 0), ui::AlignLeft);
drawable.buffer<VertexBuf>(ID_TEXT).setVertices(gl::TriangleStrip, verts, gl::Static);
}

uScrollMvpMatrix = root().projMatrix2D() *
Expand Down Expand Up @@ -310,26 +261,22 @@ DocumentWidget::DocumentWidget(String const &name) : d(new Instance(this))

void DocumentWidget::setText(String const &styledText)
{
if(styledText != d->styledText)
if(styledText != d->composer.text())
{
// The wrapping task is uncancellable.
d->tasks.waitForDone();

// Show the progress indicator until the text is ready for drawing.
if(d->drawable.hasBuffer(ID_TEXT))
{
d->drawable.buffer(ID_TEXT).clear();
}

d->progress->show();
int indSize = style().rules().rule("document.progress").valuei();
setContentSize(Vector2i(indSize, indSize));

d->wraps.clear();
d->composer.release();

d->styledText = styledText;
d->text = d->format.initFromStyledText(styledText);
d->composer.setText(d->text, d->format);

d->composer.setText(styledText);
d->composer.setRange(Rangei()); // updated later

requestGeometry();
}
Expand Down
3 changes: 2 additions & 1 deletion doomsday/client/src/ui/widgets/gltextcomposer.cpp
Expand Up @@ -76,7 +76,8 @@ DENG2_PIMPL(GLTextComposer)

void releaseOutsideRange()
{
DENG2_ASSERT(atlas != 0);
if(!atlas) return;

for(int i = 0; i < lines.size(); ++i)
{
if(!isLineVisible(i))
Expand Down

0 comments on commit d3d53c4

Please sign in to comment.