Class GridState<T,C extends GridCell<T>>

java.lang.Object
io.github.palexdev.virtualizedfx.grid.GridState<T,C>

public class GridState<T,C extends GridCell<T>> extends Object
Class used by the 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 GridRows, each of these has a cell for each column. GridRows 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.

This also contains a particular global state, EMPTY, typically used to indicate that the viewport is empty, and no state can be created.
  • Field Details

    • EMPTY

      public static final GridState 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 GridChangeTypes:

      REPLACE_ELEMENT Simple case, if the row at which the change occurred, ObservableGrid.Change.getCoordinates(), is in range of this state, calls GridRow.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 a Deque.

      For each row then we get an item from the deque with Deque.poll() and call GridRow.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, calls GridRow.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 a Deque. 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 call GridRow.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 with GridHelper.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, rowsFilled(), then the old state is returned. In any other case the computation can begin.

      A 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 with GridHelper.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 two Sets 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 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 call clear() to make sure that remaining rows in the old state are disposed and cleared, then exit the switch. In case the Set is not empty then we always expect to have one remaining index in the second Set, while in the first Set 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.

      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.

      Once we get the index at which the column has been added and the expected range with GridHelper.columnsRange(), on each row in the state we delegate the computation to GridRow.onColumnAdd(int, IntegerRange), rows are moved then to the new state.

      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.

      Once we get the index at which the column has been removed and the expected range with GridHelper.columnsRange(), on each row in the state we delegate the computation to GridRow.onColumnRemove(int, IntegerRange), rows are moved then to the new state.

      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 be 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 call ObservableGrid.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 of GridHelper.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: columnsRange.diff() * cellWidth or in the above exceptional case by (columnsRange.diff() - 1) * cellWidth

      At this point rows are laid out from the bottom to the top, and each row is responsible for laying out its cells with GridRow.layoutCells(double, boolean).
    • layoutPaginatedRows

      public void layoutPaginatedRows()
      This is the implementation of layoutRows() exclusively for PaginatedVirtualGrids.

      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: columnsRange.diff() * cellWidth or in the above exceptional case by (columnsRange.diff() - 1) * cellWidth

      At this point rows are laid out from the top to the bottom, and each row is responsible for laying out its cells with GridRow.layoutCells(double, boolean).

      Last but not least, for PaginatedVirtualGrids 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 with GridRow.setVisible(boolean).
    • addRow

      protected void addRow(int index)
      Creates a new GridRow with the given index and the state's columns range, initializes it with GridRow.init() then adds it to the state's map.
    • addRow

      protected void addRow(int index, GridRow<T,C> row)
      Adds the given GridRow to the state's map at the given index.
    • clear

      protected void clear()
      For every GridRow in the state's map calls GridRow.clear() then clears the map, making the state empty.
    • getNodes

      public List<Node> getNodes()
      By iterating over all the rows in the state (using Streams) this converts the cells contained in the rows to a list of Nodes, with Cell.getNode()
    • getCells

      public List<C> 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

      public Map<Integer,C> 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

      protected Map<Integer,GridRow<T,C>> getRows()
      Returns:
      the map used to keep the GridRows
    • getRowsUnmodifiable

      public Map<Integer,GridRow<T,C>> 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

      public UpdateType 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, causing haveCellsChanged() to return true, which will tell the viewport to update its children.