Skip to content

Commit

Permalink
Refactor|UI|Client: Dialog action button placement to opposite edge
Browse files Browse the repository at this point in the history
Dialog action buttons are now placed in a separate menu on the
opposite side of the dialog. However, there is only a single buttons
data model for a dialog, shared by both widgets.

The Gear button in UpdateAvailableDialog is now displayed using this
mechanism.
  • Loading branch information
skyjake committed Sep 13, 2013
1 parent 154dd23 commit 4c412af
Show file tree
Hide file tree
Showing 30 changed files with 332 additions and 137 deletions.
6 changes: 3 additions & 3 deletions doomsday/client/client.pro
Expand Up @@ -118,7 +118,7 @@ DENG_CONVENIENCE_HEADERS += \
include/BspLeaf \
include/BspNode \
include/CommandAction \
include/ContextWidgetOrganizer \
include/ChildWidgetOrganizer \
include/Decoration \
include/DialogContentStylist \
include/EntityDatabase \
Expand Down Expand Up @@ -376,7 +376,7 @@ DENG_HEADERS += \
include/ui/framework/actionitem.h \
include/ui/framework/atlasproceduralimage.h \
include/ui/framework/commandaction.h \
include/ui/framework/contextwidgetorganizer.h \
include/ui/framework/childwidgetorganizer.h \
include/ui/framework/data.h \
include/ui/framework/dialogcontentstylist.h \
include/ui/framework/fontlinewrapping.h \
Expand Down Expand Up @@ -722,7 +722,7 @@ SOURCES += \
src/ui/fi_main.cpp \
src/ui/finaleinterpreter.cpp \
src/ui/framework/commandaction.cpp \
src/ui/framework/contextwidgetorganizer.cpp \
src/ui/framework/childwidgetorganizer.cpp \
src/ui/framework/data.cpp \
src/ui/framework/dialogcontentstylist.cpp \
src/ui/framework/fontlinewrapping.cpp \
Expand Down
1 change: 1 addition & 0 deletions doomsday/client/include/ChildWidgetOrganizer
@@ -0,0 +1 @@
#include "ui/framework/childwidgetorganizer.h"
1 change: 0 additions & 1 deletion doomsday/client/include/ContextWidgetOrganizer

This file was deleted.

@@ -1,4 +1,4 @@
/** @file contextwidgetorganizer.h Organizes widgets according to a UI context.
/** @file childwidgetorganizer.h Organizes widgets according to a UI context.
*
* @authors Copyright (c) 2013 Jaakko Keränen <jaakko.keranen@iki.fi>
*
Expand All @@ -16,8 +16,8 @@
* http://www.gnu.org/licenses</small>
*/

#ifndef DENG_CLIENT_CONTEXTWIDGETORGANIZER_H
#define DENG_CLIENT_CONTEXTWIDGETORGANIZER_H
#ifndef DENG_CLIENT_CHILDWIDGETORGANIZER_H
#define DENG_CLIENT_CHILDWIDGETORGANIZER_H

