Class VFXTableHelper.VariableTableHelper<T>

java.lang.Object
io.github.palexdev.virtualizedfx.table.VFXTableHelper.AbstractHelper<T>
io.github.palexdev.virtualizedfx.table.VFXTableHelper.VariableTableHelper<T>
All Implemented Interfaces:
VFXTableHelper<T>
Enclosing interface:
VFXTableHelper<T>

public static class VFXTableHelper.VariableTableHelper<T> extends VFXTableHelper.AbstractHelper<T>
Concrete implementation of VFXTableHelper.AbstractHelper for ColumnsLayoutMode.VARIABLE. Here the range of rows and columns to display, as well as the viewport position, the virtual max x and y properties are defined as follows:

- the columns range is always given by the number of columns in the table - 1. If there are no column, then it will be Utils.INVALID_RANGE. The computation depends only on the columns' list.

- the rows range is given by the VFXTableHelper.AbstractHelper.firstRow() element minus the buffer size VFXTable.rowsBufferSizeProperty(), (cannot be negative) and the sum between this start index and the total number of needed rows given by VFXTableHelper.AbstractHelper.totalRows(). It may happen that the number of indexes given by the range end - start + 1 is lesser than the number of rows we need. In such cases, the range start is corrected to be end - needed + 1. A typical situation for this is when the table's vertical position reaches the max scroll. If the viewport's height is 0 or the number of needed rows is 0, then the range will be Utils.INVALID_RANGE. The computation has the following dependencies: the table's height, the column's size (because it also specifies the header height, which influences the viewport's height), the vertical position, the rows buffer size, the rows' height and the items' list size.

- the viewport's position, a computation that is at the core of virtual scrolling. The viewport, which contains the columns and the cells (even though the table's viewport is a bit more complex), is not supposed to scroll by insane numbers of pixels both for performance reasons and because it is not necessary. For the vertical positions, first we get the range of rows to display and the rows' height. We compute the range to the first visible row, which is given by IntegerRange.of(range.getMin(), first()), in other words we limit the 'complete' range to the start buffer including the first row after the buffer. The number of indexes in the newfound range (given by IntegerRange.diff()) is multiplied by the rows' height, this way we are finding the number of pixels to the first visible row, pixelsToFirst. At this point, we are missing only one last piece of information: how much of the first row do we actually see? We call this amount visibleAmountFirst and it's given by vPos % size. Finally, the viewport's vertical position is given by this formula -(pixelsToFirst + visibleAmountFirst). Since this layout mode disables virtualization along the x-axis, the horizontal position is simply given by -hPos. If a range is equal to Utils.INVALID_RANGE, the respective position will be 0! While it's true that the calculations are more complex and 'needy', it's important to note that this approach allows avoiding 'hacks' to correctly lay out the cells in the viewport. No need for special offsets at the top or bottom anymore. The viewport's position computation has the following dependencies: the vertical and horizontal positions, the rows' height and the columns' size.

- the virtual max y property, which gives the total number of pixels on the y-axis. Virtual means that it's not the actual size of the container, rather the size it would have if it was not virtualized. The value is given by the number of rows multiplied by the rows' height. The computation depends on the columns' list, the columns' size (because the viewport height also depends on the height specified by the columns' size property), the table's size (number of items), and the rows' height.

- the virtual max x property, which gives the total number of pixels on the x-axis. Since virtualization is disabled in this axis, the value is simply the sum of all the table's columns (to be precise, the vaòue is given by the cache-binding, see below).

Performance Optimizations Disabling virtualization on a complex 2D structure like VFXTable is indeed dangerous. While it's safe to assume that the number of columns is pretty much never going to be a big enough number to cause performance issues, it's also true that: 1) we can't be sure on how many columns are actually too many; 2) since it is a 2D structure having n more columns in the viewport does not mean that we will have n more nodes in the scene graph. Rather, we can affirm that at best we will have n more nodes, but keep in mind that for each column there are going to be a number of cells equal to the number of rows.

