From 7015f1cd17898d861ec12c25b212b41f3517c45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Sun, 5 Jun 2016 15:36:30 +0300 Subject: [PATCH] UI|Client: Virtualized items for package lists There is typically a large number of packages available, so virtualizing the list contents makes things much faster. The package item heights are no longer animated so they'll work more nicely with virtualization. --- .../include/ui/widgets/homeitemwidget.h | 11 +++++- .../include/ui/widgets/packageswidget.h | 3 ++ .../src/ui/home/gamepanelbuttonwidget.cpp | 6 ++-- .../client/src/ui/home/panelbuttonwidget.cpp | 2 +- .../client/src/ui/widgets/homeitemwidget.cpp | 18 +++++++--- .../client/src/ui/widgets/packageswidget.cpp | 34 +++++++++++++++---- 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/doomsday/apps/client/include/ui/widgets/homeitemwidget.h b/doomsday/apps/client/include/ui/widgets/homeitemwidget.h index a3afbcd25a..8c7b0e660d 100644 --- a/doomsday/apps/client/include/ui/widgets/homeitemwidget.h +++ b/doomsday/apps/client/include/ui/widgets/homeitemwidget.h @@ -35,7 +35,15 @@ class HomeItemWidget : public de::GuiWidget, public de::IAssetGroup Q_OBJECT public: - HomeItemWidget(de::String const &name = ""); + enum Flag + { + NonAnimatedHeight = 0, + AnimatedHeight = 0x1, + }; + Q_DECLARE_FLAGS(Flags, Flag) + +public: + HomeItemWidget(Flags flags = AnimatedHeight, de::String const &name = ""); de::AssetGroup &assets() override; @@ -97,6 +105,7 @@ class HomeItemWidget : public de::GuiWidget, public de::IAssetGroup DENG2_PRIVATE(d) }; +Q_DECLARE_OPERATORS_FOR_FLAGS(HomeItemWidget::Flags) Q_DECLARE_OPERATORS_FOR_FLAGS(HomeItemWidget::LogoFlags) #endif // DENG_CLIENT_UI_HOME_HOMEITEMWIDGET_H diff --git a/doomsday/apps/client/include/ui/widgets/packageswidget.h b/doomsday/apps/client/include/ui/widgets/packageswidget.h index 727ef3c189..69325c5715 100644 --- a/doomsday/apps/client/include/ui/widgets/packageswidget.h +++ b/doomsday/apps/client/include/ui/widgets/packageswidget.h @@ -76,6 +76,9 @@ class PackagesWidget : public de::GuiWidget, public de::IPersistent de::LineEditWidget &searchTermsEditor(); + // Events. + void initialize(); + // Implements IPersistent. void operator >> (de::PersistentState &toState) const; void operator << (de::PersistentState const &fromState); diff --git a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp index 1e5289ebe1..113bc267ea 100644 --- a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp @@ -152,14 +152,14 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) //- ChildWidgetOrganizer::IFilter --------------------------------------------- - bool isItemAccepted(ChildWidgetOrganizer const &, - ui::Data const &data, ui::Data::Pos pos) const + bool isItemAccepted(ChildWidgetOrganizer const &, ui::Data const &, + ui::Item const &it) const { // User-created profiles currently have no saves associated with them. if (gameProfile.isUserCreated()) return false; // Only saved sessions for this game are to be included. - auto const &item = data.at(pos).as(); + auto const &item = it.as(); return item.gameId() == gameProfile.game(); } }; diff --git a/doomsday/apps/client/src/ui/home/panelbuttonwidget.cpp b/doomsday/apps/client/src/ui/home/panelbuttonwidget.cpp index 2a28b445e1..36a0b8233b 100644 --- a/doomsday/apps/client/src/ui/home/panelbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/home/panelbuttonwidget.cpp @@ -32,7 +32,7 @@ DENG_GUI_PIMPL(PanelButtonWidget) }; PanelButtonWidget::PanelButtonWidget(String const &name) - : HomeItemWidget(name) + : HomeItemWidget(AnimatedHeight, name) , d(new Instance(this)) { setBehavior(Focusable); diff --git a/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp b/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp index 46f56c76dd..f28cb2b096 100644 --- a/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp @@ -185,7 +185,7 @@ DENG_GUI_PIMPL(HomeItemWidget) } }; -HomeItemWidget::HomeItemWidget(String const &name) +HomeItemWidget::HomeItemWidget(Flags flags, String const &name) : GuiWidget(name) , d(new Instance(this)) { @@ -196,16 +196,24 @@ HomeItemWidget::HomeItemWidget(String const &name) style().fonts().font("default").height() + style().fonts().font("default").lineSpacing(); - AutoRef smoothHeight(new AnimationRule(d->label->rule().height(), 0.3)); + AutoRef height; + if(flags.testFlag(AnimatedHeight)) + { + height.reset(new AnimationRule(d->label->rule().height(), 0.3)); + } + else + { + height.reset(d->label->rule().height()); + } d->background->rule() .setInput(Rule::Top, rule().top()) - .setInput(Rule::Height, smoothHeight) + .setInput(Rule::Height, height) .setInput(Rule::Left, d->icon->rule().right()) .setInput(Rule::Right, rule().right()); d->icon->rule() - .setSize(iconSize, smoothHeight) + .setSize(iconSize, height) .setInput(Rule::Left, rule().left()) .setInput(Rule::Top, rule().top()); d->icon->set(Background(Background::BorderGlow, @@ -218,7 +226,7 @@ HomeItemWidget::HomeItemWidget(String const &name) d->label->margins().setRight(*d->labelRightMargin + rule("gap")); // Use an animated height rule for smoother list layout behavior. - rule().setInput(Rule::Height, smoothHeight); + rule().setInput(Rule::Height, height); } AssetGroup &HomeItemWidget::assets() diff --git a/doomsday/apps/client/src/ui/widgets/packageswidget.cpp b/doomsday/apps/client/src/ui/widgets/packageswidget.cpp index 0a7dec2072..59715e1afb 100644 --- a/doomsday/apps/client/src/ui/widgets/packageswidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/packageswidget.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ DENG_GUI_PIMPL(PackagesWidget) , public ChildWidgetOrganizer::IFilter , public ChildWidgetOrganizer::IWidgetFactory { + LoopCallback mainCall; LineEditWidget *search; ButtonWidget *clearSearch; HomeMenuWidget *menu; @@ -135,7 +137,8 @@ DENG_GUI_PIMPL(PackagesWidget) { public: PackageListWidget(PackageItem const &item, PackagesWidget &owner) - : _owner(owner) + : HomeItemWidget(NonAnimatedHeight) // virtualized, so don't make things difficult + , _owner(owner) , _item(&item) { icon().setImageFit(ui::FitToSize | ui::OriginalAspectRatio); @@ -352,6 +355,8 @@ DENG_GUI_PIMPL(PackagesWidget) .setInput(Rule::Top, search->rule().bottom()); menu->organizer().setWidgetFactory(*this); menu->organizer().setFilter(*this); + menu->setVirtualizationEnabled(true, rule("gap").valuei()*2 + rule("unit").valuei() + + int(style().fonts().font("default").height().value()*3)); QObject::connect(search, &LineEditWidget::editorContentChanged, [this] () { updateFilterTerms(); }); @@ -359,6 +364,12 @@ DENG_GUI_PIMPL(PackagesWidget) [this] () { focusFirstListedPackge(); }); } + ~Instance() + { + // Private instance deleted before child widgets. + menu->organizer().unsetFilter(); + } + void populate() { StringList packages = App::packageLoader().findAllPackages(); @@ -405,8 +416,13 @@ DENG_GUI_PIMPL(PackagesWidget) void updateFilterTerms() { - /// @todo Parse quoted terms. -jk - setFilterTerms(search->text().strip().split(QRegExp("\\s"), QString::SkipEmptyParts)); + // Refiltering will potentially alter the widget tree, so doing it during + // event handling is not a great idea. + mainCall.enqueue([this] () + { + /// @todo Parse quoted terms. -jk + setFilterTerms(search->text().strip().split(QRegExp("\\s"), QString::SkipEmptyParts)); + }); } void setFilterTerms(QStringList const &terms) @@ -438,10 +454,10 @@ DENG_GUI_PIMPL(PackagesWidget) return true; } - bool isItemAccepted(ChildWidgetOrganizer const &, - ui::Data const &data, ui::Data::Pos pos) const + bool isItemAccepted(ChildWidgetOrganizer const &, ui::Data const &, + ui::Item const &it) const { - auto &item = data.at(pos).as(); + auto &item = it.as(); // The terms are looked in: // - title @@ -569,6 +585,12 @@ LineEditWidget &PackagesWidget::searchTermsEditor() return *d->search; } +void PackagesWidget::initialize() +{ + GuiWidget::initialize(); + d->menu->organizer().setVisibleArea(root().viewTop(), root().viewBottom()); +} + void PackagesWidget::operator >> (PersistentState &toState) const { if (name().isEmpty()) return;