M module-apps/DatabaseModel.hpp => module-apps/DatabaseModel.hpp +23 -16
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 17,14 17,13 @@ namespace app
template <class T> class DatabaseModel
{
protected:
- /// Pointer to application that owns the model
- Application *application = nullptr;
- uint32_t recordsCount = 0;
+ Application *application = nullptr;
+ unsigned int recordsCount = 0;
+ int modelIndex = 0;
std::vector<std::shared_ptr<T>> records;
- uint32_t modelIndex = 0;
public:
- DatabaseModel(Application *app) : application{app}, recordsCount{0}
+ explicit DatabaseModel(Application *app) : application{app}, recordsCount{0}
{}
virtual ~DatabaseModel()
@@ 50,7 49,7 @@ namespace app
}
}
- virtual void clear()
+ void clear()
{
records.clear();
recordsCount = 0;
@@ 58,21 57,29 @@ namespace app
std::shared_ptr<T> getRecord(gui::Order order)
{
- auto index = modelIndex;
+ auto index = 0;
+ if (order == gui::Order::Next) {
+ index = modelIndex;
- if (index >= records.size()) {
- return nullptr;
+ modelIndex++;
}
+ else if (order == gui::Order::Previous) {
+ index = records.size() - 1 + modelIndex;
- if (order == gui::Order::Previous) {
- index = records.size() - 1 - modelIndex;
+ modelIndex--;
}
- auto item = records[index];
-
- modelIndex++;
+ if (isIndexValid(index)) {
+ return records[index];
+ }
+ else {
+ return nullptr;
+ }
+ }
- return item;
+ [[nodiscard]] bool isIndexValid(unsigned int index) const noexcept
+ {
+ return index < records.size();
}
};
M module-apps/InternalModel.hpp => module-apps/InternalModel.hpp +16 -15
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 42,25 42,22 @@ namespace app
gui::ListItem *getRecord(gui::Order order)
{
- unsigned int index = 0;
+ auto index = 0;
if (order == gui::Order::Previous) {
- index = internalOffset + internalLimit - 1 - modelIndex;
+ index = internalOffset + internalLimit - 1 + modelIndex;
+
+ modelIndex--;
}
if (order == gui::Order::Next) {
index = internalOffset + modelIndex;
- }
-
- if (isValidIndex(index, order)) {
- return getNextInternalDataElement(index);
+ modelIndex++;
}
- else {
- return nullptr;
- }
+ return getInternalDataElement(index, order);
}
- [[nodiscard]] bool isValidIndex(unsigned int index, gui::Order order) const
+ [[nodiscard]] bool isIndexValid(unsigned int index, gui::Order order) const noexcept
{
return (index < internalData.size()) || (order == gui::Order::Previous && index < internalOffset);
}
@@ 73,11 70,15 @@ namespace app
Item->clearNavigationItem(gui::NavigationDirection::DOWN);
}
- gui::ListItem *getNextInternalDataElement(unsigned int index)
+ [[nodiscard]] gui::ListItem *getInternalDataElement(unsigned int index, gui::Order order)
{
- modelIndex++;
- clearItemProperties(internalData[index]);
- return internalData[index];
+ if (isIndexValid(index, order)) {
+ clearItemProperties(internalData[index]);
+ return internalData[index];
+ }
+ else {
+ return nullptr;
+ }
}
};
M module-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp => module-apps/application-alarm-clock/windows/AlarmClockMainWindow.cpp +2 -1
@@ 48,7 48,8 @@ namespace app::alarmClock
style::alarmClock::window::listView_y,
style::alarmClock::window::listView_w,
style::alarmClock::window::listView_h,
- presenter->getAlarmsItemProvider());
+ presenter->getAlarmsItemProvider(),
+ style::listview::ScrollBarType::Fixed);
alarmsList->focusChangedCallback = [this](gui::Item &) {
onListFilled();
return true;
M module-apps/application-alarm-clock/windows/CustomRepeatWindow.cpp => module-apps/application-alarm-clock/windows/CustomRepeatWindow.cpp +2 -1
@@ 29,7 29,8 @@ namespace app::alarmClock
style::alarmClock::window::listView_y,
style::alarmClock::window::listView_w,
style::alarmClock::window::listView_h,
- presenter->getItemProvider());
+ presenter->getItemProvider(),
+ style::listview::ScrollBarType::None);
setFocusItem(list);
}
M module-apps/application-alarm-clock/windows/NewEditAlarmWindow.cpp => module-apps/application-alarm-clock/windows/NewEditAlarmWindow.cpp +2 -1
@@ 31,7 31,8 @@ namespace app::alarmClock
style::alarmClock::window::listView_y,
style::alarmClock::window::listView_w,
style::alarmClock::window::listView_h,
- presenter->getAlarmsItemProvider());
+ presenter->getAlarmsItemProvider(),
+ style::listview::ScrollBarType::None);
setFocusItem(list);
}
M module-apps/application-calendar/windows/CustomRepeatWindow.cpp => module-apps/application-calendar/windows/CustomRepeatWindow.cpp +2 -1
@@ 39,7 39,8 @@ namespace gui
style::window::calendar::listView_y,
style::window::calendar::listView_w,
style::window::calendar::listView_h,
- customRepeatModel);
+ customRepeatModel,
+ style::listview::ScrollBarType::None);
setFocusItem(list);
}
M module-apps/application-calendar/windows/DayEventsWindow.cpp => module-apps/application-calendar/windows/DayEventsWindow.cpp +4 -3
@@ 47,7 47,7 @@ namespace gui
return false;
}
- dayMonthTitle = item->getDayMonthText();
+ dayMonthTitle = item->getDayMonthText();
filterFrom = item->getDateFilter();
auto filterTill = filterFrom + date::days{1};
dayEventsModel->setFilters(filterFrom, filterTill, dayMonthTitle);
@@ 79,7 79,8 @@ namespace gui
style::window::calendar::listView_y,
style::window::calendar::listView_w,
style::window::calendar::listView_h,
- dayEventsModel);
+ dayEventsModel,
+ style::listview::ScrollBarType::Fixed);
setFocusItem(dayEventsList);
}
@@ 101,7 102,7 @@ namespace gui
rec->date_from = filterFrom;
rec->date_till = filterFrom + std::chrono::hours(style::window::calendar::time::max_hour_24H_mode) +
std::chrono::minutes(style::window::calendar::time::max_minutes);
- auto event = std::make_shared<EventsRecord>(*rec);
+ auto event = std::make_shared<EventsRecord>(*rec);
data->setData(event);
application->switchWindow(
style::window::calendar::name::new_edit_event, gui::ShowMode::GUI_SHOW_INIT, std::move(data));
M module-apps/application-calendar/windows/EventDetailWindow.cpp => module-apps/application-calendar/windows/EventDetailWindow.cpp +2 -1
@@ 38,7 38,8 @@ namespace gui
style::window::calendar::listView_y,
style::window::calendar::listView_w,
style::window::calendar::listView_h,
- eventDetailModel);
+ eventDetailModel,
+ style::listview::ScrollBarType::PreRendered);
setFocusItem(bodyList);
}
M module-apps/application-calendar/windows/NewEditEventWindow.cpp => module-apps/application-calendar/windows/NewEditEventWindow.cpp +3 -2
@@ 35,7 35,8 @@ namespace gui
style::window::calendar::listView_y,
style::window::calendar::listView_w,
style::window::calendar::listView_h,
- newEditEventModel);
+ newEditEventModel,
+ style::listview::ScrollBarType::PreRendered);
setFocusItem(list);
}
@@ 54,7 55,7 @@ namespace gui
if (mode == ShowMode::GUI_SHOW_INIT) {
auto rec = dynamic_cast<EventRecordData *>(data);
if (rec != nullptr) {
- eventRecord = rec->getData();
+ eventRecord = rec->getData();
}
newEditEventModel->loadData(eventRecord);
}
M module-apps/application-calllog/windows/CallLogMainWindow.cpp => module-apps/application-calllog/windows/CallLogMainWindow.cpp +7 -1
@@ 47,7 47,13 @@ namespace gui
bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::open));
bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));
- list = new gui::ListView(this, mainWindow::x, mainWindow::y, mainWindow::w, mainWindow::h, calllogModel);
+ list = new gui::ListView(this,
+ mainWindow::x,
+ mainWindow::y,
+ mainWindow::w,
+ mainWindow::h,
+ calllogModel,
+ style::listview::ScrollBarType::Fixed);
setFocusItem(list);
}
M module-apps/application-meditation/windows/MeditationListViewWindows.cpp => module-apps/application-meditation/windows/MeditationListViewWindows.cpp +7 -2
@@ 22,8 22,13 @@ void MeditationListViewWindow::buildInterface()
{
AppWindow::buildInterface();
model->createData();
- list = new gui::ListView(
- this, listViewWindow::X, listViewWindow::Y, listViewWindow::Width, listViewWindow::Height, model);
+ list = new gui::ListView(this,
+ listViewWindow::X,
+ listViewWindow::Y,
+ listViewWindow::Width,
+ listViewWindow::Height,
+ model,
+ style::listview::ScrollBarType::Fixed);
setFocusItem(list);
bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));
}
M module-apps/application-messages/windows/MessagesMainWindow.cpp => module-apps/application-messages/windows/MessagesMainWindow.cpp +2 -1
@@ 53,7 53,8 @@ namespace gui
msgThreadStyle::ListPositionY,
msgThreadStyle::listWidth,
msgThreadStyle::listHeight,
- threadsModel);
+ threadsModel,
+ style::listview::ScrollBarType::Fixed);
list->setScrollTopMargin(style::margins::small);
list->rebuildList();
M module-apps/application-messages/windows/SMSTemplatesWindow.cpp => module-apps/application-messages/windows/SMSTemplatesWindow.cpp +2 -1
@@ 46,7 46,8 @@ namespace gui
namespace style = style::messages::templates::list;
- list = new gui::ListView(this, style::x, style::y, style::w, style::h, smsTemplateModel);
+ list = new gui::ListView(
+ this, style::x, style::y, style::w, style::h, smsTemplateModel, ::style::listview::ScrollBarType::Fixed);
setFocusItem(list);
}
M module-apps/application-messages/windows/SMSThreadViewWindow.cpp => module-apps/application-messages/windows/SMSThreadViewWindow.cpp +3 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "SMSThreadViewWindow.hpp"
@@ 37,7 37,8 @@ namespace gui
style::messages::smsList::y,
style::messages::smsList::w,
style::messages::smsList::h,
- smsModel);
+ smsModel,
+ style::listview::ScrollBarType::Proportional);
smsList->setOrientation(style::listview::Orientation::BottomTop);
setFocusItem(smsList);
M module-apps/application-messages/windows/SearchResults.cpp => module-apps/application-messages/windows/SearchResults.cpp +2 -1
@@ 37,7 37,8 @@ namespace gui
msgThreadStyle::ListPositionY,
msgThreadStyle::listWidth,
msgThreadStyle::listHeight,
- model);
+ model,
+ style::listview::ScrollBarType::Fixed);
list->setScrollTopMargin(style::margins::small);
setFocusItem(list);
M module-apps/application-music-player/windows/MusicPlayerAllSongsWindow.cpp => module-apps/application-music-player/windows/MusicPlayerAllSongsWindow.cpp +2 -1
@@ 59,7 59,8 @@ namespace gui
musicPlayerStyle::allSongsWindow::y,
musicPlayerStyle::allSongsWindow::w,
musicPlayerStyle::allSongsWindow::h,
- songsModel);
+ songsModel,
+ style::listview::ScrollBarType::Fixed);
auto successCallback = [this](const audio::Volume &volume) {
auto volumeText = audio::GetVolumeText(volume);
M module-apps/application-notes/windows/NoteMainWindow.cpp => module-apps/application-notes/windows/NoteMainWindow.cpp +7 -2
@@ 82,8 82,13 @@ namespace app::notes
windowStyle::search_image::ImageSource);
namespace listStyle = app::notes::style::list;
- list = new gui::ListView(
- this, listStyle::X, listStyle::Y, listStyle::Width, listStyle::Height, presenter->getNotesItemProvider());
+ list = new gui::ListView(this,
+ listStyle::X,
+ listStyle::Y,
+ listStyle::Width,
+ listStyle::Height,
+ presenter->getNotesItemProvider(),
+ ::style::listview::ScrollBarType::Fixed);
list->setPenWidth(listStyle::PenWidth);
list->setPenFocusWidth(listStyle::FocusedPenWidth);
list->focusChangedCallback = [this]([[maybe_unused]] gui::Item &item) {
M module-apps/application-notes/windows/SearchResultsWindow.cpp => module-apps/application-notes/windows/SearchResultsWindow.cpp +7 -2
@@ 34,8 34,13 @@ namespace app::notes
bottomBar->setActive(gui::BottomBar::Side::RIGHT, true);
bottomBar->setText(gui::BottomBar::Side::RIGHT, utils::localize.get(::style::strings::common::back));
- list =
- new gui::ListView(this, style::list::X, style::list::Y, style::list::Width, style::list::Height, listModel);
+ list = new gui::ListView(this,
+ style::list::X,
+ style::list::Y,
+ style::list::Width,
+ style::list::Height,
+ listModel,
+ ::style::listview::ScrollBarType::Fixed);
list->setScrollTopMargin(::style::margins::small);
setFocusItem(list);
}
M module-apps/application-phonebook/windows/PhonebookContactDetails.cpp => module-apps/application-phonebook/windows/PhonebookContactDetails.cpp +2 -1
@@ 36,7 36,8 @@ namespace gui
phonebookStyle::contactDetailsWindow::contactDetailsList::y,
phonebookStyle::contactDetailsWindow::contactDetailsList::w,
phonebookStyle::contactDetailsWindow::contactDetailsList::h,
- contactDetailsModel);
+ contactDetailsModel,
+ style::listview::ScrollBarType::PreRendered);
setFocusItem(bodyList);
}
M module-apps/application-phonebook/windows/PhonebookIceContacts.cpp => module-apps/application-phonebook/windows/PhonebookIceContacts.cpp +2 -1
@@ 32,7 32,8 @@ namespace gui
phonebookStyle::iceContactsWindow::contactsListIce::y,
phonebookStyle::iceContactsWindow::contactsListIce::w,
phonebookStyle::iceContactsWindow::contactsListIce::h,
- phonebookModel);
+ phonebookModel,
+ style::listview::ScrollBarType::Fixed);
setFocusItem(contactsListIce);
M module-apps/application-phonebook/windows/PhonebookNewContact.cpp => module-apps/application-phonebook/windows/PhonebookNewContact.cpp +2 -1
@@ 40,7 40,8 @@ namespace gui
phonebookStyle::newContactWindow::newContactsList::y,
phonebookStyle::newContactWindow::newContactsList::w,
phonebookStyle::newContactWindow::newContactsList::h,
- newContactModel);
+ newContactModel,
+ style::listview::ScrollBarType::PreRendered);
setFocusItem(list);
}
M module-gui/gui/widgets/BoxLayout.cpp => module-gui/gui/widgets/BoxLayout.cpp +5 -0
@@ 100,6 100,11 @@ namespace gui
Item::erase();
}
+ bool BoxLayout::empty() const noexcept
+ {
+ return children.empty();
+ }
+
void BoxLayout::setVisible(bool value, bool previous)
{
visible = value; // maybe use parent setVisible(...)? would be better but which one?
M module-gui/gui/widgets/BoxLayout.hpp => module-gui/gui/widgets/BoxLayout.hpp +1 -0
@@ 97,6 97,7 @@ namespace gui
bool removeWidget(Item *item) override;
bool erase(Item *item) override;
void erase() override;
+ [[nodiscard]] bool empty() const noexcept;
/// add item if it will fit in box, return true on success
/// axis sets direction to define space left in container
template <Axis axis> void addWidget(Item *item);
M module-gui/gui/widgets/ListItemProvider.hpp => module-gui/gui/widgets/ListItemProvider.hpp +2 -2
@@ 26,11 26,11 @@ namespace gui
virtual unsigned int requestRecordsCount() = 0;
- virtual unsigned int getMinimalItemHeight() const = 0;
+ [[nodiscard]] virtual unsigned int getMinimalItemHeight() const = 0;
virtual ListItem *getItem(Order order) = 0;
- virtual void requestRecords(const uint32_t offset, const uint32_t limit) = 0;
+ virtual void requestRecords(uint32_t offset, uint32_t limit) = 0;
};
} // namespace gui
M module-gui/gui/widgets/ListView.cpp => module-gui/gui/widgets/ListView.cpp +228 -45
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ListView.hpp"
@@ 9,8 9,13 @@
namespace gui
{
- ListViewScroll::ListViewScroll(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
- : Rect{parent, x, y, w, h}
+ ListViewScroll::ListViewScroll(Item *parent,
+ unsigned int x,
+ unsigned int y,
+ unsigned int w,
+ unsigned int h,
+ style::listview::ScrollBarType type)
+ : Rect{parent, x, y, w, h}, type(type)
{
setRadius(style::listview::scroll::radius);
@@ 19,29 24,97 @@ namespace gui
activeItem = false;
}
- bool ListViewScroll::shouldShowScroll(unsigned int currentPageSize, unsigned int elementsCount)
+ void ListViewScroll::updateProportional(const ListViewScrollUpdateData &data)
+ {
+ double scrollStep =
+ static_cast<double>((parent->widgetArea.h - data.topMargin)) / static_cast<double>(data.elementsCount);
+
+ auto scrollH = scrollStep * data.listPageSize;
+ auto scrollY = scrollStep * data.startIndex > 0 ? scrollStep * data.startIndex : data.topMargin;
+
+ setArea(BoundingBox(
+ parent->widgetArea.w - style::listview::scroll::margin, scrollY, style::listview::scroll::w, scrollH));
+ }
+
+ void ListViewScroll::updateFixed(const ListViewScrollUpdateData &data)
{
+ auto elementsOnPage = (parent->widgetArea.h - data.topMargin) / data.elementMinimalHeight;
+ pagesCount = data.elementsCount % elementsOnPage == 0 ? data.elementsCount / elementsOnPage
+ : data.elementsCount / elementsOnPage + 1;
+
+ currentPage = data.startIndex / elementsOnPage;
+
+ auto scrollH = (parent->widgetArea.h - data.topMargin) / pagesCount;
+ auto scrollY = scrollH * currentPage > 0 ? scrollH * currentPage : data.topMargin;
+
+ setArea(BoundingBox(
+ parent->widgetArea.w - style::listview::scroll::margin, scrollY, style::listview::scroll::w, scrollH));
+ }
+
+ void ListViewScroll::updatePreRendered(const ListViewScrollUpdateData &data)
+ {
+ if (data.startIndex != storedStartIndex) {
+ if (data.direction == style::listview::Direction::Bottom) {
+ if (data.boundaries == style::listview::Boundaries::Continuous && (data.startIndex == 0)) {
+ currentPage = 0;
+ }
+ else if (currentPage + 1 < pagesCount) {
+ currentPage++;
+ }
+ }
+ else {
+ if (data.boundaries == style::listview::Boundaries::Continuous && storedStartIndex == 0) {
+ currentPage = pagesCount - 1;
+ }
+ else if (currentPage > 0 && storedStartIndex != 0) {
+ currentPage--;
+ }
+ }
+ }
+
+ storedStartIndex = data.startIndex;
+
+ auto scrollH = (parent->widgetArea.h - data.topMargin) / pagesCount;
+ auto scrollY = currentPage * scrollH > 0 ? currentPage * scrollH : data.topMargin;
+
+ setArea(BoundingBox(
+ parent->widgetArea.w - style::listview::scroll::margin, scrollY, style::listview::scroll::w, scrollH));
+ }
+
+ void ListViewScroll::updateStartConditions(const unsigned int index,
+ const unsigned int page,
+ const unsigned int count)
+ {
+ storedStartIndex = index;
+ currentPage = page;
+ pagesCount = count;
+ }
+
+ bool ListViewScroll::shouldShowScroll(unsigned int currentPageSize, unsigned int elementsCount)
+ {
return ((parent->widgetArea.w > style::listview::scroll::min_space) &&
(parent->widgetArea.h > style::listview::scroll::min_space) && currentPageSize < elementsCount);
}
- void ListViewScroll::update(unsigned int startIndex,
- unsigned int currentPageSize,
- unsigned int elementsCount,
- int topMargin)
+ void ListViewScroll::update(const ListViewScrollUpdateData &data)
{
- if (shouldShowScroll(currentPageSize, elementsCount)) {
+ if (shouldShowScroll(data.listPageSize, data.elementsCount)) {
- assert(elementsCount != 0);
- double scrollStep =
- static_cast<double>((parent->widgetArea.h - topMargin)) / static_cast<double>(elementsCount);
-
- auto scrollH = scrollStep * currentPageSize;
- auto scrollY = scrollStep * startIndex > 0 ? scrollStep * startIndex : topMargin;
+ switch (type) {
+ case style::listview::ScrollBarType::Proportional:
+ updateProportional(data);
+ break;
+ case style::listview::ScrollBarType::Fixed:
+ updateFixed(data);
+ break;
+ case style::listview::ScrollBarType::PreRendered:
+ updatePreRendered(data);
+ break;
+ case style::listview::ScrollBarType::None:
+ break;
+ }
- setArea(BoundingBox(
- parent->widgetArea.w - style::listview::scroll::margin, scrollY, style::listview::scroll::w, scrollH));
setVisible(true);
}
else
@@ 56,15 129,16 @@ namespace gui
style::listview::scroll::x,
style::listview::scroll::y,
style::listview::scroll::w,
- style::listview::scroll::h);
+ style::listview::scroll::h,
+ style::listview::ScrollBarType::None);
type = gui::ItemType::LIST;
}
ListView::ListView(Item *parent,
- uint32_t x,
- uint32_t y,
- uint32_t w,
- uint32_t h,
+ unsigned int x,
+ unsigned int y,
+ unsigned int w,
+ unsigned int h,
std::shared_ptr<ListItemProvider> prov,
style::listview::ScrollBarType scrollBarType)
: Rect{parent, x, y, w, h}
@@ 101,7 175,8 @@ namespace gui
style::listview::scroll::x,
style::listview::scroll::y,
style::listview::scroll::w,
- style::listview::scroll::h);
+ style::listview::scroll::h,
+ scrollBarType);
}
setProvider(std::move(prov));
@@ 116,7 191,10 @@ namespace gui
void ListView::setElementsCount(unsigned int count)
{
- elementsCount = count;
+ if (elementsCount != count) {
+ elementsCount = count;
+ onElementsCountChanged();
+ }
}
void ListView::setBoundaries(style::listview::Boundaries value)
@@ 189,24 267,20 @@ namespace gui
{
if (rebuildType == style::listview::RebuildType::Full) {
setStartIndex();
- storedFocusIndex = 0;
+ storedFocusIndex = style::listview::nPos;
}
else if (rebuildType == style::listview::RebuildType::OnOffset) {
if (dataOffset < elementsCount) {
startIndex = dataOffset;
- storedFocusIndex = 0;
+ storedFocusIndex = style::listview::nPos;
}
else {
LOG_ERROR("Requested rebuild on index greater than elements count");
}
}
else if (rebuildType == style::listview::RebuildType::InPlace) {
-
- storedFocusIndex = body->getFocusItemIndex();
-
- if (direction == style::listview::Direction::Top) {
- int position = currentPageSize - 1 - storedFocusIndex;
- storedFocusIndex = std::abs(position);
+ if (!body->empty()) {
+ storedFocusIndex = getFocusItemIndex();
}
}
@@ 216,6 290,18 @@ namespace gui
direction = style::listview::Direction::Bottom;
}
+ unsigned int ListView::getFocusItemIndex()
+ {
+ auto index = body->getFocusItemIndex();
+
+ if (direction == style::listview::Direction::Top) {
+ int position = currentPageSize - 1 - index;
+ index = std::abs(position);
+ }
+
+ return index;
+ }
+
std::shared_ptr<ListItemProvider> ListView::getProvider()
{
return provider;
@@ 263,15 349,19 @@ namespace gui
return;
}
- onElementsCountChanged();
-
clearItems();
addItemsOnPage();
setFocus();
if (scroll) {
- scroll->update(startIndex, currentPageSize, elementsCount, scrollTopMargin);
+ scroll->update(ListViewScrollUpdateData{startIndex,
+ currentPageSize,
+ elementsCount,
+ provider->getMinimalItemHeight(),
+ direction,
+ boundaries,
+ scrollTopMargin});
}
resizeWithScroll();
pageLoaded = true;
@@ 287,10 377,14 @@ namespace gui
void ListView::onProviderDataUpdate()
{
+ if (!renderFullList()) {
+ return;
+ }
+
refresh();
}
- Order ListView::getOrderFromDirection()
+ Order ListView::getOrderFromDirection() const noexcept
{
if (direction == style::listview::Direction::Bottom)
return Order::Next;
@@ 298,6 392,14 @@ namespace gui
return Order::Previous;
}
+ Order ListView::getOppositeOrderFromDirection() const noexcept
+ {
+ if (direction == style::listview::Direction::Bottom)
+ return Order::Previous;
+
+ return Order::Next;
+ }
+
void ListView::setStartIndex()
{
if (orientation == style::listview::Orientation::TopBottom) {
@@ 311,7 413,6 @@ namespace gui
void ListView::recalculateStartIndex()
{
if (direction == style::listview::Direction::Top) {
-
startIndex = startIndex < currentPageSize ? 0 : startIndex - currentPageSize;
}
}
@@ 324,6 425,8 @@ namespace gui
startIndex == 0) {
if (body->getSizeLeft() > provider->getMinimalItemHeight()) {
focusOnLastItem = true;
+
+ checkFullRenderRequirement();
rebuildList();
}
}
@@ 332,6 435,8 @@ namespace gui
startIndex + currentPageSize == elementsCount) {
if (body->getSizeLeft() > provider->getMinimalItemHeight()) {
focusOnLastItem = true;
+
+ checkFullRenderRequirement();
rebuildList();
}
}
@@ 357,7 462,11 @@ namespace gui
body->addWidget(item);
- if (item->visible != true) {
+ if (!item->visible) {
+ // In case model is tracking internal indexes -> undo last get.
+ if (requestFullListRender) {
+ provider->getItem(getOppositeOrderFromDirection());
+ }
break;
}
@@ 367,11 476,75 @@ namespace gui
recalculateStartIndex();
}
+ void ListView::checkFullRenderRequirement()
+ {
+ if (scroll && scroll->type == style::listview::ScrollBarType::PreRendered) {
+ requestFullListRender = true;
+ }
+ }
+
+ bool ListView::renderFullList()
+ {
+ if (!requestFullListRender) {
+ return true;
+ }
+
+ if (elementsCount != 0 && !requestCompleteData) {
+ requestCompleteData = true;
+ provider->requestRecords(0, elementsCount);
+ return false;
+ }
+
+ if (requestCompleteData) {
+
+ auto page = 0;
+ auto pageStartIndex = 0;
+
+ clearItems();
+
+ while (true) {
+
+ addItemsOnPage();
+
+ if (currentPageSize == 0) {
+ break;
+ }
+
+ if (currentPageSize + pageStartIndex == elementsCount) {
+ break;
+ }
+
+ page += 1;
+ pageStartIndex += currentPageSize;
+
+ clearItems();
+ }
+
+ clearItems();
+ requestCompleteData = false;
+ requestFullListRender = false;
+
+ if (lastRebuildRequest.first == style::listview::RebuildType::Full) {
+ if (orientation == style::listview::Orientation::TopBottom) {
+ scroll->updateStartConditions(startIndex, 0, page + 1);
+ }
+ else {
+ scroll->updateStartConditions(startIndex, page, page + 1);
+ }
+ }
+
+ reSendLastRebuildRequest();
+ return false;
+ }
+
+ return true;
+ } // namespace gui
+
void ListView::setFocus()
{
setFocusItem(body);
- if (storedFocusIndex != 0) {
+ if (storedFocusIndex != style::listview::nPos) {
if (!body->setFocusOnElement(storedFocusIndex)) {
body->setFocusOnLastElement();
}
@@ 393,6 566,8 @@ namespace gui
else if (notEmptyListCallback) {
notEmptyListCallback();
}
+
+ checkFullRenderRequirement();
}
bool ListView::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
@@ 400,7 575,13 @@ namespace gui
Rect::onDimensionChanged(oldDim, newDim);
body->setSize(body->getWidth(), newDim.h);
if (scroll) {
- scroll->update(startIndex, currentPageSize, elementsCount, scrollTopMargin);
+ scroll->update(ListViewScrollUpdateData{startIndex,
+ currentPageSize,
+ elementsCount,
+ provider->getMinimalItemHeight(),
+ direction,
+ boundaries,
+ scrollTopMargin});
}
return true;
@@ 430,7 611,8 @@ namespace gui
startIndex = startIndex < diff ? 0 : startIndex - diff;
}
- fillFirstPage();
+ checkFullRenderRequirement();
+ rebuildList();
}
}
@@ 476,7 658,7 @@ namespace gui
direction = style::listview::Direction::Bottom;
body->setReverseOrder(false);
pageLoaded = false;
- storedFocusIndex = 0;
+ storedFocusIndex = style::listview::nPos;
provider->requestRecords(startIndex, calculateLimit());
return true;
@@ 489,9 671,9 @@ namespace gui
if (startIndex == 0 && boundaries == style::listview::Boundaries::Continuous) {
- topFetchIndex = elementsCount - (elementsCount % currentPageSize);
startIndex = elementsCount;
- limit = calculateLimit(style::listview::Direction::Top) - topFetchIndex;
+ topFetchIndex = elementsCount - calculateLimit(style::listview::Direction::Top);
+ limit = calculateLimit(style::listview::Direction::Top);
}
else if (startIndex == 0 && boundaries == style::listview::Boundaries::Fixed) {
@@ 508,9 690,10 @@ namespace gui
direction = style::listview::Direction::Top;
body->setReverseOrder(true);
pageLoaded = false;
- storedFocusIndex = 0;
+ storedFocusIndex = style::listview::nPos;
provider->requestRecords(topFetchIndex, limit);
return true;
}
+
} /* namespace gui */
M module-gui/gui/widgets/ListView.hpp => module-gui/gui/widgets/ListView.hpp +46 -8
@@ 16,20 16,50 @@ namespace gui
using rebuildRequest = std::pair<style::listview::RebuildType, unsigned int>;
+ struct ListViewScrollUpdateData
+ {
+ const unsigned int startIndex;
+ const unsigned int listPageSize;
+ const unsigned int elementsCount;
+ const unsigned int elementMinimalHeight;
+ const style::listview::Direction direction;
+ const style::listview::Boundaries boundaries;
+ const int topMargin;
+ };
+
class ListViewScroll : public Rect
{
+ private:
+ unsigned int storedStartIndex = 0;
+ unsigned int currentPage = style::listview::nPos;
+ unsigned int pagesCount = 0;
+
+ void updateProportional(const ListViewScrollUpdateData &data);
+ void updateFixed(const ListViewScrollUpdateData &data);
+ void updatePreRendered(const ListViewScrollUpdateData &data);
+
public:
- ListViewScroll(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
+ style::listview::ScrollBarType type = style::listview::ScrollBarType::None;
+
+ ListViewScroll(Item *parent,
+ unsigned int x,
+ unsigned int y,
+ unsigned int w,
+ unsigned int h,
+ style::listview::ScrollBarType type);
bool shouldShowScroll(unsigned int listPageSize, unsigned int elementsCount);
- void update(unsigned int startIndex, unsigned int listPageSize, unsigned int elementsCount, int topMargin);
+ void updateStartConditions(const unsigned int storedStartIndex,
+ const unsigned int currentPage,
+ const unsigned int pagesCount);
+ void update(const ListViewScrollUpdateData &data);
};
class ListView : public Rect
{
protected:
unsigned int startIndex = 0;
- unsigned int storedFocusIndex = 0;
+ unsigned int storedFocusIndex = style::listview::nPos;
unsigned int elementsCount = 0;
std::shared_ptr<ListItemProvider> provider = nullptr;
VBox *body = nullptr;
@@ 48,6 78,12 @@ namespace gui
void clearItems();
virtual void addItemsOnPage();
+
+ bool requestCompleteData = false;
+ bool requestFullListRender = false;
+ bool renderFullList();
+ void checkFullRenderRequirement();
+
void setFocus();
void refresh();
void resizeWithScroll();
@@ 55,11 91,13 @@ namespace gui
void fillFirstPage();
void setStartIndex();
void recalculateOnBoxRequestedResize();
+ [[nodiscard]] unsigned int getFocusItemIndex();
/// Default empty list to inform that there is no elements - callback should be override in applications
void onElementsCountChanged();
unsigned int calculateMaxItemsOnPage();
unsigned int calculateLimit(style::listview::Direction value = style::listview::Direction::Bottom);
- Order getOrderFromDirection();
+ [[nodiscard]] Order getOrderFromDirection() const noexcept;
+ [[nodiscard]] Order getOppositeOrderFromDirection() const noexcept;
virtual bool requestNextPage();
virtual bool requestPreviousPage();
void setup(style::listview::RebuildType rebuildType, unsigned int dataOffset = 0);
@@ 67,10 105,10 @@ namespace gui
public:
ListView();
ListView(Item *parent,
- uint32_t x,
- uint32_t y,
- uint32_t w,
- uint32_t h,
+ unsigned int x,
+ unsigned int y,
+ unsigned int w,
+ unsigned int h,
std::shared_ptr<ListItemProvider> prov,
style::listview::ScrollBarType scrollType = style::listview::ScrollBarType::Proportional);
~ListView();
M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +9 -1
@@ 3,6 3,7 @@
#pragma once
+#include <limits>
#include <gui/core/Color.hpp>
#include <gui/Common.hpp>
#include <Alignment.hpp>
@@ 178,6 179,8 @@ namespace style
namespace listview
{
+ inline constexpr auto nPos = std::numeric_limits<unsigned int>::max();
+
/// Possible List boundaries handling types
enum class Boundaries
{
@@ 208,7 211,12 @@ namespace style
{
None, ///< None - list without scroll bar (but with scrolling).
Proportional, ///< Proportional - scroll bar size calculated based on elements count in model and currently
- ///< displayed number of elements.
+ ///< displayed number of elements. Use with large unequal heights lists elements.
+ Fixed, ///< Fixed - scroll bar size calculated based on fixed equal elements sizes in list.
+ ///< Use when all elements have equal heights.
+ PreRendered ///< PreRendered - scroll bar size calculated based on pre rendered pages on whole list. Use
+ ///< when elements are not equal heights but there are few of them as its renders whole
+ ///< context and can be time consuming.
};
enum class Orientation
M module-gui/test/test-google/test-gui-listview.cpp => module-gui/test/test-google/test-gui-listview.cpp +7 -7
@@ 239,11 239,11 @@ TEST_F(ListViewTesting, Continuous_Type_Test)
moveNTimes(1, style::listview::Direction::Top);
ASSERT_TRUE(testListView->listBorderReached) << "Navigate top by one - page should change to last page";
testListView->listBorderReached = false;
- ASSERT_EQ(4, testListView->currentPageSize) << "4 elements should be displayed";
+ ASSERT_EQ(6, testListView->currentPageSize) << "6 elements should be displayed";
ASSERT_EQ(9, dynamic_cast<gui::TestListItem *>(testListView->body->children.front())->ID)
<< "First element ID should be 9";
- ASSERT_EQ(6, dynamic_cast<gui::TestListItem *>(testListView->body->children.back())->ID)
+ ASSERT_EQ(3, dynamic_cast<gui::TestListItem *>(testListView->body->children.back())->ID)
<< "Last element ID should be 3 (9 - 6)";
ASSERT_EQ(style::listview::Direction::Top, testListView->direction) << "List Direction should be Top";
@@ 251,11 251,11 @@ TEST_F(ListViewTesting, Continuous_Type_Test)
ASSERT_TRUE(testListView->listBorderReached) << "Navigate top by page size - page should change";
testListView->listBorderReached = false;
ASSERT_EQ(6, testListView->currentPageSize) << "6 elements should be displayed";
- ASSERT_EQ(5, dynamic_cast<gui::TestListItem *>(testListView->body->children.front())->ID)
- << "First element ID should be 5";
- ASSERT_EQ(0, dynamic_cast<gui::TestListItem *>(testListView->body->children.back())->ID)
- << "Last element ID should be 0";
- ASSERT_EQ(style::listview::Direction::Top, testListView->direction) << "List Direction should be Top";
+ ASSERT_EQ(0, dynamic_cast<gui::TestListItem *>(testListView->body->children.front())->ID)
+ << "First element ID should be 0";
+ ASSERT_EQ(6, dynamic_cast<gui::TestListItem *>(testListView->body->children.back())->ID)
+ << "Last element ID should be 6";
+ ASSERT_EQ(style::listview::Direction::Bottom, testListView->direction) << "List Direction should be Bottom";
moveNTimes(1, style::listview::Direction::Bottom);
ASSERT_TRUE(testListView->listBorderReached) << "Navigate bot by one - page should change";