diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index 472b4db652..6ff8334a37 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -367,6 +367,7 @@ DENG_HEADERS += \ include/ui/widgets/legacywidget.h \ include/ui/widgets/lineeditwidget.h \ include/ui/widgets/logwidget.h \ + include/ui/widgets/styledlogsinkformatter.h \ include/ui/widgets/taskbarwidget.h \ include/ui/widgets/widgetactions.h \ include/ui/windowsystem.h \ diff --git a/doomsday/client/data/defaultstyle.pack/fonts.dei b/doomsday/client/data/defaultstyle.pack/fonts.dei index d65d2e21d8..d1cde77e3e 100644 --- a/doomsday/client/data/defaultstyle.pack/fonts.dei +++ b/doomsday/client/data/defaultstyle.pack/fonts.dei @@ -18,7 +18,7 @@ group { font default { family: Lucida Grande - size: 24pt + size: 18pt weight: normal style: normal } @@ -37,7 +37,7 @@ font title inherits default { } group log { - font normal inherits monospace {} + font normal inherits default {} } group editor { diff --git a/doomsday/client/include/ui/widgets/fontlinewrapping.h b/doomsday/client/include/ui/widgets/fontlinewrapping.h index bb5a9adaed..dc34c062f2 100644 --- a/doomsday/client/include/ui/widgets/fontlinewrapping.h +++ b/doomsday/client/include/ui/widgets/fontlinewrapping.h @@ -39,6 +39,8 @@ class FontLineWrapping : public de::shell::ILineWrapping bool isEmpty() const; void clear(); void wrapTextToWidth(de::String const &text, int maxWidth); + + de::String const &text() const; de::shell::WrappedLine line(int index) const; int width() const; int height() const; diff --git a/doomsday/client/include/ui/widgets/gltextcomposer.h b/doomsday/client/include/ui/widgets/gltextcomposer.h index 05bc1be834..b66e2b7964 100644 --- a/doomsday/client/include/ui/widgets/gltextcomposer.h +++ b/doomsday/client/include/ui/widgets/gltextcomposer.h @@ -75,6 +75,10 @@ class GLTextComposer */ void release(); + void makeVertices(Vertices &triStrip, + de::Vector2i const &topLeft, + Alignment const &lineAlign); + /** * Generates vertices for all the text lines and concatenates them * onto the existing triangle strip in @a triStrip. diff --git a/doomsday/client/include/ui/widgets/logwidget.h b/doomsday/client/include/ui/widgets/logwidget.h index 4a63e97151..2940bd5168 100644 --- a/doomsday/client/include/ui/widgets/logwidget.h +++ b/doomsday/client/include/ui/widgets/logwidget.h @@ -70,7 +70,7 @@ class LogWidget : public QObject, public GuiWidget void scroll(int to); // Events. - void initialize(); + void viewResized(); void draw(); bool handleEvent(de::Event const &event); @@ -84,6 +84,10 @@ public slots: void scrollPositionChanged(int pos); void scrollMaxChanged(int maximum); +protected: + void glInit(); + void glDeinit(); + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/client/include/ui/widgets/styledlogsinkformatter.h b/doomsday/client/include/ui/widgets/styledlogsinkformatter.h new file mode 100644 index 0000000000..3320a68f63 --- /dev/null +++ b/doomsday/client/include/ui/widgets/styledlogsinkformatter.h @@ -0,0 +1,39 @@ +/** @file styledlogsinkformatter.h + * + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 + */ + +#ifndef STYLEDLOGSINKFORMATTER_H +#define STYLEDLOGSINKFORMATTER_H + +#include +#include + +/** + * Formats log entries for styled output. + */ +class StyledLogSinkFormatter : public de::LogSink::IFormatter +{ +public: + Lines logEntryToTextLines(de::LogEntry const &entry) + { + // 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); + } +}; + +#endif // STYLEDLOGSINKFORMATTER_H diff --git a/doomsday/client/src/ui/clientwindow.cpp b/doomsday/client/src/ui/clientwindow.cpp index 1245caef30..3e539617ec 100644 --- a/doomsday/client/src/ui/clientwindow.cpp +++ b/doomsday/client/src/ui/clientwindow.cpp @@ -38,6 +38,7 @@ #include "ui/widgets/taskbarwidget.h" #include "ui/widgets/lineeditwidget.h" #include "ui/widgets/consolecommandwidget.h" +#include "ui/widgets/logwidget.h" #include "ui/mouse_qt.h" #include "dd_main.h" @@ -96,6 +97,13 @@ DENG2_PIMPL(ClientWindow), root.add(test); root.setFocus(test); + LogWidget *log = new LogWidget; + log->rule() + .setInput(Rule::Left, root.viewLeft()) + .setInput(Rule::Bottom, test->rule().top()) + .setInput(Rule::Top, root.viewTop() + 200); + root.add(log); + // Initially the widget is disabled. It will be enabled when the window // is visible and ready to be drawn. legacy->disable(); diff --git a/doomsday/client/src/ui/widgets/fontlinewrapping.cpp b/doomsday/client/src/ui/widgets/fontlinewrapping.cpp index bee97d983d..a5e619761d 100644 --- a/doomsday/client/src/ui/widgets/fontlinewrapping.cpp +++ b/doomsday/client/src/ui/widgets/fontlinewrapping.cpp @@ -24,7 +24,13 @@ using namespace de::shell; DENG2_PIMPL_NOREF(FontLineWrapping) { Font const *font; - QList lines; + struct Line { + WrappedLine line; + int width; + + Line(WrappedLine const &ln = WrappedLine(Range()), int w = 0) : line(ln), width(w) {} + }; + QList lines; String text; Instance() : font(0) {} @@ -51,6 +57,11 @@ DENG2_PIMPL_NOREF(FontLineWrapping) } return 0; } + + void appendLine(Range const &range) + { + lines.append(Line(WrappedLine(range), rangeVisibleWidth(range))); + } }; FontLineWrapping::FontLineWrapping() : d(new Instance) @@ -98,9 +109,11 @@ void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth) forever { // Quick check: does the remainder fit? - if(d->rangeVisibleWidth(Range(begin, text.size())) <= maxWidth) + Range range(begin, text.size()); + int visWidth = d->rangeVisibleWidth(range); + if(visWidth <= maxWidth) { - d->lines.append(WrappedLine(Range(begin, text.size()))); + d->lines.append(Instance::Line(WrappedLine(Range(begin, text.size())), visWidth)); break; } @@ -119,12 +132,15 @@ void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth) } } + DENG2_ASSERT(end != text.size()); + + /* if(end == text.size()) { // Out of characters; time to stop. - d->lines.append(WrappedLine(Range(begin, text.size()))); + d->appendLine(Range(begin, text.size())); break; - } + }*/ // Find a good (whitespace) break point. while(!text.at(end).isSpace()) @@ -140,37 +156,38 @@ void FontLineWrapping::wrapTextToWidth(String const &text, int maxWidth) if(text.at(end) == newline) { // The newline will be omitted from the wrapped lines. - d->lines.append(WrappedLine(Range(begin, end))); + d->appendLine(Range(begin, end)); begin = end + 1; } else { while(text.at(end).isSpace()) ++end; - d->lines.append(WrappedLine(Range(begin, end))); + d->appendLine(Range(begin, end)); begin = end; - //while(text.at(begin).isSpace()) ++begin; } } // Mark the final line. - d->lines.last().isFinal = true; + d->lines.last().line.isFinal = true; +} + +String const &FontLineWrapping::text() const +{ + return d->text; } WrappedLine FontLineWrapping::line(int index) const { DENG2_ASSERT(index >= 0 && index < height()); - return d->lines[index]; + return d->lines[index].line; } int FontLineWrapping::width() const { - if(!d->font) return 0; - int w = 0; for(int i = 0; i < d->lines.size(); ++i) { - WrappedLine const &span = d->lines[i]; - w = de::max(w, d->rangeVisibleWidth(span.range)); + w = de::max(w, d->lines[i].width); } return w; } @@ -230,7 +247,7 @@ Vector2i FontLineWrapping::charTopLeftInPixels(int line, int charIndex) { if(line >= height()) return Vector2i(); - WrappedLine const span = d->lines[line]; + WrappedLine const span = d->lines[line].line; Range const range(span.range.start, de::min(span.range.end, span.range.start + charIndex)); Vector2i cp; diff --git a/doomsday/client/src/ui/widgets/gltextcomposer.cpp b/doomsday/client/src/ui/widgets/gltextcomposer.cpp index 43bfa4ed52..905cb2ce51 100644 --- a/doomsday/client/src/ui/widgets/gltextcomposer.cpp +++ b/doomsday/client/src/ui/widgets/gltextcomposer.cpp @@ -91,8 +91,6 @@ DENG2_PIMPL(GLTextComposer) Line &line = lines[i]; line.range = span.range; line.id = atlas->alloc(font->rasterize(part)); - - //qDebug() << lines.back().asText() << part; } // Remove the excess lines. @@ -144,6 +142,13 @@ bool GLTextComposer::update() return d->allocLines(); } +void GLTextComposer::makeVertices(Vertices &triStrip, + Vector2i const &topLeft, + Alignment const &lineAlign) +{ + makeVertices(triStrip, Rectanglei(topLeft, topLeft), AlignTop | AlignLeft, lineAlign); +} + void GLTextComposer::makeVertices(Vertices &triStrip, Rectanglei const &rect, Alignment const &alignInRect, @@ -167,7 +172,7 @@ void GLTextComposer::makeVertices(Vertices &triStrip, { p.y += int(rect.height()) - contentSize.y; } - else + else if(!alignInRect.testFlag(AlignTop)) { p.y += (int(rect.height()) - contentSize.y) / 2; } diff --git a/doomsday/client/src/ui/widgets/logwidget.cpp b/doomsday/client/src/ui/widgets/logwidget.cpp index 0bf2cf458f..74370c9cd2 100644 --- a/doomsday/client/src/ui/widgets/logwidget.cpp +++ b/doomsday/client/src/ui/widgets/logwidget.cpp @@ -18,11 +18,13 @@ #include "ui/widgets/logwidget.h" #include "ui/widgets/guirootwidget.h" +#include "ui/widgets/fontlinewrapping.h" +#include "ui/widgets/gltextcomposer.h" +#include "ui/widgets/styledlogsinkformatter.h" #include "ui/style.h" #include "clientapp.h" #include -#include #include #include #include @@ -35,49 +37,113 @@ using namespace de; DENG2_PIMPL(LogWidget) { + typedef GLBufferT VertexBuf; + // Log entries. MemoryLogSink sink; - MonospaceLogSinkFormatter formatter; + StyledLogSinkFormatter formatter; int maxEntries; - // Cache. - struct CacheEntry { - Id id; - duint height; // pixels - QImage *rasterized; + /// Cache of drawable entries. + struct CacheEntry + { + int y; + FontLineWrapping wraps; + GLTextComposer composer; + + CacheEntry(Font const &font, Atlas &atlas) : y(0) + { + wraps.setFont(font); + composer.setAtlas(atlas); + } + + ~CacheEntry() + { + // Free atlas allocations. + composer.release(); + } - CacheEntry() : id(Id::None), height(0), rasterized(0) {} + int height() const + { + return wraps.height() * wraps.font().lineSpacing().valuei(); + } + + bool needWrap() const + { + return wraps.isEmpty(); + } + + void wrap(String const &text, int width) + { + wraps.wrapTextToWidth(text, width); + composer.setText(wraps.text()); + composer.setWrapping(wraps); + } + + void make(GLTextComposer::Vertices &verts) + { + composer.update(); + composer.makeVertices(verts, Vector2i(0, y), GLTextComposer::AlignLeft); + } + + void clear() + { + composer.release(); + } }; - QList cache; ///< Indices match entry indices in sink. - duint cacheWidth; + + QList cache; ///< Indices match entry indices in sink. + int cacheWidth; // State. - int visibleOffset; + Animation visibleOffset; + int maxScroll; int lastMaxScroll; + int firstVisibleIndex; + int lastVisibleIndex; // Style. Font const *font; + int margin; // GL objects. - AtlasTexture atlas; + VertexBuf *buf; + VertexBuf *bgBuf; + AtlasTexture *entryAtlas; Drawable drawable; + GLUniform uMvpMatrix; + GLUniform uTex; + GLUniform uColor; + GLUniform uBgMvpMatrix; + GLUniform uBgTex; Matrix4f projMatrix; Matrix4f viewMatrix; + Id bgTex; Instance(Public *i) : Base(i), maxEntries(1000), cacheWidth(0), visibleOffset(0), + maxScroll(0), lastMaxScroll(0), - font(0) + firstVisibleIndex(-1), + lastVisibleIndex(-1), + font(0), + buf(0), + entryAtlas(0), + uMvpMatrix("uMvpMatrix", GLUniform::Mat4), + uTex ("uTex", GLUniform::Sampler2D), + uColor ("uColor", GLUniform::Vec4), + uBgMvpMatrix("uMvpMatrix", GLUniform::Mat4), + uBgTex ("uTex", GLUniform::Sampler2D) { updateStyle(); } ~Instance() { - clearCache(); + LogBuffer::appBuffer().removeSink(sink); } void clear() @@ -88,13 +154,49 @@ DENG2_PIMPL(LogWidget) void clearCache() { - atlas.clear(); + entryAtlas->clear(); cache.clear(); } void updateStyle() { font = &self.style().fonts().font("log.normal"); + margin = self.style().rules().rule("gap").valuei(); + } + + void glInit() + { + entryAtlas = AtlasTexture::newWithRowAllocator( + Atlas::BackingStore | Atlas::AllowDefragment, + GLTexture::maximumSize().min(Atlas::Size(4096, 2048))); + + bgTex = self.root().atlas().alloc(Image::solidColor(Image::Color(255, 255, 255, 255), + Image::Size(1, 1))); + uBgTex = self.root().atlas(); + + uTex = entryAtlas; + uColor = Vector4f(1, 1, 1, 1); + + drawable.addBufferWithNewProgram(bgBuf = new VertexBuf, "bg"); + self.root().shaders().build(drawable.program("bg"), "generic.tex_color") + << uBgMvpMatrix + << uBgTex; + + // Vertex buffer for the log entries. + drawable.addBuffer(buf = new VertexBuf); + self.root().shaders().build(drawable.program(), "generic.tex_color") + << uMvpMatrix + //<< uColor + << uTex; + } + + void glDeinit() + { + self.root().atlas().release(bgTex); + + clearCache(); + delete entryAtlas; + entryAtlas = 0; } void prune() @@ -107,18 +209,24 @@ DENG2_PIMPL(LogWidget) } while(excess-- > 0 && !cache.isEmpty()) { - CacheEntry entry = cache.takeFirst(); - atlas.release(entry.id); - delete entry.rasterized; + delete cache.takeFirst(); + + firstVisibleIndex--; + lastVisibleIndex--; } } + duint contentWidth() const + { + return de::max(margin, self.rule().width().valuei() - 2 * margin); + } + int totalHeight() { int total = 0; for(int idx = sink.entryCount() - 1; idx >= 0; --idx) { - total += cache[idx].height; + total += cache[idx]->height(); } return total; } @@ -131,21 +239,217 @@ DENG2_PIMPL(LogWidget) void clampVisibleOffset(int visibleHeight) { - setVisibleOffset(de::min(visibleOffset, maxVisibleOffset(visibleHeight))); + setVisibleOffset(de::min(int(visibleOffset), maxVisibleOffset(visibleHeight))); } void setVisibleOffset(int off) { - if(visibleOffset != off) + if(int(visibleOffset) != off) { - visibleOffset = off; + visibleOffset.setValue(off, .3f); emit self.scrollPositionChanged(off); } } + + String styledTextForEntry(int index) + { + // No cached entry for this, generate one. + LogEntry const &entry = sink.entry(index); + + StyledLogSinkFormatter::Lines lines = formatter.logEntryToTextLines(entry); + DENG2_ASSERT(lines.size() == 1); + return lines[0]; + } + + void cacheEntries() + { + // Cache entries we don't yet have. + while(cache.size() < sink.entryCount()) + { + int idx = cache.size(); + + // No cached entry for this, generate one. + CacheEntry *cached = new CacheEntry(*font, *entryAtlas); + cached->wrap(styledTextForEntry(idx), contentWidth()); + if(idx > 0) + { + CacheEntry *previous = cache[idx - 1]; + cached->y = previous->y + previous->height(); + } + cache.append(cached); + + // Adjust visible offset so it remains fixed in relation to + // existing entries. + if(visibleOffset.target() > 0) + { + setVisibleOffset(visibleOffset.target() + cached->height()); + } + } + } + + void rewrapCache() + { + for(int i = 0; i < cache.size(); ++i) + { + CacheEntry *entry = cache[i]; + entry->wrap(styledTextForEntry(i), contentWidth()); + } + } + + void releaseExcessComposedEntries() + { + if(lastVisibleIndex < 0 || firstVisibleIndex < 0) return; + + // We don't need to keep all entries ready for drawing immediately. + + int const visRange = lastVisibleIndex - firstVisibleIndex; + + // Excess entries before the visible range. + int excess = firstVisibleIndex - visRange; + for(int i = 0; i <= excess; ++i) + { + cache[i]->clear(); + } + + // Excess entries after the visible range. + excess = lastVisibleIndex + visRange; + for(int i = excess; i < cache.size(); ++i) + { + cache[i]->clear(); + } + } + + void updateProjection() + { + projMatrix = self.root().projMatrix2D(); + + uBgMvpMatrix = projMatrix; + } + + void drawEntries() + { + // While we're drawing, new entries shouldn't be added. + DENG2_GUARD(sink); + + Rectanglei pos = self.rule().recti(); + Vector2i const contentSize(contentWidth(), pos.height()); + + // If the width of the widget changes, text needs to be reflowed with the + // new width. + if(cacheWidth != contentSize.x) + { + rewrapCache(); + cacheWidth = contentSize.x; + } + + // Get new entries. + cacheEntries(); + + // We should now have a cached entry for each log entry. Not all of them + // are composed, though. + DENG2_ASSERT(cache.size() == sink.entryCount()); + + clampVisibleOffset(contentSize.y); + + GLState &st = GLState::push(); + // TODO -- Set up a scissor to limit the drawn entries. + + // Draw in reverse, as much as we need. + int yBottom = contentSize.y + visibleOffset; + + // Scrolling is done using the matrix. + maxScroll = maxVisibleOffset(contentSize.y); + uMvpMatrix = projMatrix * Matrix4f::translate( + Vector2f(pos.topLeft + Vector2i(margin, visibleOffset - maxScroll))); + + firstVisibleIndex = -1; + lastVisibleIndex = -1; + + // Copy all visible entries to the buffer. + VertexBuf::Vertices verts; + for(int idx = sink.entryCount() - 1; yBottom > 0 && idx >= 0; --idx) + { + CacheEntry *entry = cache[idx]; + + yBottom -= entry->height(); + + if(yBottom < contentSize.y) + { + // This entry is visible. + entry->make(verts); + + if(lastVisibleIndex == -1) lastVisibleIndex = idx; + firstVisibleIndex = idx; + } + } + + buf->setVertices(gl::TriangleStrip, verts, gl::Dynamic); + + // Draw the scroll indicator. + + /* + if(d->showScrollIndicator && d->visibleOffset > 0) + { + int const indHeight = de::clamp(2, de::floor(float(buf.height() * buf.height()) / + float(d->totalHeight())), buf.height() / 2); + float const indPos = float(d->visibleOffset) / float(maxScroll); + int const avail = buf.height() - indHeight; + for(int i = 0; i < indHeight; ++i) + { + buf.put(Vector2i(buf.width() - 1, i + avail - indPos * avail), + TextCanvas::Char(':', TextCanvas::Char::Reverse)); + } + } + targetCanvas().draw(buf, pos.topLeft); + */ + + GLState::pop(); + + // We won't keep an unlimited number of entries in memory; delete the + // oldest ones if limit has been reached. + prune(); + } + + void draw() + { + Rectanglei pos; + if(self.checkPlace(pos) || !bgBuf->isReady()) + { + // Update the background quad. + VertexBuf::Vertices verts; + + VertexBuf::Type v; + v.rgba = Vector4f(0, 0, 0, .9f); + v.texCoord = self.root().atlas().imageRectf(bgTex).middle(); + + v.pos = pos.topLeft; verts << v; + v.pos = pos.topRight(); verts << v; + v.pos = pos.bottomLeft(); verts << v; + v.pos = pos.bottomRight; verts << v; + + bgBuf->setVertices(gl::TriangleStrip, verts, gl::Static); + } + + drawEntries(); + + drawable.draw(); + + releaseExcessComposedEntries(); + + // Notify now that we know what the max scroll is. + if(lastMaxScroll != maxScroll) + { + lastMaxScroll = maxScroll; + emit self.scrollMaxChanged(maxScroll); + } + } }; LogWidget::LogWidget(String const &name) : GuiWidget(name), d(new Instance(this)) -{} +{ + rule().setInput(Rule::Width, Const(400)); // TODO -- from rule defs + LogBuffer::appBuffer().addSink(d->sink); +} LogSink &LogWidget::logSink() { @@ -164,7 +468,7 @@ int LogWidget::scrollPosition() const int LogWidget::scrollPageSize() const { - return de::max(1, rule().height().valuei() - 1); + return de::max(1, rule().height().valuei() / 2); // - ); } int LogWidget::maximumScroll() const @@ -177,108 +481,19 @@ void LogWidget::scroll(int to) d->visibleOffset = de::max(0, to); } -void LogWidget::initialize() +void LogWidget::viewResized() { - // Reserve / set up GL objects. - + d->updateProjection(); } void LogWidget::draw() { - Rectanglei pos = rule().recti(); - /* - TextCanvas buf(pos.size()); -*/ - if(d->cacheWidth != pos.width()) - { - d->cacheWidth = pos.width(); - d->formatter.setMaxLength(d->cacheWidth); - - // Width has changed, zap the cache. - d->clearCache(); - } - - // While we're drawing, new entries shouldn't be added. - d->sink.lock(); -/* - // Cache entries we don't yet have. We'll do this in normal order so that - // the formatter gets them chronologically. - while(d->cache.size() < d->sink.entryCount()) - { - int idx = d->cache.size(); - - // No cached entry for this, generate one. - LogEntry const &entry = d->sink.entry(idx); - QList lines = d->formatter.logEntryToTextLines(entry); - - TextCanvas *buf = new TextCanvas(Vector2ui(pos.width(), lines.size())); - d->cache.append(buf); - - TextCanvas::Char::Attribs attribs = (entry.flags() & LogEntry::Remote? - TextCanvas::Char::DefaultAttributes : TextCanvas::Char::Bold); - - // Draw the text. - for(int i = 0; i < lines.size(); ++i) - { - buf->drawText(Vector2i(0, i), lines[i], attribs); - } - - // Adjust visible offset. - if(d->visibleOffset > 0) - { - d->setVisibleOffset(d->visibleOffset + lines.size()); - } - } - - DENG2_ASSERT(d->cache.size() == d->sink.entryCount()); - - d->clampVisibleOffset(buf.height()); - - // Draw in reverse, as much as we need. - int yBottom = buf.height() + d->visibleOffset; - - for(int idx = d->sink.entryCount() - 1; yBottom > 0 && idx >= 0; --idx) - { - TextCanvas *canvas = d->cache[idx]; - yBottom -= canvas->size().y; - if(yBottom < buf.height()) - { - buf.draw(*canvas, Vector2i(0, yBottom)); - } - } - */ - // Draw the scroll indicator. - int const maxScroll = d->maxVisibleOffset(pos.height()); - /* - if(d->showScrollIndicator && d->visibleOffset > 0) - { - int const indHeight = de::clamp(2, de::floor(float(buf.height() * buf.height()) / - float(d->totalHeight())), buf.height() / 2); - float const indPos = float(d->visibleOffset) / float(maxScroll); - int const avail = buf.height() - indHeight; - for(int i = 0; i < indHeight; ++i) - { - buf.put(Vector2i(buf.width() - 1, i + avail - indPos * avail), - TextCanvas::Char(':', TextCanvas::Char::Reverse)); - } - } - targetCanvas().draw(buf, pos.topLeft); -*/ - - d->prune(); - d->sink.unlock(); - - // Notify now that we know what the max scroll is. - if(d->lastMaxScroll != maxScroll) - { - d->lastMaxScroll = maxScroll; - emit scrollMaxChanged(maxScroll); - } + d->draw(); } bool LogWidget::handleEvent(Event const &event) { - if(event.type() != Event::KeyPress) return false; + if(!event.isKeyDown()) return false; KeyEvent const &ev = static_cast(event); @@ -287,11 +502,11 @@ bool LogWidget::handleEvent(Event const &event) switch(ev.ddKey()) { case DDKEY_PGUP: - d->setVisibleOffset(d->visibleOffset + pageSize); + d->setVisibleOffset(int(d->visibleOffset.target()) + pageSize); return true; case DDKEY_PGDN: - d->setVisibleOffset(de::max(0, d->visibleOffset - pageSize)); + d->setVisibleOffset(de::max(0, int(d->visibleOffset.target()) - pageSize)); return true; default: @@ -305,3 +520,13 @@ void LogWidget::scrollToBottom() { d->setVisibleOffset(0); } + +void LogWidget::glInit() +{ + d->glInit(); +} + +void LogWidget::glDeinit() +{ + d->glDeinit(); +}