diff --git a/escher/Makefile b/escher/Makefile index 12224b3d3..5889d1ed6 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -12,6 +12,7 @@ objs += $(addprefix escher/src/,\ responder.o\ scroll_view.o\ scroll_view_indicator.o\ + simple_table_view_data_source.o\ solid_color_view.o\ stack_view.o\ stack_view_controller.o\ @@ -22,6 +23,7 @@ objs += $(addprefix escher/src/,\ tab_view_controller.o\ table_view.o\ table_view_cell.o\ + table_view_data_source.o\ text_field.o\ text_view.o\ tiled_view.o\ diff --git a/escher/include/escher.h b/escher/include/escher.h index 42ea1465d..a071a4f9e 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/list_view.h b/escher/include/escher/list_view.h index f908d5514..99c220989 100644 --- a/escher/include/escher/list_view.h +++ b/escher/include/escher/list_view.h @@ -1,9 +1,9 @@ #ifndef ESCHER_LIST_VIEW_H #define ESCHER_LIST_VIEW_H -#include +#include -class ListViewDataSource : public TableViewDataSource{ +class ListViewDataSource : public SimpleTableViewDataSource{ public: int numberOfColumns() override; KDCoordinate cellWidth() override; diff --git a/escher/include/escher/scroll_view.h b/escher/include/escher/scroll_view.h index f55f529c8..4380d59ea 100644 --- a/escher/include/escher/scroll_view.h +++ b/escher/include/escher/scroll_view.h @@ -11,6 +11,8 @@ public: void setContentOffset(KDPoint offset); KDPoint contentOffset(); + KDCoordinate topMargin() const; + KDCoordinate leftMargin() const; void drawRect(KDContext * ctx, KDRect rect) const override; protected: KDCoordinate maxContentWidthDisplayableWithoutScrolling(); diff --git a/escher/include/escher/simple_table_view_data_source.h b/escher/include/escher/simple_table_view_data_source.h new file mode 100644 index 000000000..ae9257183 --- /dev/null +++ b/escher/include/escher/simple_table_view_data_source.h @@ -0,0 +1,23 @@ +#ifndef ESCHER_SIMPLE_TABLE_VIEW_DATA_SOURCE_H +#define ESCHER_SIMPLE_TABLE_VIEW_DATA_SOURCE_H + +#include + +class SimpleTableViewDataSource : public TableViewDataSource { +public: + virtual KDCoordinate cellHeight() = 0; + virtual KDCoordinate cellWidth() = 0; + KDCoordinate columnWidth(int i) override; + KDCoordinate rowHeight(int j) override; + KDCoordinate cumulatedWidthFromIndex(int i) override; + KDCoordinate cumulatedHeightFromIndex(int j) override; + int indexFromCumulatedWidth(KDCoordinate offsetX) override; + int indexFromCumulatedHeight(KDCoordinate offsetY) override; + virtual View * reusableCell(int index) = 0; + virtual int reusableCellCount() = 0; + View * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + int typeAtLocation(int i, int j) override; +}; + +#endif diff --git a/escher/include/escher/table_view.h b/escher/include/escher/table_view.h index beaae14c2..f50649101 100644 --- a/escher/include/escher/table_view.h +++ b/escher/include/escher/table_view.h @@ -2,25 +2,15 @@ #define ESCHER_TABLE_VIEW_H #include - -class TableViewDataSource { -public: - virtual int numberOfRows() = 0; - virtual int numberOfColumns() = 0; - virtual void willDisplayCellAtLocation(View * cell, int x, int y); - virtual KDCoordinate cellHeight() = 0; - virtual KDCoordinate cellWidth() = 0; - virtual View * reusableCell(int index) = 0; - virtual int reusableCellCount() = 0; -}; +#include class TableView : public ScrollView { public: - TableView(TableViewDataSource * dataSource, KDCoordinate topMargin = 0, KDCoordinate rightMargin = 0, - KDCoordinate bottomMargin = 0, KDCoordinate leftMargin = 0); + TableView(TableViewDataSource * dataSource, KDCoordinate topMargin = 0, + KDCoordinate rightMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate leftMargin = 0); - void scrollToCell(int x, int y); - View * cellAtLocation(int x, int y); + void scrollToCell(int i, int j); + View * cellAtLocation(int i, int j); protected: #if ESCHER_VIEW_LOGGING const char * className() const override; @@ -32,8 +22,8 @@ private: KDCoordinate height() const; KDCoordinate width() const; - void scrollToCell(int x, int y) const; - View * cellAtLocation(int x, int y); + void scrollToCell(int i, int j) const; + View * cellAtLocation(int i, int j); protected: #if ESCHER_VIEW_LOGGING const char * className() const override; @@ -45,7 +35,11 @@ private: /* realCellWidth enables to handle list view for which * TableViewDataSource->cellWidht = 0 */ - KDCoordinate realCellWidth() const; + KDCoordinate columnWidth(int x) const; + /* These two methods transform an index (of subview for instance) into + * coordinates that refer to the data source entire table */ + int absoluteColumnNumberFromSubviewIndex(int index) const; + int absoluteRowNumberFromSubviewIndex(int index) const; int numberOfFullyDisplayableRows() const; int numberOfFullyDisplayableColumns() const; int numberOfDisplayableRows() const; @@ -56,6 +50,10 @@ private: bool columnAtIndexIsBeforeFullyVisibleRange(int index) const; bool rowAtIndexIsAfterFullyVisibleRange(int index) const; bool columnAtIndexIsAfterFullyVisibleRange(int index) const; + int typeOfSubviewAtIndex(int index) const; + /* This method transform a index (of subview for instance) into an index + * refering to the set of cells of type "type". */ + int typeIndexFromSubviewIndex(int index, int type) const; TableView * m_tableView; TableViewDataSource * m_dataSource; }; diff --git a/escher/include/escher/table_view_data_source.h b/escher/include/escher/table_view_data_source.h new file mode 100644 index 000000000..62dcffc2a --- /dev/null +++ b/escher/include/escher/table_view_data_source.h @@ -0,0 +1,28 @@ +#ifndef ESCHER_TABLE_VIEW_DATA_SOURCE_H +#define ESCHER_TABLE_VIEW_DATA_SOURCE_H + +#include + +class TableViewDataSource { +public: + virtual int numberOfRows() = 0; + virtual int numberOfColumns() = 0; + virtual void willDisplayCellAtLocation(View * cell, int i, int j); + virtual KDCoordinate columnWidth(int i) = 0; + virtual KDCoordinate rowHeight(int j) = 0; + /* return the number of pixels to include in offset to display the column i at + the top */ + virtual KDCoordinate cumulatedWidthFromIndex(int i) = 0; + virtual KDCoordinate cumulatedHeightFromIndex(int j) = 0; + /* return the number of columns (starting with first ones) that can be fully + * displayed in offsetX pixels. + * Caution: if the offset is exactly the size of n columns, the function + * returns n-1. */ + virtual int indexFromCumulatedWidth(KDCoordinate offsetX) = 0; + virtual int indexFromCumulatedHeight(KDCoordinate offsetY) = 0; + virtual View * reusableCell(int index, int type) = 0; + virtual int reusableCellCount(int type) = 0; + virtual int typeAtLocation(int i, int j) = 0; +}; + +#endif \ No newline at end of file diff --git a/escher/src/list_view.cpp b/escher/src/list_view.cpp index fa143d947..5d1f2c4d0 100644 --- a/escher/src/list_view.cpp +++ b/escher/src/list_view.cpp @@ -5,8 +5,6 @@ extern "C" { #include } -#define MIN(x,y) ((x)<(y) ? (x) : (y)) - int ListViewDataSource::numberOfColumns() { return 1; } diff --git a/escher/src/scroll_view.cpp b/escher/src/scroll_view.cpp index d6dbaf954..e2c3ad93d 100644 --- a/escher/src/scroll_view.cpp +++ b/escher/src/scroll_view.cpp @@ -120,6 +120,13 @@ KDPoint ScrollView::contentOffset() { return m_offset; } +KDCoordinate ScrollView::topMargin() const { + return m_topMargin; +} + +KDCoordinate ScrollView::leftMargin() const { + return m_leftMargin; +} KDCoordinate ScrollView::maxContentWidthDisplayableWithoutScrolling() { return m_frame.width() - m_leftMargin - m_rightMargin; diff --git a/escher/src/simple_table_view_data_source.cpp b/escher/src/simple_table_view_data_source.cpp new file mode 100644 index 000000000..d7a8f33e3 --- /dev/null +++ b/escher/src/simple_table_view_data_source.cpp @@ -0,0 +1,52 @@ +#include +#include + +extern "C" { +#include +} + +KDCoordinate SimpleTableViewDataSource::columnWidth(int i) { + return cellWidth(); +} + +KDCoordinate SimpleTableViewDataSource::rowHeight(int j) { + return cellHeight(); +} + +KDCoordinate SimpleTableViewDataSource::cumulatedWidthFromIndex(int i) { + return cellWidth() * i; +} + +KDCoordinate SimpleTableViewDataSource::cumulatedHeightFromIndex(int j) { + return cellHeight() * j; +} + +int SimpleTableViewDataSource::indexFromCumulatedWidth(KDCoordinate offsetX) { + KDCoordinate width = cellWidth(); + if (width == 0) { + return 0; + } + return (offsetX - 1) / width; +} + +int SimpleTableViewDataSource::indexFromCumulatedHeight(KDCoordinate offsetY) { + KDCoordinate height = cellHeight(); + if (height == 0) { + return 0; + } + return (offsetY - 1) / height; +} + +View * SimpleTableViewDataSource::reusableCell(int index, int type) { + assert(type == 0); + return reusableCell(index); +} + +int SimpleTableViewDataSource::reusableCellCount(int type) { + assert(type == 0); + return reusableCellCount(); +} + +int SimpleTableViewDataSource::typeAtLocation(int i, int j) { + return 0; +} diff --git a/escher/src/table_view.cpp b/escher/src/table_view.cpp index 75eca0379..65b01c2a7 100644 --- a/escher/src/table_view.cpp +++ b/escher/src/table_view.cpp @@ -7,10 +7,6 @@ extern "C" { #define MIN(x,y) ((x)<(y) ? (x) : (y)) -void TableViewDataSource::willDisplayCellAtLocation(View * cell, int x, int y) { -} - - TableView::TableView(TableViewDataSource * dataSource, KDCoordinate topMargin, KDCoordinate rightMargin, KDCoordinate bottomMargin, KDCoordinate leftMargin) : ScrollView(&m_contentView, topMargin, rightMargin, bottomMargin, leftMargin), @@ -20,12 +16,12 @@ TableView::TableView(TableViewDataSource * dataSource, KDCoordinate topMargin, K // This method computes the minimal scrolling needed to properly display the // requested cell. -void TableView::scrollToCell(int x, int y) { - m_contentView.scrollToCell(x, y); +void TableView::scrollToCell(int i, int j) { + m_contentView.scrollToCell(i, j); } -View * TableView::cellAtLocation(int x, int y) { - return m_contentView.cellAtLocation(x, y); +View * TableView::cellAtLocation(int i, int j) { + return m_contentView.cellAtLocation(i, j); } #if ESCHER_VIEW_LOGGING @@ -53,19 +49,21 @@ TableView::ContentView::ContentView(TableView * tableView, TableViewDataSource * { } -KDCoordinate TableView::ContentView::realCellWidth() const { - int cellWidth = m_dataSource->cellWidth(); - cellWidth = cellWidth ? cellWidth : m_tableView->maxContentWidthDisplayableWithoutScrolling(); - return cellWidth; +KDCoordinate TableView::ContentView::columnWidth(int i) const { + int columnWidth = m_dataSource->columnWidth(i); + columnWidth = columnWidth ? columnWidth : m_tableView->maxContentWidthDisplayableWithoutScrolling(); + return columnWidth; } KDCoordinate TableView::ContentView::height() const { - return m_dataSource->numberOfRows() * m_dataSource->cellHeight(); + return m_dataSource->cumulatedHeightFromIndex(m_dataSource->numberOfRows()); } KDCoordinate TableView::ContentView::width() const { - return m_dataSource->numberOfColumns() * realCellWidth(); + int result = m_dataSource->cumulatedWidthFromIndex(m_dataSource->numberOfColumns()); + // handle the case of list: cumulatedWidthFromIndex() = 0 + return result ? result : m_tableView->maxContentWidthDisplayableWithoutScrolling(); } void TableView::ContentView::scrollToCell(int x, int y) const { @@ -73,25 +71,47 @@ void TableView::ContentView::scrollToCell(int x, int y) const { KDCoordinate contentOffsetY = m_tableView->contentOffset().y(); if (columnAtIndexIsBeforeFullyVisibleRange(x)) { // Let's scroll the tableView to put that cell on the left (while keeping the left margin) - contentOffsetX = x*realCellWidth(); + contentOffsetX = m_dataSource->cumulatedWidthFromIndex(x); } else if (columnAtIndexIsAfterFullyVisibleRange(x)) { // Let's scroll the tableView to put that cell on the right (while keeping the right margin) - contentOffsetX = (x+1)*realCellWidth() - m_tableView->maxContentWidthDisplayableWithoutScrolling(); + contentOffsetX = m_dataSource->cumulatedWidthFromIndex(x+1)-m_tableView->maxContentWidthDisplayableWithoutScrolling(); } if (rowAtIndexIsBeforeFullyVisibleRange(y)) { // Let's scroll the tableView to put that cell on the top (while keeping the top margin) - contentOffsetY = y*m_dataSource->cellHeight(); + contentOffsetY = m_dataSource->cumulatedHeightFromIndex(y); } else if (rowAtIndexIsAfterFullyVisibleRange(y)) { // Let's scroll the tableView to put that cell on the bottom (while keeping the bottom margin) - contentOffsetY = (y+1)*m_dataSource->cellHeight() - m_tableView->maxContentHeightDisplayableWithoutScrolling(); + contentOffsetY = m_dataSource->cumulatedHeightFromIndex(y+1) - m_tableView->maxContentHeightDisplayableWithoutScrolling(); } m_tableView->setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); } +int TableView::ContentView::typeOfSubviewAtIndex(int index) const { + assert(index >= 0); + int i = absoluteColumnNumberFromSubviewIndex(index); + int j = absoluteRowNumberFromSubviewIndex(index); + int type = m_dataSource->typeAtLocation(i, j); + return type; +} + +int TableView::ContentView::typeIndexFromSubviewIndex(int index, int type) const { + int typeIndex = 0; + for (int k = 0; k < index; k++) { + if (typeOfSubviewAtIndex(k) == type) { + typeIndex++; + } + } + assert(typeIndex < m_dataSource->reusableCellCount(type)); + return typeIndex; +} + View * TableView::ContentView::cellAtLocation(int x, int y) { int relativeX = x-columnsScrollingOffset(); int relativeY = y-rowsScrollingOffset(); - return m_dataSource->reusableCell(relativeY*numberOfDisplayableColumns()+relativeX); + int type = m_dataSource->typeAtLocation(x, y); + int index = relativeY*numberOfDisplayableColumns()+relativeX; + int typeIndex = typeIndexFromSubviewIndex(index, type); + return m_dataSource->reusableCell(typeIndex, type); } #if ESCHER_VIEW_LOGGING @@ -102,83 +122,96 @@ const char * TableView::ContentView::className() const { int TableView::ContentView::numberOfSubviews() const { int result = numberOfDisplayableRows() * numberOfDisplayableColumns(); - assert(result <= m_dataSource->reusableCellCount()); return result; } +int TableView::ContentView::absoluteColumnNumberFromSubviewIndex(int index) const { + /* "x = i % columns" but we avoid a call to modulo not to implement + * "__aeabi_idivmod" */ + int j = index / numberOfDisplayableColumns(); + int i = index - j * numberOfDisplayableColumns(); + int columnOffset = columnsScrollingOffset(); + return i + columnOffset; +} + +int TableView::ContentView::absoluteRowNumberFromSubviewIndex(int index) const { + int j = index / numberOfDisplayableColumns(); + int rowOffset = rowsScrollingOffset(); + return j + rowOffset; +} + View * TableView::ContentView::subviewAtIndex(int index) { - assert(index >= 0); - assert(index < m_dataSource->reusableCellCount()); - return m_dataSource->reusableCell(index); + int type = typeOfSubviewAtIndex(index); + int typeIndex = typeIndexFromSubviewIndex(index, type); + return m_dataSource->reusableCell(typeIndex, type); } void TableView::ContentView::layoutSubviews() { - int rowOffset = rowsScrollingOffset(); - int columnOffset = columnsScrollingOffset(); + for (int index=0; indexcellHeight(); - KDCoordinate cellWidth = realCellWidth(); - KDRect cellFrame((columnOffset+x)*cellWidth, (rowOffset+y)*cellHeight, - cellWidth, cellHeight); + KDCoordinate rowHeight = m_dataSource->rowHeight(j); + KDCoordinate columnWidth = this->columnWidth(i); + KDCoordinate verticalOffset = m_dataSource->cumulatedHeightFromIndex(j); + KDCoordinate horizontalOffset = m_dataSource->cumulatedWidthFromIndex(i); + KDRect cellFrame(horizontalOffset, verticalOffset, + columnWidth, rowHeight); cell->setFrame(cellFrame); - m_dataSource->willDisplayCellAtLocation(cell, columnOffset+x, rowOffset+y); + m_dataSource->willDisplayCellAtLocation(cell, i, j); } } + int TableView::ContentView::numberOfFullyDisplayableRows() const { // The number of displayable rows taking into accounts margins - return m_tableView->maxContentHeightDisplayableWithoutScrolling()/m_dataSource->cellHeight(); + int rowOffsetWithMargin = m_dataSource->indexFromCumulatedHeight(m_tableView->contentOffset().y() + + m_tableView->topMargin()); + int displayedHeightWithOffsetAndMargin = m_dataSource->indexFromCumulatedHeight(m_tableView->maxContentHeightDisplayableWithoutScrolling() + + m_tableView->contentOffset().y() + m_tableView->topMargin()); + return displayedHeightWithOffsetAndMargin - rowOffsetWithMargin; } int TableView::ContentView::numberOfFullyDisplayableColumns() const { - // The number of displayable columns taking into accounts margins - return m_tableView->maxContentWidthDisplayableWithoutScrolling()/m_dataSource->cellHeight(); + // The number of displayable rows taking into accounts margins + int columnOffsetWithMargin = m_dataSource->indexFromCumulatedWidth(m_tableView->contentOffset().x() + + m_tableView->leftMargin()); + int displayedWidthWithOffsetAndMargin = m_dataSource->indexFromCumulatedWidth(m_tableView->maxContentWidthDisplayableWithoutScrolling() + + m_tableView->contentOffset().x() + m_tableView->leftMargin()); + return displayedWidthWithOffsetAndMargin - columnOffsetWithMargin; } int TableView::ContentView::numberOfDisplayableRows() const { + int rowOffset = rowsScrollingOffset(); + int displayedHeightWithOffset = m_dataSource->indexFromCumulatedHeight(m_tableView->bounds().height() + m_tableView->contentOffset().y()); return MIN( m_dataSource->numberOfRows(), - m_tableView->bounds().height() / m_dataSource->cellHeight() + 2 - ); + displayedHeightWithOffset + 1 + ) - rowOffset; } int TableView::ContentView::numberOfDisplayableColumns() const { - KDCoordinate width = realCellWidth(); - if (width == 0) { - return 0; - } + int columnOffset = columnsScrollingOffset(); + int displayedWidthWithOffset = m_dataSource->indexFromCumulatedWidth(m_tableView->bounds().width() + m_tableView->contentOffset().x()); return MIN( m_dataSource->numberOfColumns(), - m_tableView->bounds().width() / width + 2 - ); + displayedWidthWithOffset + 1 + ) - columnOffset; } int TableView::ContentView::rowsScrollingOffset() const { /* Here, we want to translate the offset at which our tableView is displaying * us into an integer offset we can use to ask cells to our data source. */ - KDCoordinate pixelScrollingOffset = -m_frame.y(); - return pixelScrollingOffset / m_dataSource->cellHeight(); + return m_dataSource->indexFromCumulatedHeight(m_tableView->contentOffset().y()); } int TableView::ContentView::columnsScrollingOffset() const { /* Here, we want to translate the offset at which our tableView is displaying * us into an integer offset we can use to ask cells to our data source. */ - KDCoordinate width = realCellWidth(); - if (width == 0) { - return 0; - } - KDCoordinate pixelScrollingOffset = -m_frame.x(); - return pixelScrollingOffset / width; + return m_dataSource->indexFromCumulatedWidth(m_tableView->contentOffset().x()); } bool TableView::ContentView::rowAtIndexIsBeforeFullyVisibleRange(int index) const { @@ -190,11 +223,13 @@ bool TableView::ContentView::columnAtIndexIsBeforeFullyVisibleRange(int index) c } bool TableView::ContentView::rowAtIndexIsAfterFullyVisibleRange(int index) const { - int relativeIndex = index - rowsScrollingOffset(); - return (relativeIndex >= numberOfFullyDisplayableRows()); + int minHeightToDisplayRowAtIndex = m_dataSource->cumulatedHeightFromIndex(index+1); + int heightToTheBottomOfTheScreen = m_tableView->contentOffset().y()+m_tableView->maxContentHeightDisplayableWithoutScrolling()+m_tableView->topMargin(); + return minHeightToDisplayRowAtIndex >= heightToTheBottomOfTheScreen; } bool TableView::ContentView::columnAtIndexIsAfterFullyVisibleRange(int index) const { - int relativeIndex = index - columnsScrollingOffset(); - return (relativeIndex >= numberOfFullyDisplayableColumns()); + int minWidthToDisplayColumnAtIndex = m_dataSource->cumulatedWidthFromIndex(index+1); + int widthToTheRightOfTheScreen = m_tableView->contentOffset().x()+m_tableView->maxContentWidthDisplayableWithoutScrolling()+m_tableView->leftMargin(); + return minWidthToDisplayColumnAtIndex >= widthToTheRightOfTheScreen; } diff --git a/escher/src/table_view_data_source.cpp b/escher/src/table_view_data_source.cpp new file mode 100644 index 000000000..eb728bff9 --- /dev/null +++ b/escher/src/table_view_data_source.cpp @@ -0,0 +1,4 @@ +#include + +void TableViewDataSource::willDisplayCellAtLocation(View * cell, int i, int j) { +}