#include "data.h"
#include "guiwidget.h"
Expand All @@ -29,15 +29,15 @@
* change, and reordering them when the items' order changes.
*
* The concrete task of creating widgets is done by an object that implements
* the ContextWidgetOrganizer::IWidgetFactory interface. Also, third parties
* the ChildWidgetOrganizer::IWidgetFactory interface. Also, third parties
* may observe widget creation and updates and alter the widget as they choose.
*
* @todo Virtualization: it is not required that all the items of the context
* are represented by widgets on screen at the same time. In contexts with
* large numbers of items, virtualization should be applied to keep only a
* subset/range of items present as widgets.
*/
class ContextWidgetOrganizer
class ChildWidgetOrganizer
{
public:
/**
Expand Down Expand Up @@ -71,6 +71,28 @@ class ContextWidgetOrganizer
virtual void updateItemWidget(GuiWidget &widget, ui::Item const &item) = 0;
};

/**
* Filters out data items.
*/
class IFilter
{
public:
virtual ~IFilter() {}

/**
* Determines whether an item should be ignored by the organizer.
*
* @param organizer Which organizer is asking the question.
* @param data Data context.
* @param pos Position of the item in the context.
*
* @return @c true to accept item, @c false to ignore it.
*/
virtual bool isItemAccepted(ChildWidgetOrganizer const &organizer,
ui::Data const &data,
ui::Data::Pos pos) const = 0;
};

/**
* Notified when the organizer creates a widget for a context item. Allows
* third parties to customize the widget as needed.
Expand All @@ -88,7 +110,7 @@ class ContextWidgetOrganizer
ui::Item const &item))

public:
ContextWidgetOrganizer(GuiWidget &container);
ChildWidgetOrganizer(GuiWidget &container);

/**
* Sets the object responsible for creating widgets for this organizer. The
Expand All @@ -101,6 +123,13 @@ class ContextWidgetOrganizer

IWidgetFactory &widgetFactory() const;

/**
* Sets the object that decides whether items are accepted or ignored.
*
* @param filter Filtering object.
*/
void setFilter(IFilter const &filter);

/**
* Sets the data context of the organizer. If there was a previous context,
* all widgets created for it are deleted from the container. The widgets
Expand All @@ -126,11 +155,11 @@ class ContextWidgetOrganizer
* Simple widget factory that creates label widgets with their default
* settings, using the label from the ui::Item.
*/
class DefaultWidgetFactory : public ContextWidgetOrganizer::IWidgetFactory
class DefaultWidgetFactory : public ChildWidgetOrganizer::IWidgetFactory
{
public:
GuiWidget *makeItemWidget(ui::Item const &item, GuiWidget const *parent);
void updateItemWidget(GuiWidget &widget, ui::Item const &item);
};

#endif // DENG_CLIENT_CONTEXTWIDGETORGANIZER_H
#endif // DENG_CLIENT_CHILDWIDGETORGANIZER_H
2 changes: 1 addition & 1 deletion doomsday/client/include/ui/framework/data.h
Expand Up @@ -37,7 +37,7 @@ class Item;
*
* Data has ownership of all the items in it.
*
* @see ContextWidgetOrganizer
* @see ChildWidgetOrganizer
*/
class Data
{
Expand Down
48 changes: 44 additions & 4 deletions doomsday/client/include/ui/framework/guiwidgetprivate.h
Expand Up @@ -13,20 +13,36 @@ class Style;
* used (i.e., glInit() and glDeinit() are being called).
*
* Use DENG_GUI_PIMPL() instead of the DENG2_PIMPL() macro.
*
* Note that GuiWidgetPrivate automatically observes the root widget's atlas
* content repositioning, so derived private implementations can just override
* the observer method if necessary.
*/
template <typename PublicType>
class GuiWidgetPrivate : public de::Private<PublicType>
class GuiWidgetPrivate : public de::Private<PublicType>,
DENG2_OBSERVES(de::Atlas, Reposition)
{
public:
typedef GuiWidgetPrivate<PublicType> Base; // shadows de::Private<>::Base

public:
GuiWidgetPrivate(PublicType &i) : de::Private<PublicType>(i) {}
GuiWidgetPrivate(PublicType &i)
: de::Private<PublicType>(i),
_observingAtlas(0)
{}

GuiWidgetPrivate(PublicType *i) : de::Private<PublicType>(i) {}
GuiWidgetPrivate(PublicType *i)
: de::Private<PublicType>(i),
_observingAtlas(0)
{}

virtual ~GuiWidgetPrivate()
{
if(_observingAtlas)
{
_observingAtlas->audienceForReposition -= this;
}

/**
* Ensure that the derived's class's glDeinit() method has been
* called before the private class instance is destroyed. At least
Expand All @@ -38,6 +54,16 @@ class GuiWidgetPrivate : public de::Private<PublicType>
DENG2_ASSERT(!Base::self.isInitialized());
}

void observeRootAtlas() const
{
if(!_observingAtlas)
{
// Automatically start observing the root atlas.
_observingAtlas = &root().atlas();
_observingAtlas->audienceForReposition += this;
}
}

bool hasRoot() const
{
return Base::self.hasRoot();
Expand All @@ -50,12 +76,14 @@ class GuiWidgetPrivate : public de::Private<PublicType>
}

de::AtlasTexture &atlas() const
{
{
observeRootAtlas();
return root().atlas();
}

de::GLUniform &uAtlas() const
{
observeRootAtlas();
return root().uAtlas();
}

Expand All @@ -68,6 +96,18 @@ class GuiWidgetPrivate : public de::Private<PublicType>
{
return Base::self.style();
}

void atlasContentRepositioned(de::Atlas &atlas)
{
if(_observingAtlas == &atlas)
{
// Make sure the new texture coordinates get used by the widget.
Base::self.requestGeometry();
}
}

private:
mutable de::Atlas *_observingAtlas;
};

#define DENG_GUI_PIMPL(ClassName) \
Expand Down
29 changes: 27 additions & 2 deletions doomsday/client/include/ui/widgets/dialogwidget.h
Expand Up @@ -49,6 +49,7 @@ class GuiRootWidget;
* +- heading (LabelWidget; optional)
* +- area (ScrollAreaWidget; contains actual dialog widgets)
* +- buttons (MenuWidget)
* +- extra (MenuWidget; might be empty)
* </pre>
*
* Scrolling is set up so that the dialog height doesn't surpass the view
Expand Down Expand Up @@ -82,7 +83,13 @@ class DialogWidget : public PopupWidget
Reject = 0x4,
Yes = 0x8,
No = 0x10,
Action = 0x20
Action = 0x20,

IdMask = 0xff0000,
Id1 = 0x010000,
Id2 = 0x020000,
Id3 = 0x030000,
Id4 = 0x040000
};
Q_DECLARE_FLAGS(RoleFlags, RoleFlag)

Expand All @@ -109,6 +116,8 @@ class DialogWidget : public PopupWidget
*/
ButtonItem(RoleFlags flags, de::String const &label, de::Action *action);

ButtonItem(RoleFlags flags, de::Image const &image, de::Action *action);

RoleFlags role() const { return _role; }

private:
Expand All @@ -128,7 +137,23 @@ class DialogWidget : public PopupWidget

ScrollAreaWidget &area();

MenuWidget &buttons();
//MenuWidget &buttons();

/**
* Additional buttons of the dialog, laid out opposite to the normal dialog
* buttons. These are used for functionality related to the dialog's content,
* but they don't accept or reject the dialog. For instance, shortcut for
* settings, showing what's new in an update, etc.
*
* @return Widget for dialog's extra buttons.
*/
//MenuWidget &extraButtons();

ui::Data &buttons();

ButtonWidget &buttonWidget(de::String const &label) const;

ButtonWidget *buttonWidget(int roleId) const;

/**
* Shows the dialog and blocks execution until the dialog is closed --
Expand Down
7 changes: 4 additions & 3 deletions doomsday/client/include/ui/widgets/menuwidget.h
Expand Up @@ -20,7 +20,7 @@
#define DENG_CLIENT_MENUWIDGET_H

#include "ui/Data"
#include "ContextWidgetOrganizer"
#include "ChildWidgetOrganizer"
#include "ui/ActionItem"
#include "ui/SubmenuItem"
#include "ui/VariableToggleItem"
Expand All @@ -39,7 +39,7 @@
* MenuWidget::ISortOrder. Sorting affects layout only, not the actual order of
* the children.
*
* MenuWidget uses a ContextWidgetOrganizer to create widgets based on the
* MenuWidget uses a ChildWidgetOrganizer to create widgets based on the
* provided menu items. The organizer can be queried to find widgets matching
* specific items.
*/
Expand Down Expand Up @@ -87,7 +87,8 @@ class MenuWidget : public ScrollAreaWidget
*/
void setItems(ui::Data const &items);

ContextWidgetOrganizer const &organizer() const;
ChildWidgetOrganizer &organizer();
ChildWidgetOrganizer const &organizer() const;

/**
* Returns the number of visible items in the menu. Hidden items are not
Expand Down
6 changes: 3 additions & 3 deletions doomsday/client/src/ui/dialogs/aboutdialog.cpp
Expand Up @@ -113,14 +113,14 @@ AboutDialog::AboutDialog() : DialogWidget("about"), d(new Instance(this))
// Total size of the dialog's content.
area().setContentSize(layout.width(), layout.height() + homepage->rule().height());

buttons().items()
buttons()
<< new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Close"))
<< new DialogButtonItem(DialogWidget::Action, tr("GL"), new SignalAction(this, SLOT(showGLInfo())))
<< new DialogButtonItem(DialogWidget::Action, tr("Audio"), new SignalAction(this, SLOT(showAudioInfo())));

// The popups are anchored to their button.
d->glPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("GL"))->rule(), ui::Up);
d->audioPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("Audio"))->rule(), ui::Up);
d->glPopup->setAnchorAndOpeningDirection(buttonWidget(tr("GL")).rule(), ui::Up);
d->audioPopup->setAnchorAndOpeningDirection(buttonWidget(tr("Audio")).rule(), ui::Up);
}

void AboutDialog::showGLInfo()
Expand Down
2 changes: 1 addition & 1 deletion doomsday/client/src/ui/dialogs/audiosettingsdialog.cpp
Expand Up @@ -110,7 +110,7 @@ AudioSettingsDialog::AudioSettingsDialog(String const &name)

area().setContentSize(layout.width(), layout.height());

buttons().items()
buttons()
<< new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close"))
<< new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"),
new SignalAction(this, SLOT(resetToDefaults())));
Expand Down
2 changes: 1 addition & 1 deletion doomsday/client/src/ui/dialogs/coloradjustmentdialog.cpp
Expand Up @@ -69,7 +69,7 @@ ColorAdjustmentDialog::ColorAdjustmentDialog(String const &name)
: DialogWidget(name, WithHeading), d(new Instance(this))
{
heading().setText(tr("Color Adjustments"));
buttons().items()
buttons()
<< new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close"))
<< new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"),
new SignalAction(this, SLOT(resetToDefaults())));
Expand Down
2 changes: 1 addition & 1 deletion doomsday/client/src/ui/dialogs/inputdialog.cpp
Expand Up @@ -33,7 +33,7 @@ InputDialog::InputDialog(String const &name)
d->editor->setSignalOnEnter(true);
connect(d->editor, SIGNAL(enterPressed(QString)), this, SLOT(accept()));

buttons().items()
buttons()
<< new DialogButtonItem(Default | Accept)
<< new DialogButtonItem(Reject);

Expand Down
2 changes: 1 addition & 1 deletion doomsday/client/src/ui/dialogs/inputsettingsdialog.cpp
Expand Up @@ -137,7 +137,7 @@ InputSettingsDialog::InputSettingsDialog(String const &name)

area().setContentSize(layout.width(), layout.height());

buttons().items()
buttons()
<< new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close"))
<< new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"),
new SignalAction(this, SLOT(resetToDefaults())));
Expand Down
2 changes: 1 addition & 1 deletion doomsday/client/src/ui/dialogs/networksettingsdialog.cpp
Expand Up @@ -60,7 +60,7 @@ NetworkSettingsDialog::NetworkSettingsDialog(String const &name)

area().setContentSize(layout.width(), layout.height());

buttons().items()
buttons()
<< new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close"))
<< new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"),
new SignalAction(this, SLOT(resetToDefaults())));
Expand Down
5 changes: 2 additions & 3 deletions doomsday/client/src/ui/dialogs/renderersettingsdialog.cpp
Expand Up @@ -187,16 +187,15 @@ RendererSettingsDialog::RendererSettingsDialog(String const &name)

area().setContentSize(layout.width(), layout.height());

buttons().items()
buttons()
<< new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close"))
<< new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"),
new SignalAction(this, SLOT(resetToDefaults())))
<< new DialogButtonItem(DialogWidget::Action, tr("Developer"),
new SignalAction(this, SLOT(showDeveloperPopup())));

// Identifiers popup opens from the button.
d->devPopup->setAnchorAndOpeningDirection(
buttons().organizer().itemWidget(tr("Developer"))->rule(), ui::Up);
d->devPopup->setAnchorAndOpeningDirection(buttonWidget(tr("Developer")).rule(), ui::Up);

connect(this, SIGNAL(closed()), d->devPopup, SLOT(close()));
connect(d->appear, SIGNAL(profileEditorRequested()), this, SLOT(editProfile()));
Expand Down

0 comments on commit 4c412af

Please sign in to comment.