diff --git a/doomsday/client/data/defaultstyle.pack/graphics/game-libdoom.png b/doomsday/client/data/defaultstyle.pack/graphics/game-libdoom.png index ddeed3ff3a..7b43da46fb 100644 Binary files a/doomsday/client/data/defaultstyle.pack/graphics/game-libdoom.png and b/doomsday/client/data/defaultstyle.pack/graphics/game-libdoom.png differ diff --git a/doomsday/client/data/defaultstyle.pack/graphics/game-libheretic.png b/doomsday/client/data/defaultstyle.pack/graphics/game-libheretic.png index f3bf20af51..c3ad783617 100644 Binary files a/doomsday/client/data/defaultstyle.pack/graphics/game-libheretic.png and b/doomsday/client/data/defaultstyle.pack/graphics/game-libheretic.png differ diff --git a/doomsday/client/data/defaultstyle.pack/graphics/game-libhexen.png b/doomsday/client/data/defaultstyle.pack/graphics/game-libhexen.png index f52fd0bc7f..ac0c5eaf01 100644 Binary files a/doomsday/client/data/defaultstyle.pack/graphics/game-libhexen.png and b/doomsday/client/data/defaultstyle.pack/graphics/game-libhexen.png differ diff --git a/doomsday/client/include/ui/widgets/menuwidget.h b/doomsday/client/include/ui/widgets/menuwidget.h index 4754dda475..6d55c0657e 100644 --- a/doomsday/client/include/ui/widgets/menuwidget.h +++ b/doomsday/client/include/ui/widgets/menuwidget.h @@ -28,9 +28,30 @@ * One of the dimensions of the grid can be configured to use ui::Expand * policy, but then the child widgets must manage their size on that axis by * themselves. + * + * A sort order for the items can be optionally defined using + * MenuWidget::ISortOrder. Sorting affects layout only, not the actual order of + * the children. */ class MenuWidget : public ScrollAreaWidget { +public: + class ISortOrder + { + public: + virtual ~ISortOrder() {} + + /** + * Determines the sort order for a pair of menu items. + * + * @param a First menu item. + * @param b Second menu item. + * + * @return -1 if a < b; +1 if a > b; 0 if equal. + */ + virtual int compareMenuItemsForSorting(Widget const &a, Widget const &b) const = 0; + }; + public: MenuWidget(de::String const &name = ""); @@ -57,6 +78,13 @@ class MenuWidget : public ScrollAreaWidget void setGridSize(int columns, ui::SizePolicy columnPolicy, int rows, ui::SizePolicy rowPolicy); + /** + * Sets the sort order for item layout. + * + * @param sorting Sort order object. MenuWidget takes ownership. + */ + void setLayoutSortOrder(ISortOrder *sorting); + ButtonWidget *addItem(de::String const &styledText, de::Action *action = 0); ButtonWidget *addItem(de::Image const &image, de::String const &styledText, de::Action *action = 0); diff --git a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp index 93ecc6296a..8d2b0ef9f8 100644 --- a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp +++ b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp @@ -33,10 +33,25 @@ DENG2_OBSERVES(App, StartupComplete) typedef QMap Buttons; Buttons buttons; + /** + * Sorts the game buttons by label text. + */ + struct Sorting : public ISortOrder + { + int compareMenuItemsForSorting(Widget const &a, Widget const &b) const + { + ButtonWidget const &x = a.as(); + ButtonWidget const &y = b.as(); + return x.text().compareWithoutCase(y.text()); + } + }; + Instance(Public *i) : Base(i) { App_Games().audienceForAddition += this; App::app().audienceForStartupComplete += this; + + self.setLayoutSortOrder(new Sorting); } ~Instance() diff --git a/doomsday/client/src/ui/widgets/menuwidget.cpp b/doomsday/client/src/ui/widgets/menuwidget.cpp index e22074eb9d..0d0dae0165 100644 --- a/doomsday/client/src/ui/widgets/menuwidget.cpp +++ b/doomsday/client/src/ui/widgets/menuwidget.cpp @@ -24,6 +24,8 @@ using namespace ui; DENG2_PIMPL(MenuWidget) { bool needLayout; + QScopedPointer sorting; + WidgetList sortedChildren; SizePolicy colPolicy; SizePolicy rowPolicy; @@ -105,6 +107,8 @@ DENG2_PIMPL(MenuWidget) Vector2i size; int ord = 0; + DENG2_ASSERT(sortedChildren.size() == int(self.childCount())); + foreach(Widget *i, self.Widget::children()) { if(isVisibleItem(i)) @@ -116,10 +120,29 @@ DENG2_PIMPL(MenuWidget) return size; } + // Functor for quicksort comparisons. + struct Sorter { + Instance &d; + Sorter(Instance *inst) : d(*inst) {} + bool operator () (Widget const *a, Widget const *b) const { + DENG2_ASSERT(!d.sorting.isNull()); + return d.sorting->compareMenuItemsForSorting(*a, *b) < 0; + } + }; + + void prepareSortedChildren() + { + sortedChildren = self.Widget::children(); + if(!sorting.isNull()) + { + qSort(sortedChildren.begin(), sortedChildren.end(), Sorter(this)); + } + } + GuiWidget *findItem(int col, int row) const { int ord = 0; - foreach(Widget *i, self.Widget::children()) + foreach(Widget *i, sortedChildren) { if(isVisibleItem(i)) { @@ -234,6 +257,12 @@ void MenuWidget::setGridSize(int columns, ui::SizePolicy columnPolicy, d->needLayout = true; } +void MenuWidget::setLayoutSortOrder(ISortOrder *sorting) +{ + d->sorting.reset(sorting); + d->needLayout = true; +} + ButtonWidget *MenuWidget::addItem(String const &styledText, Action *action) { return addItem(Image(), styledText, action); @@ -278,7 +307,8 @@ int MenuWidget::count() const void MenuWidget::updateLayout() { - //qDebug() << path().toString() << "Menu has" << d->countVisible() << "visible items"; + // Sort children again. + d->prepareSortedChildren(); Rule const *baseVert = holdRef(&contentRule().top()); diff --git a/doomsday/client/src/ui/widgets/popupmenuwidget.cpp b/doomsday/client/src/ui/widgets/popupmenuwidget.cpp index 0c4405cc55..9a9cd0cf7e 100644 --- a/doomsday/client/src/ui/widgets/popupmenuwidget.cpp +++ b/doomsday/client/src/ui/widgets/popupmenuwidget.cpp @@ -150,9 +150,9 @@ void PopupMenuWidget::preparePopupForOpening() PopupWidget::preparePopupForOpening(); // Redo the layout. + menu().updateLayout(); menu().rule().setInput(Rule::Width, *refless(menu().newColumnWidthRule(0)) + 2 * margin()); - menu().updateLayout(); } void PopupMenuWidget::popupClosing()