diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index e6cbf09bbe..f216e48558 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -346,6 +346,7 @@ DENG_HEADERS += \ include/ui/widgets/lineeditwidget.h \ include/ui/widgets/logwidget.h \ include/ui/widgets/menuwidget.h \ + include/ui/widgets/notificationwidget.h \ include/ui/widgets/popupmenuwidget.h \ include/ui/widgets/popupwidget.h \ include/ui/widgets/scrollareawidget.h \ @@ -637,6 +638,7 @@ SOURCES += \ src/ui/widgets/lineeditwidget.cpp \ src/ui/widgets/logwidget.cpp \ src/ui/widgets/menuwidget.cpp \ + src/ui/widgets/notificationwidget.cpp \ src/ui/widgets/popupmenuwidget.cpp \ src/ui/widgets/popupwidget.cpp \ src/ui/widgets/scrollareawidget.cpp \ diff --git a/doomsday/client/include/ui/widgets/notificationwidget.h b/doomsday/client/include/ui/widgets/notificationwidget.h new file mode 100644 index 0000000000..a92ef7cbe3 --- /dev/null +++ b/doomsday/client/include/ui/widgets/notificationwidget.h @@ -0,0 +1,53 @@ +/** @file notificationwidget.h Notifiction area. + * + * @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 DENG_CLIENT_NOTIFICATIONWIDGET_H +#define DENG_CLIENT_NOTIFICATIONWIDGET_H + +#include "guiwidget.h" + +/** + * Notification area. + * + * Children the widget are expected to size themselves and allow unrestricted, + * automatical positioning inside the area. Children can be added and removed + * dynamically. The notification area is dismissed if there are no visible + * notifications. + * + * The client window owns an instance of NotificationWidget. Other widgets and + * subsystems are expected to give ownership of their notifications to the + * window's NotificationWidget. + */ +class NotificationWidget : public GuiWidget +{ +public: + NotificationWidget(de::String const &name = ""); + + // Events. + void viewResized(); + void drawContent(); + +protected: + void glInit(); + void glDeinit(); + +private: + DENG2_PRIVATE(d) +}; + +#endif // DENG_CLIENT_NOTIFICATIONWIDGET_H diff --git a/doomsday/client/src/ui/widgets/notificationwidget.cpp b/doomsday/client/src/ui/widgets/notificationwidget.cpp new file mode 100644 index 0000000000..13a57af3a9 --- /dev/null +++ b/doomsday/client/src/ui/widgets/notificationwidget.cpp @@ -0,0 +1,149 @@ +/** @file notificationwidget.cpp Notification area. + * + * @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 + */ + +#include "ui/widgets/notificationwidget.h" +#include "ui/widgets/guirootwidget.h" + +#include +#include + +using namespace de; + +DENG2_PIMPL(NotificationWidget) +{ + // GL objects: + typedef DefaultVertexBuf VertexBuf; + Drawable drawable; + GLUniform uMvpMatrix; + GLUniform uColor; + + Instance(Public *i) + : Base(i), + uMvpMatrix("uMvpMatrix", GLUniform::Mat4), + uColor ("uColor", GLUniform::Vec4) + { + updateStyle(); + } + + void updateStyle() + { + self.set(Background(self.style().colors().colorf("background"))); + } + + void glInit() + { + drawable.addBuffer(new VertexBuf); + + self.root().shaders().build(drawable.program(), "generic.color_ucolor") + << uMvpMatrix << uColor; + } + + void glDeinit() + { + drawable.clear(); + } + + void updateGeometry() + { + Rectanglei pos; + if(self.hasChangedPlace(pos) || self.geometryRequested()) + { + self.requestGeometry(false); + + VertexBuf::Builder verts; + self.glMakeGeometry(verts); + drawable.buffer().setVertices(gl::TriangleStrip, verts, gl::Static); + } + } + + void updateChildLayout() + { + Rule const &outer = self.style().rules().rule("gap"); + Rule const &inner = self.style().rules().rule("unit"); + + Rule const *totalWidth = 0; + Rule const *totalHeight = 0; + + WidgetList const children = self.Widget::children(); + for(int i = 0; i < children.size(); ++i) + { + GuiWidget &w = children[i]->as(); + + // The children are laid out simply in a row from right to left. + w.rule().setInput(Rule::Top, self.rule().top() + outer); + if(i > 0) + { + w.rule().setInput(Rule::Right, children[i - 1]->as().rule().left() - inner); + changeRef(totalWidth, *totalWidth + inner + w.rule().width()); + } + else + { + w.rule().setInput(Rule::Right, self.rule().right() - outer); + totalWidth = holdRef(w.rule().width()); + } + + if(!totalHeight) + { + totalHeight = holdRef(w.rule().height()); + } + else + { + changeRef(totalHeight, OperatorRule::maximum(*totalHeight, w.rule().height())); + } + } + + // Update the total size of the notification area. + self.rule() + .setInput(Rule::Width, *totalWidth + outer * 2) + .setInput(Rule::Height, *totalHeight + outer * 2); + + releaseRef(totalWidth); + releaseRef(totalHeight); + } +}; + +NotificationWidget::NotificationWidget(String const &name) : d(new Instance(this)) +{ + // Initially the widget is empty. + rule().setSize(Const(0), Const(0)); + hide(); +} + +void NotificationWidget::viewResized() +{ + d->uMvpMatrix = root().projMatrix2D(); +} + +void NotificationWidget::drawContent() +{ + d->updateGeometry(); + + d->uColor = Vector4f(1, 1, 1, visibleOpacity()); + d->drawable.draw(); +} + +void NotificationWidget::glInit() +{ + d->glInit(); +} + +void NotificationWidget::glDeinit() +{ + d->glDeinit(); +} + diff --git a/doomsday/libdeng2/include/de/data/counted.h b/doomsday/libdeng2/include/de/data/counted.h index a8b68d9062..73295705f4 100644 --- a/doomsday/libdeng2/include/de/data/counted.h +++ b/doomsday/libdeng2/include/de/data/counted.h @@ -160,6 +160,20 @@ inline CountedType const *holdRef(CountedType const &counted) { return counted.template ref(); } +template +inline void changeRef(CountedType1 const *&counted, CountedType2 const *newRef) { + CountedType1 const *old = counted; + counted = holdRef(newRef); + releaseRef(old); +} + +template +inline void changeRef(CountedType1 const *&counted, CountedType2 const &newRef) { + CountedType1 const *old = counted; + counted = holdRef(newRef); + releaseRef(old); +} + /** * Releases a reference to a Counted object. Afterwards, the pointer is cleared * to NULL. diff --git a/doomsday/libdeng2/include/de/widgets/widget.h b/doomsday/libdeng2/include/de/widgets/widget.h index a538f2b6f9..6cff64b724 100644 --- a/doomsday/libdeng2/include/de/widgets/widget.h +++ b/doomsday/libdeng2/include/de/widgets/widget.h @@ -85,6 +85,18 @@ class DENG2_PUBLIC Widget Widget(String const &name = ""); virtual ~Widget(); + template + Type &as() { + DENG2_ASSERT(dynamic_cast(this) != 0); + return *static_cast(this); + } + + template + Type const &as() const { + DENG2_ASSERT(dynamic_cast(this) != 0); + return *static_cast(this); + } + /** * Returns the automatically generated, unique identifier of the widget. */