Class GridState<T,C extends GridCell<T>>
ViewportManager
to represent the state of the viewport at a given time.
The idea is to have an immutable state so that each state is a different object, with some exceptional cases when the state doesn't need to be re-computed, so the old object is returned.
This offers information such as: - The range of rows contained in the state, getRowsRange()
- The range of columns contained by each GridRow
in the state, getColumnsRange()
- The cells in the viewport. These are stored in GridRow
s, each of these has a cell for each column.
GridRow
s are kept in a map: rowIndex -> gridRow
- The expected number of rows, getTargetSize()
. Note that this is computed by GridHelper.maxRows()
,
so the result may be greater than the number of rows available in the data structure
- The type of event that lead the old state to transition to the new one, see UpdateType
-A flag to check if new cells were created or some were deleted, haveCellsChanged()
.
This is used by VirtualGridSkin
since we want to update the viewport children only when the cells change.
EMPTY
, typically used to indicate that the viewport
is empty, and no state can be created.-
Field Summary
Fields -
Constructor Summary
ConstructorsConstructorDescriptionGridState
(VirtualGrid<T, C> grid, io.github.palexdev.mfxcore.base.beans.range.IntegerRange rowsRange, io.github.palexdev.mfxcore.base.beans.range.IntegerRange columnsRange) -
Method Summary
Modifier and TypeMethodDescriptionprotected void
addRow
(int index) Creates a newGridRow
with the given index and the state's columns range, initializes it withGridRow.init()
then adds it to the state's map.protected void
Adds the givenGridRow
to the state's map at the given index.protected void
Sets the cellsChanged flag to true, causinghaveCellsChanged()
to return true, which will tell the viewport to update its children.This is responsible for transitioning to a new state when a change occurs in the grid's items data structure.protected void
clear()
For everyGridRow
in the state's map callsGridRow.clear()
then clears the map, making the state empty.boolean
getCells()
By iterating over all the rows in the state (using Streams) this gathers all the cells contained in the rows into one list.io.github.palexdev.mfxcore.base.beans.range.IntegerRange
By iterating over all the rows in the state (using Streams) this gathers all the cells contained in the rows in one map.getNodes()
By iterating over all the rows in the state (using Streams) this converts the cells contained in the rows to a list ofNode
s, withCell.getNode()
getRows()
io.github.palexdev.mfxcore.base.beans.range.IntegerRange
int
getType()
boolean
hScroll
(io.github.palexdev.mfxcore.base.beans.range.IntegerRange columnsRange) This is responsible for transitioning to a new state when the viewport scrolls horizontally.init
(io.github.palexdev.mfxcore.base.beans.range.IntegerRange rowsRange, io.github.palexdev.mfxcore.base.beans.range.IntegerRange columnsRange) Responsible for filling the viewport the needed amount of rows/cells.boolean
isEmpty()
void
This is the implementation oflayoutRows()
exclusively forPaginatedVirtualGrid
s.void
This is responsible for laying out the rows in the viewport with the help ofGridHelper.layout(Node, double, double)
.boolean
int
size()
int
vScroll
(io.github.palexdev.mfxcore.base.beans.range.IntegerRange rowsRange) This is responsible for transitioning to a new state when the viewport scrolls vertically.
-
Field Details
-
EMPTY
-
-
Constructor Details
-
GridState
public GridState() -
GridState
public GridState(VirtualGrid<T, C> grid, io.github.palexdev.mfxcore.base.beans.range.IntegerRange rowsRange, io.github.palexdev.mfxcore.base.beans.range.IntegerRange columnsRange)
-
-
Method Details
-
init
protected GridState<T,C> init(io.github.palexdev.mfxcore.base.beans.range.IntegerRange rowsRange, io.github.palexdev.mfxcore.base.beans.range.IntegerRange columnsRange) Responsible for filling the viewport the needed amount of rows/cells. So this may supply or remove cells according to the viewport size.If the given ranges for rows and columns are the same as the ones of the state then the old state is returned.
This is used by
ViewportManager.init()
.- Returns:
- a new
GridState
which is the result of transitioning from this state to a new one given the new ranges for rows and columns
-
vScroll
protected GridState<T,C> vScroll(io.github.palexdev.mfxcore.base.beans.range.IntegerRange rowsRange) This is responsible for transitioning to a new state when the viewport scrolls vertically.Used by
ViewportManager.onVScroll()
. -
hScroll
protected GridState<T,C> hScroll(io.github.palexdev.mfxcore.base.beans.range.IntegerRange columnsRange) This is responsible for transitioning to a new state when the viewport scrolls horizontally.Used by
ViewportManager.onHScroll()
. -
change
protected GridState<T,C> change(io.github.palexdev.mfxcore.collections.ObservableGrid.Change<T> change) This is responsible for transitioning to a new state when a change occurs in the grid's items data structure.Specifically this handles the following
REPLACE_ELEMENT Simple case, if the row at which the change occurred,GridChangeType
s:ObservableGrid.Change.getCoordinates()
, is in range of this state, callsGridRow.onReplace(int, Object)
on it.Note that this type of change returns the old state.
REPLACE_DIAGONAL The new diagonal is given by the change's added list, we put these new items in aDeque
.For each row then we get an item from the deque with
Deque.poll()
and callGridRow.onDiagReplace(Object)
.Note that this type of change returns the old state.
REPLACE_ROW Simple case, if the row at which the change occurred,ObservableGrid.Change.getCoordinates()
, is in range of this state, callsGridRow.onReplace()
on it.Note that this type of change returns the old state.
REPLACE_COLUMN The new column is given by the change's added list, we put these new items in aDeque
. Note though that since not all the column may be displayed in the viewport we get only the items in the columns range,getColumnsRange()
.For each row then we get an item from the deque with
Deque.poll()
and callGridRow.onReplace(int, Object)
.Note that this type of change returns the old state.
ADD_ROW This is a more complex operation. Before starting the computation we get the index at which the row was added and the rows range we expect withGridHelper.rowsRange()
.If the range is equal to the one in the state, the insertion index is greater than the range max and the viewport is filled,
ArowsFilled()
, then the old state is returned. In any other case the computation can begin.Set
will keep track of the rows we have re-used (to be precise it will contain the remaining ones).The computation is divided in three main parts:
- Valid rows: these are the ones that come before the insertion index
- Partial rows: these are the ones that come after the insertion index but their index must be updated to be +1. Rows that were valid are ignored. Rows for which the new index would be outside the expected range are also ignored.
- Remaining rows: for insertions we always expect one row to be remaining at this point. If the viewport was full, then this row is one of the ignored ones (not the valid ones though). If the viewport was not full then a new row needs to be created
REMOVE_ROW This is a more complex operation. Before starting the computation we get the index at which the row was removed and the rows range we expect withGridHelper.rowsRange()
.If the range is equal to the one in the state and the removal index is greater than the range max, the old state is returned. In any other case the computation can begin.
This time twoSet
s are used: one to keep track of the rows we have re-used (to be precise it will contain the remaining ones), and another to keep track of the "covered" indexes of the expected range.The computation is divided in three main parts:
- Valid rows: these are the ones that come before the removal index. Note that the start index for the loop is computes as the maximum between the state's range minimum and the expected range minimum, so
Math.max(expRange.getMin(), stateRange.getMin())
.- Partial rows: these are the ones that come after the removal index but their index must be updated to be -1. Rows that were valid are ignored. Rows for which the new index would be outside the expected range are also ignored.
- Remaining rows: for removals this part is a bit different. Before proceeding, we must check if the
ADD_COLUMN This is a more complex and costly operation. No matter where the change occurred, almost all the cells will need either a partial (index only) or full (index and item) update.Set
used to keep track of the "covered" indexes is empty, because in such case it means that we already have all the rows needed to fill the viewport. In this case we callclear()
to make sure that remaining rows in the old state are disposed and cleared, then exit the switch. In case theSet
is not empty then we always expect to have one remaining index in the secondSet
, while in the firstSet
there may be or not an index which will be a reusable row. In either cases, we need another row to fill the viewport, so one is reused if available or created.Once we get the index at which the column has been added and the expected range with
REMOVE_COLUMN This is a more complex and costly operation. No matter where the change occurred, almost all the cells will need either a partial (index only) or full (index and item) update.GridHelper.columnsRange()
, on each row in the state we delegate the computation toGridRow.onColumnAdd(int, IntegerRange)
, rows are moved then to the new state.Once we get the index at which the column has been removed and the expected range with
Before returning the state which is the result of one of the above changes, we make sure to update the state's type, which will beGridHelper.columnsRange()
, on each row in the state we delegate the computation toGridRow.onColumnRemove(int, IntegerRange)
, rows are moved then to the new state.UpdateType.CHANGE
, then we check if the total size of the new state (totalSize()
) is different from the old state total size so that in such case we can also tell the new state that the viewport will also need to update its children. Then we callObservableGrid.Change.endChange()
to dispose the current change and finally return the state. -
layoutRows
public void layoutRows()This is responsible for laying out the rows in the viewport with the help ofGridHelper.layout(Node, double, double)
. Before starting the actual layout computation there are a bunch of information required for it to work properly:- The cells' size, as well as the number of rows and columns of the grid
- The rows and columns ranges. These are not computed with
GridHelper
because in some exceptional cases they may differ- The computed ranges are needed to check for two exceptional cases. For how the viewport works we always have one Cell of overscan/buffer whatever you want to call it. But when we are at the end (last column or last row, or both) that one Cell will need to be moved to be the first cell of the range since there are no more items to the right/bottom. Two boolean flags are used to detect such cases.
- Cells are laid out from the bottom-right. The two positions are computed as follows:
⠀ ⠀ - The bottom position for rows is given by:
rowsRange.diff() * cellHeight
or in the above exceptional case by(rowsRange.diff() - 1) * cellHeight
⠀ ⠀ - The right position for columns is given by:
At this point rows are laid out from the bottom to the top, and each row is responsible for laying out its cells withcolumnsRange.diff() * cellWidth
or in the above exceptional case by(columnsRange.diff() - 1) * cellWidth
GridRow.layoutCells(double, boolean)
. -
layoutPaginatedRows
public void layoutPaginatedRows()This is the implementation oflayoutRows()
exclusively forPaginatedVirtualGrid
s.This is simpler as there is no "free" vertical scrolling, all cells will have a precise vertical position at any time in the page.
Before starting the actual layout computation there are a bunch of information required for it to work properly:- The cells' size, as well as the number columns of the grid
- The rows and columns ranges. These are not computed with
GridHelper
because in some exceptional cases they may differ- The computed ranges are needed to check for two exceptional cases. For how the viewport works we always have one Cell of overscan/buffer whatever you want to call it. But when we are at the end (last column or last row, or both) that one Cell will need to be moved to be the first cell of the range since there are no more items to the right/bottom. For
PaginatedVirtualGrid
we only need a boolean flag for the columns as there is no overscan for the rows.- Cells are laid out from the top-right. The two positions are computed as follows:
⠀ ⠀ - The top position starts at 0 and increases by
cellHeight
at each loop iteration⠀ ⠀ - The right position for columns is given by:
At this point rows are laid out from the top to the bottom, and each row is responsible for laying out its cells withcolumnsRange.diff() * cellWidth
or in the above exceptional case by(columnsRange.diff() - 1) * cellWidth
GridRow.layoutCells(double, boolean)
. Last but not least, forPaginatedVirtualGrid
s it may happen that there aren't enough rows to entirely fill a page, in such case extra rows are still present in the viewport, but they need to be hidden withGridRow.setVisible(boolean)
. -
addRow
protected void addRow(int index) Creates a newGridRow
with the given index and the state's columns range, initializes it withGridRow.init()
then adds it to the state's map. -
addRow
Adds the givenGridRow
to the state's map at the given index. -
clear
protected void clear()For everyGridRow
in the state's map callsGridRow.clear()
then clears the map, making the state empty. -
getNodes
By iterating over all the rows in the state (using Streams) this converts the cells contained in the rows to a list ofNode
s, withCell.getNode()
-
getCells
By iterating over all the rows in the state (using Streams) this gathers all the cells contained in the rows into one list. -
getIndexedCells
By iterating over all the rows in the state (using Streams) this gathers all the cells contained in the rows in one map. Cells are mapped as follows: linearIndex -> Cell.Note that since Cells are kept by their column index in the rows, we use flatMap on
GridRow.getLinearCells()
. -
rowsFilled
public boolean rowsFilled()- Returns:
- whether there are enough rows is this state to fill the viewport
-
columnsFilled
public boolean columnsFilled()- Returns:
- whether there are enough columns in this state to fill the viewport
-
size
public int size()- Returns:
- the number of rows in this state
-
totalSize
public int totalSize()- Returns:
- the total number of cells in this state, as the sum of all the cells of each individual
GridRow
-
isEmpty
public boolean isEmpty()- Returns:
- whether the state is empty, no rows in it
-
getRows
- Returns:
- the map used to keep the
GridRow
s
-
getRowsUnmodifiable
- Returns:
getRows()
as an unmodifiable map
-
getRowsRange
public io.github.palexdev.mfxcore.base.beans.range.IntegerRange getRowsRange()- Returns:
- the range of rows in the state
-
getColumnsRange
public io.github.palexdev.mfxcore.base.beans.range.IntegerRange getColumnsRange()- Returns:
- the range of columns of each
GridRow
in the state
-
getTargetSize
public int getTargetSize()- Returns:
- the expected number of rows
-
getType
- Returns:
- the type of change/event that caused an old state to transition to this new one
-
haveCellsChanged
public boolean haveCellsChanged()- Returns:
- whether a change caused the total size of the new state to change compared to the old state
-
cellsChanged
protected void cellsChanged()Sets the cellsChanged flag to true, causinghaveCellsChanged()
to return true, which will tell the viewport to update its children.
-