I believe it's worth to optimize the helper as much as possible to mitigate the issue. So, for this reason, this helper makes use of special cache ColumnsLayoutCache which aims to improve layout operations by avoiding re-computations when they are not needed. For example, if we compute the width and the position of column, then we don't need to re-compute it again when laying out corresponding cells, that would be a waste!

The cache can compute columns' widths, their x positions and even check whether they are visible in the viewport. I won't go into many details here on how the cache exactly works, read its docs to know more about it, just know that after the first computation, values will be memorized. Further requests will be as fast as a simple 'getter' method. The cache is also responsible for automatically invalidate the cached values when certain conditions change.

For ColumnsLayoutCache to work properly, this helper defines a series of methods which are actually responsible for the computations. I decided to keep such methods here rather than defining them in the cache mainly for two reasons: 1) I strongly believe such operations are the helper's responsibility; 2) By doing so we generalize the cache class, making it flexible to use, and suitable for more use-cases. These methods are: computeColumnWidth(VFXTableColumn, boolean), computeColumnPos(int, double), computeVisibility(VFXTableColumn).

  • Constructor Details

    • VariableTableHelper

      public VariableTableHelper(VFXTable<T> table)
  • Method Details

    • computeColumnWidth

      protected double computeColumnWidth(VFXTableColumn<T,?> column, boolean isLast)
      This is used by the ColumnsLayoutCache to compute the width of the given column. The value is given by Math.max(minW, prefW), where:

      - minW is given by VFXTable.columnsSizeProperty()

      - prefW is given by Region.prefWidth(double)

      If there's only one column in the table, then the returned value is the maximum between the above formula and the table's width.

      If the column is the last one in the list, then the final value is given by Math.max(Math.max(minW, prefW), tableW - partialW), where partialW is given by ColumnsLayoutCache.getPartialWidth().

    • computeColumnPos

      protected double computeColumnPos(int index, double prevPos)
      This is used by the ColumnsLayoutCache to compute the x position of a given column (by its index) and the last know position. For example, let's suppose I want to lay out the column at index 1, and the column at index 0 (the previous one) is 100px wider. The prevPos parameter passed to this method will be 100px. This makes the computation for index 1 easy, as it is simply is the sum prevPos + col1Width.
    • computeVisibility

      protected boolean computeVisibility(VFXTableColumn<T,?> column)
      This is used by the ColumnsLayoutCache to compute the visibility of a given column (and all its related cells ofc). There are a lot of requirements for this check but the concept is quite simple.

      Before getting all the dependencies, we ensure that the column's table instance is not null, that its index is not negative, that the column is in the scene graph (null check on the column's Scene and Parent). If any of these conditions fail false is returned.

      The idea is to use something similar to Bounds.intersects(Bounds) but only for the width and x position. First we compute the viewport bounds which are given by [hPos, hPos + tableWidth], then we get the column's position and width by using ColumnsLayoutCache.getColumnPos(int) and ColumnsLayoutCache.getColumnWidth(VFXTableColumn).

      The result is given by this formula: (columnX + columnWidth >= vBounds.getMin()) && (columnX <= vBounds.getMax())

    • initBindings

      protected void initBindings()
      Description copied from class: VFXTableHelper.AbstractHelper
      Bindings and listeners should be initialized here, automatically called after the table instance is set.
      Overrides:
      initBindings in class VFXTableHelper.AbstractHelper<T>
    • firstColumn

      public int firstColumn()
      Always 0.
      Returns:
      the index of the first visible column
    • visibleColumns

      public int visibleColumns()
      Always the size of VFXTable.getColumns().
      Returns:
      the number of columns visible in the viewport. Not necessarily the same as VFXTableHelper.totalColumns()
    • totalColumns

      public int totalColumns()
      Always the size of VFXTable.getColumns().
      Returns:
      the total number of columns in the viewport which doesn't include only the number of visible columns but also the number of buffer columns
      See Also:
    • getColumnWidth

      public double getColumnWidth(VFXTableColumn<T,?> column)
      Returns:
      the width for the given column
    • getColumnPos

      public double getColumnPos(int layoutIdx, VFXTableColumn<T,?> column)
      Returns:
      the x position for the given column and its layout index in the viewport
    • isInViewport

      public boolean isInViewport(VFXTableColumn<T,?> column)
      Returns:
      whether the given column is currently visible in the viewport
    • layoutColumn

      public boolean layoutColumn(int layoutIdx, VFXTableColumn<T,?> column)
      Lays out the given column. The layout index is necessary to identify the position of a column among the others (comes before/after).

      In ColumnsLayoutMode.VARIABLE the layoutIndex is always the same as the column's index.

      Positions the column at X: getColumnPos(index, column) and Y: 0.

      Sizes the column to W: getColumnWidth(column) and H: columnsHeight

      Additionally, this method makes use of the 'inViewport' functionality to hide and not lay out columns when they are not visible in the viewport. The layout operation is also avoided in case the column is visible and both its x positions and width are already good to go.
      Returns:
      false if the column was hidden or no layout operation was run, true otherwise
      See Also:
    • layoutCell

      public boolean layoutCell(int layoutIdx, VFXTableCell<T> cell)
      Lays out the given cell. The layout index is necessary to identify the position of a cell among the others (comes before/after).

      In ColumnsLayoutMode.VARIABLE the layoutIndex is always the same as the column's index.

      The layout logic is the exact same as described here layoutColumn(int, VFXTableColumn).

      Here's where the ColumnsLayoutCache shines. Since cells are laid out the exact same way as the corresponding column, the width, positions and visibility computations are already done when invoking layoutColumn(int, VFXTableColumn). Obviously, to do so, we first need to get the cell's corresponding column from VFXTable.getColumns() by the given index.

      Note: the pre/post layout hooks defined by VFXCell are called even if the cell will only be set to hidden. This allows implementations to perform actions depending on the visibility state without relying on listeners.
      See Also:
    • autosizeColumn

      public void autosizeColumn(VFXTableColumn<T,?> column)
      If the current state is VFXTableState.INVALID then exits immediately.

      If the given column's skin is still null, then we must 'delay' the operation and wait for the skin to be created, so that we can compute the column's width.

      The first pass is to get the column's ideal width which is given by Math.max(minW, prefW) + extra where:

      - minW is specified by VFXTable.columnsSizeProperty()

      - prefW is obtained by calling VFXTableColumn.computePrefWidth(double)

      - extra is an extra number of pixels added to the final value specified by VFXTable.extraAutosizeWidthProperty()

      If the state is empty (no rows), the computation ends and the column's width is set to the value found by the above formula.

      The second pass is to get the widest cell among the ones in the viewport by using VFXTableRow.getWidthOf(VFXTableColumn, boolean). The forceLayout flag is true if this operation was 'delayed' before for the aforementioned reasons.

      Finally, the column's width is set to: Math.max(Math.max(minW, prefW), maxCellsWidth) + extra.

      Note: the columns are resized using the method VFXTableColumn.resize(double).

      See Also:
    • autosizeColumns

      public void autosizeColumns()
      This simply calls autosizeColumn(VFXTableColumn) on all the table's columns.
    • visibleCells

      public int visibleCells()
      Description copied from interface: VFXTableHelper
      Depends on the implementation!
      Returns:
      the number of cells for which the corresponding column is visible in the viewport
      See Also:
    • scrollToIndex

      public void scrollToIndex(javafx.geometry.Orientation orientation, int index)
      Description copied from interface: VFXTableHelper
      Scrolls in the viewport, depending on the given direction (orientation) to:

      - the item at the given index if it's Orientation.VERTICAL

      - the column at the given index if it's Orientation.HORIZONTAL

    • dispose

      public void dispose()
      Automatically called by VFXTable when a helper is not needed anymore (changed). If the helper uses listeners/bindings that may lead to memory leaks, this is the right place to remove them.

      Sets the table reference to null.

      Overridden here to also dispose the ColumnsLayoutCache.
      Specified by:
      dispose in interface VFXTableHelper<T>
      Overrides:
      dispose in class VFXTableHelper.AbstractHelper<T>