All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.palexdev.virtualizedfx.table.VFXTable Maven / Gradle / Ivy

There is a newer version: 21.6.4
Show newest version
/*
 * Copyright (C) 2024 Parisi Alessandro - [email protected]
 * This file is part of VirtualizedFX (https://github.com/palexdev/VirtualizedFX)
 *
 * VirtualizedFX is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * VirtualizedFX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with VirtualizedFX. If not, see .
 */

package io.github.palexdev.virtualizedfx.table;

import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;

import io.github.palexdev.mfxcore.base.beans.Size;
import io.github.palexdev.mfxcore.base.beans.range.IntegerRange;
import io.github.palexdev.mfxcore.base.properties.functional.FunctionProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableDoubleProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableIntegerProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableSizeProperty;
import io.github.palexdev.mfxcore.base.properties.styleable.StyleableSizeProperty.SizeConverter;
import io.github.palexdev.mfxcore.controls.Control;
import io.github.palexdev.mfxcore.controls.SkinBase;
import io.github.palexdev.mfxcore.observables.When;
import io.github.palexdev.mfxcore.utils.fx.PropUtils;
import io.github.palexdev.mfxcore.utils.fx.StyleUtils;
import io.github.palexdev.virtualizedfx.base.VFXContainer;
import io.github.palexdev.virtualizedfx.base.VFXScrollable;
import io.github.palexdev.virtualizedfx.base.VFXStyleable;
import io.github.palexdev.virtualizedfx.cells.base.VFXTableCell;
import io.github.palexdev.virtualizedfx.controls.VFXScrollPane;
import io.github.palexdev.virtualizedfx.enums.BufferSize;
import io.github.palexdev.virtualizedfx.enums.ColumnsLayoutMode;
import io.github.palexdev.virtualizedfx.events.VFXContainerEvent;
import io.github.palexdev.virtualizedfx.grid.VFXGrid;
import io.github.palexdev.virtualizedfx.list.VFXList;
import io.github.palexdev.virtualizedfx.properties.CellFactory;
import io.github.palexdev.virtualizedfx.properties.VFXTableStateProperty;
import io.github.palexdev.virtualizedfx.table.VFXTableHelper.FixedTableHelper;
import io.github.palexdev.virtualizedfx.table.VFXTableHelper.VariableTableHelper;
import io.github.palexdev.virtualizedfx.table.ViewportLayoutRequest.ViewportLayoutRequestProperty;
import io.github.palexdev.virtualizedfx.table.defaults.VFXDefaultTableRow;
import io.github.palexdev.virtualizedfx.utils.VFXCellsCache;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.Orientation;
import javafx.scene.shape.Rectangle;

/**
 * Implementation of a virtualized container to show a list of items as tabular data.
 * The default style class is: '.vfx-table'.
 * 

* Extends {@link Control}, implements {@link VFXContainer}, has its own skin implementation {@link VFXTableSkin} * and behavior {@link VFXTableManager}. Uses cells of type {@link VFXTableCell}. *

* This is a stateful component, meaning that every meaningful variable (position, size, cell size, etc.) will produce a new * {@link VFXTableState} when changing. The state determines how and which items are displayed in the container. *

* Core features {@literal &} Implementation Details *

- This container is a bit special because it's like a combination of both {@link VFXList} and {@link VFXGrid}. * We are displaying data in two dimensions, just like the grid, because we have both the items and the columns. * However, each item occupies a row, just like in the list. Because of such nature, this component is also more complex * to use/setup. There are three core aspects: *

 *     1) The columns: to display tabular data from a single object type, we usually want to divide such objects in data fragments,
 *        where each type belongs to a specific category. This is the columns' role, and to do this, we have to introduce the second core aspect...
 *     2) The cell factory is not a property of the container anymore, rather, each column has its cell factory.
 *        This way, every column can build a cell that is going to extract the appropriate piece of data from the object type (e.g., User Object -> Name String).
 *        The default cell implementations have specific properties to do exactly this, so you don't really need a different cell type for each column;
 *        to be precise, you just need to set the appropriate function to extract the data and display it.
 *        However, note that the container does not force you to use such defaults, in theory, you could come up with a totally different strategy if you like.
 *     3) The cells are not positioned directly into the viewport, rather, they are grouped in rows. So, you'll also need to specify a row factory now.
 *        By default, {@link VFXDefaultTableRow} is used, you may want to extend such class and change the factory to implement missing features.
 *        As for the reasons, there are fundamentally two:
 *          i) Selection: suppose you have a selection model, you click on a cell, and you now want the entire row to be highlighted.
 *             I won't say it's impossible, but it's definitely not practical. You would have to manage every single cell in the row,
 *             and it also makes it harder to style in CSS.
 *         ii) The base class {@link VFXTableRow} actually specifies some abstract methods that are crucial for the system to correctly manage the cells.
 *             Such methods could, in theory, be placed elsewhere, but doing it like this makes everything cleaner and easier to handle.
 * 
* Last but not least, there is another peculiar feature worth mentioning. Virtualized containers are super efficient mainly * for two reasons: every cell has a fixed size, because of this, it's easy to determine how many cells we need, which items * to display, and thus we just create and render the needed number of nodes. The table, like the list and the grid, is no different. * There are a bunch of properties to do what I just described which will be discussed more in depth below. The point is, * usually, tables have fixed cell heights, but the width depends on the "parent" column. Also, they often offer the possibility * of resizing columns to fit the "children" cells' content, or even the possibility of resizing each column with the mouse. * In other words, to support such features, we would have no choice but to disable the virtualization on the x-axis, * which means a potentially huge performance hit. For this reason, and because I strive to make things as flexible as possible for * the sake of the users, I implemented two layout modes {@link ColumnsLayoutMode}. I'll detail how it works below, just * know that this is only one of the many mechanisms that regulate the columns' width. *

- The default behavior implementation, {@link VFXTableManager}, can be considered as the name suggests more like * a 'manager' than an actual behavior. It is responsible for reacting to core changes in the functionalities defined here * to produce a new state. * The state can be considered as a 'picture' of the container at a certain time. Each combination of the variables that * influence the way items are shown (how many, start, end, changes in the list, etc.) will produce a specific state. * Because of how the table is designed, there are actually two kinds of states here. The first one is common to all * virtualized containers, and represents the overall state of the component, for the table it's the {@link VFXTableState} * class. The other one is specific to the table, and it's actually the {@link VFXTableRow} class itself. * The global state tells how many rows should be present in the viewport, which items from the list (rows range) * and which "data fragments" (columns range) to display; but then each row has its sub-state, they keep the cells' list, * the columns range, and are responsible for updating the cells when needed as well as positioning and sizing them. * Of course, you are free to customize pretty much all of these mechanisms, BUT, beware, VFX components are no joke, * they are complex, make sure to read the documentation before! *

- The items' list is managed automatically (permutations, insertions, removals, updates). Compared to previous * algorithms, the {@link VFXTableManager} adopts a much simpler strategy while still trying to keep the cell updates count * as low as possible to improve performance. See {@link VFXTableManager#onItemsChanged()}. *

- The columns' list is also managed automatically. Now it's even more convenient as it's not needed to pass * the table instance to the column object, rather the manager will automatically set the reference when they are * added/removed from the table. *

- The function used to generate the rows, called "rowFactory", can be changed anytime, even at runtime, see * {@link VFXTableManager#onRowFactoryChanged()}. *

- Core computations such as the range of rows, the range of columns, the estimated size, the layout of cells, etc., * are delegated to a separate 'helper' class which is the {@link VFXTableHelper}. There are two concrete implementations * for each of the {@link ColumnsLayoutMode}. You are allowed to change the helper through the {@link #helperFactoryProperty()}. *

- The vertical and horizontal positions are available through the properties {@link #hPosProperty()} and {@link #vPosProperty()} * It could indeed be possible to use a single property for the position, but they are split for performance reasons. *

- The virtual bounds of the container are given by two properties: *

 *     1) the {@link #virtualMaxXProperty()} which specifies the total number of pixels on the x-axis
 *     2) the {@link #virtualMaxYProperty()} which specifies the total number of pixels on the y-axis
 * 
*

- You can access the current state through the {@link #stateProperty()}. The state gives crucial information about * the container such as the rows range, the columns range and the visible rows (by index and by item). If you'd like to observe * for changes in the displayed items, then you want to add a listener on this property. See also {@link VFXTableState}. *

- It is possible to force the viewport to update the layout by invoking {@link #requestViewportLayout()}, * although this should never be necessary as it is automatically handled by "system". *

- The columns' size can be controlled through the {@link #columnsSizeProperty()}. * When using the {@link ColumnsLayoutMode#FIXED}, every column will have the width specified by the property. * Instead, when using the other mode {@link ColumnsLayoutMode#VARIABLE}, the value is treated as the minimum width * every column should have. The height is always the same for every column for obvious reasons. * (Make sure to also read {@link VFXTableColumn} to learn how columns' width is managed) *

The {@link #columnsLayoutModeProperty()} allows you to specify how to lay out and handle the columns. *

Just like the list and the grid, the table also makes use of buffers to render a couple more rows and columns to * make the scrolling smoother. There are two buffers, one for the columns {@link #columnsBufferSizeProperty()} and one * for the rows {@link #rowsBufferSizeProperty()}. Beware, since the {@link ColumnsLayoutMode#VARIABLE} basically disables * the virtualization alongside the x-axis, the columns' buffer won't be used, all columns will be rendered (although with * some internal optimizations). *

Also, just like the list and the grid, the table makes use of caches to store rows and cells that are not needed * anymore but could be used again in the future. One cache is here (table's class) and is responsible for storing rows * (since the row factory is also here), the other is in each {@link VFXTableColumn} and is responsible for storing cells * (since every column has its own cell factory). You can control the caches' capacity by the following properties: * {@link #rowsCacheCapacityProperty()}, {@link VFXTableColumn#cellsCacheCapacityProperty()}. *

 *     Note 1: the cache needs to know how to generate rows/cells for the {@link VFXCellsCache#populate()} feature to
 *             work properly. This is automatically handled by the table and columns, their factory will always be
 *             "shared" with their cache instances.
 *     Note 2: by default both capacities are set to 10 cells. However, for the table's nature, such number is likely to be
 *             too small, but it also depends from case to case. You can play around with the values and see if there's
 *             any benefit to performance.
 * 
*

* Other features {@literal &} Details *

- One of the shared features between other virtualized containers is the way layout requests are handled: by having a * read-only property {@link #needsViewportLayoutProperty()} and a way to request it {@link #requestViewportLayout()}. * However, the table handles request a bit differently. While in other containers, the property is just boolean value, * here the request is a custom class: {@link ViewportLayoutRequest}. The reason is simple, but solves a series of * inconvenient issues. First, the request can carry a column object that can be used by layout methods to optimize the process, * thus computing only a portion of the layout, this mainly useful when using the {@link ColumnsLayoutMode#VARIABLE} mode. * However, this is optional, meaning that if the column instance is {@code null}, then a full layout must be issued. * Second, requests can also act as callbacks, through a boolean property one can know if the layout was actually computed * or not. This is crucial to make the autosize feature work (see below). *

- The table allows you to autosize all or specific columns so that the content is fully shown. You can do so by * calling either: {@link #autosizeColumn(int)}, {@link #autosizeColumn(VFXTableColumn)} or {@link #autosizeColumns()}. * Their behavior depends on the set {@link ColumnsLayoutMode}. * In {@link ColumnsLayoutMode#VARIABLE} mode, columns will be resized to make their header and all their "children" cells fit the content. * In {@link ColumnsLayoutMode#FIXED} mode, since columns can't have different size, the algorithm chooses the greatest * needed width among all the columns and then sets the {@link #columnsSizeProperty()}. * Of course, the width computation is done on the currently shown items, meaning that if you scroll and there are now * items that are even bigger than the current set width, then you'll have to autosize again. *

- Columns' indexes. Since columns are stored in a list, there is not a fast way to retrieve * the index of a column from the instance itself, {@link List#indexOf(Object)} is too slow in the context of a virtualized * container. So, the system tries to avoid as much as possible to use columns' indexes, BUT implements a mechanism to * make it much, much faster. Every {@link VFXTableColumn} has a read-only property to store its index: {@link VFXTableColumn#indexProperty()}. * The system automatically updates the property at layout time (see {@link VFXTableSkin#updateColumnIndex(VFXTableColumn, int)}), * and offers a method {@link #indexOf(VFXTableColumn)} to retrieve it (it's more than just a getter!). *

- Columns re-ordering/swapping. Since table's columns are nodes which are part of the viewport, adding duplicates * to the list will generate a JavaFX exception. For this reason, any time you want to make some changes to the columns' * list, that may involve having duplicates in it, it's recommended to use a temporary new list and then use 'setAll'. * {@link VFXTableColumn} offers a utility method to swap to columns, so please use {@link VFXTableColumn#swapColumns(VFXTable, int, int)} * or {@link VFXTableColumn#swapColumns(ObservableList, int, int)} instead of {@link Collections#swap(List, int, int)}. * * @param the type of items in the table */ @SuppressWarnings({"rawtypes", "unchecked"}) public class VFXTable extends Control> implements VFXContainer, VFXStyleable, VFXScrollable { //================================================================================ // Properties //================================================================================ private final VFXCellsCache> cache; private final ListProperty items = new SimpleListProperty<>(FXCollections.observableArrayList()) { @Override public void set(ObservableList newValue) { if (newValue == null) newValue = FXCollections.observableArrayList(); super.set(newValue); } }; private final CellFactory> rowFactory = new CellFactory<>(this); private final ObservableList>> columns = FXCollections.observableArrayList(); private final ReadOnlyObjectWrapper> helper = new ReadOnlyObjectWrapper<>() { @Override public void set(VFXTableHelper newValue) { if (newValue == null) throw new NullPointerException("Table helper cannot be null!"); VFXTableHelper oldValue = get(); if (oldValue != null) oldValue.dispose(); super.set(newValue); } }; private final FunctionProperty> helperFactory = new FunctionProperty<>(defaultHelperFactory()) { @Override public void set(Function> newValue) { if (newValue == null) throw new NullPointerException("Helper helper factory cannot be null!"); super.set(newValue); } @Override protected void invalidated() { VFXTableHelper helper = get().apply(getColumnsLayoutMode()); setHelper(helper); } }; private final DoubleProperty vPos = PropUtils.clampedDoubleProperty( () -> 0.0, this::getMaxVScroll ); private final DoubleProperty hPos = PropUtils.clampedDoubleProperty( () -> 0.0, this::getMaxHScroll ); private final VFXTableStateProperty state = new VFXTableStateProperty<>(VFXTableState.INVALID); private final ViewportLayoutRequestProperty needsViewportLayout = new ViewportLayoutRequestProperty<>(); //================================================================================ // Constructors //================================================================================ public VFXTable() { cache = createCache(); initialize(); } public VFXTable(ObservableList items) { cache = createCache(); setItems(items); initialize(); } public VFXTable(ObservableList items, Collection>> columns) { cache = createCache(); setItems(items); this.columns.setAll(columns); initialize(); } //================================================================================ // Methods //================================================================================ private void initialize() { getStyleClass().setAll(defaultStyleClasses()); setDefaultBehaviorProvider(); setHelper(getHelperFactory().apply(getColumnsLayoutMode())); setRowFactory(defaultRowFactory()); } /** * Tries to retrieve a column from the columns' list by the given index * to then delegate to {@link #autosizeColumn(VFXTableColumn)}. */ public void autosizeColumn(int index) { try { VFXTableColumn> column = columns.get(index); if (column == null) return; autosizeColumn(column); } catch (Exception ignored) {} } /** * Auto-sizes a column so that its header and all its "children" cells' content is visible. * The actual resize is delegated to the helper: {@link VFXTableHelper#autosizeColumn(VFXTableColumn)}. *

* Note: this operation is peculiar in the sense that there are a few conditions to meet before the actual * resize is done. You see, to compute the maximum width to allow the content to fit, first the table, the columns * and the cells must have been laid out at least one time. So, if, when calling this method, the last layout request * was not processed ({@link ViewportLayoutRequest#wasDone()}), the operation is delayed, * and will run as soon as the condition is met. * To be precise, the operation could still be delayed, the other conditions are defined in the helper, * see {@link VFXTableHelper#autosizeColumn(VFXTableColumn)}. */ public void autosizeColumn(VFXTableColumn column) { if (getColumnsLayoutMode() == ColumnsLayoutMode.FIXED) return; When.onChanged(needsViewportLayout) .condition((o, n) -> n.wasDone()) .then((o, n) -> getHelper().autosizeColumn(column)) .oneShot(true) .executeNow(() -> getViewportLayoutRequest().wasDone()) .listen(); } /** * This will simply call {@link #autosizeColumn(VFXTableColumn)} on all the table's columns. * To be precise, the actual operation is delegated to the helper: {@link VFXTableHelper#autosizeColumns()}. *

* Just like {@link #autosizeColumn(VFXTableColumn)}, the operation could be delayed if the last layout request * was not processed {@link ViewportLayoutRequest#wasDone()} at the time calling this. */ public void autosizeColumns() { When.onChanged(needsViewportLayout) .condition((o, n) -> n.wasDone()) .then((o, n) -> getHelper().autosizeColumns()) .oneShot(true) .executeNow(() -> getViewportLayoutRequest().wasDone()) .listen(); } /** * Retrieves the given column's index in the table's columns' list. *

* Since every {@link VFXTableColumn} has its index as a property, {@link VFXTableColumn#indexProperty()}, this method * will simply invoke the related getter. * However, if the returned index is invalid {@literal (< 0)}, then it resorts to {@link List#indexOf(Object)} which is much * slower. The good thing is that if it resorts to the latter, then it also updates the column's index property, * so that the next time the index will be available through the property. */ public int indexOf(VFXTableColumn column) { if (column == null) return -1; if (column.getIndex() < 0) column.setIndex(columns.indexOf(column)); return column.getIndex(); } /** * Setter for the {@link #stateProperty()}. */ protected void update(VFXTableState state) { setState(state); } /** * Responsible for creating the rows' cache instance used by this container. * * @see VFXCellsCache * @see #rowsCacheCapacityProperty() */ protected VFXCellsCache> createCache() { return new VFXCellsCache<>(rowFactory, getRowsCacheCapacity()); } /** * @return the default function used to build rows. Uses {@link VFXDefaultTableRow}. */ protected Function> defaultRowFactory() { return VFXDefaultTableRow::new; } /** * @return the default function used to build a {@link VFXTableHelper}. */ protected Function> defaultHelperFactory() { return mode -> mode == ColumnsLayoutMode.FIXED ? new FixedTableHelper<>(this) : new VariableTableHelper<>(this); } /** * Setter for the {@link #needsViewportLayoutProperty()}. * This sets the property to {@link ViewportLayoutRequest#EMPTY}, causing the default skin to recompute the entire layout. */ public void requestViewportLayout() { setNeedsViewportLayout(ViewportLayoutRequest.EMPTY.setWasDone(false)); } /** * Setter for the {@link #needsViewportLayoutProperty()}. * This sets the property to a new {@link ViewportLayoutRequest} with the given column, causing the default skin to * recompute only a portion of the layout. */ protected void requestViewportLayout(VFXTableColumn column) { setNeedsViewportLayout(new ViewportLayoutRequest(column).setWasDone(false)); } //================================================================================ // Overridden Methods //================================================================================ /** * {@inheritDoc} *

* Note that this may be a costly operation due to nested loops. Since cells are inside rows we must first iterate * over the rows, then iterate on each of their cells and fire an update event on each of them. */ @Override public void update(int... indexes) { VFXTableState state = getState(); if (state.isEmpty()) return; if (indexes.length == 0) { state.getRowsByIndex().values().forEach(r -> r.getCellsByIndex().values().forEach(VFXContainerEvent::update) ); return; } for (int index : indexes) { VFXTableRow row = state.getRowsByIndex().get(index); if (row == null) continue; row.getCellsByIndex().values().forEach(VFXContainerEvent::update); } } @Override protected SkinBase buildSkin() { return new VFXTableSkin<>(this); } @Override public Supplier> defaultBehaviorProvider() { return () -> new VFXTableManager<>(this); } @Override public List defaultStyleClasses() { return List.of("vfx-table"); } @Override public VFXScrollPane makeScrollable() { return new VFXScrollPane(this).bindTo(this); } //================================================================================ // Delegate Methods //================================================================================ /** * Delegate for {@link VFXCellsCache#populate()} (on the rows' cache). * * @see #populateCacheAll() */ public VFXTable populateCache() { cache.populate(); return this; } /** * Populates the rows' cache and all the table's columns' caches. * * @see VFXCellsCache#populate() */ public VFXTable populateCacheAll() { populateCache(); columns.forEach(VFXTableColumn::populateCache); return this; } /** * Delegate for {@link VFXCellsCache#size()} (on the row's cache). */ public int rowsCacheSize() { return cache.size(); } /** * @return the total number of cached cells by iterating over {@link #getColumns()}. */ public int cellsCacheSize() { return columns.stream() .mapToInt(VFXTableColumn::cacheSize) .sum(); } /** * Delegate for {@link VFXTableState#getRowsRange()} */ public IntegerRange getRowsRange() {return getState().getRowsRange();} /** * Delegate for {@link VFXTableState#getColumnsRange()} */ public IntegerRange getColumnsRange() {return getState().getColumnsRange();} /** * Delegate for {@link VFXTableState#getRowsByIndexUnmodifiable()} */ public SequencedMap> getRowsByIndexUnmodifiable() {return getState().getRowsByIndexUnmodifiable();} /** * Delegate for {@link VFXTableState#getRowsByItemUnmodifiable()} */ public List>> getRowsByItemUnmodifiable() {return getState().getRowsByItemUnmodifiable();} /** * Delegate for {@link VFXTableHelper#virtualMaxXProperty()} */ @Override public ReadOnlyDoubleProperty virtualMaxXProperty() { return getHelper().virtualMaxXProperty(); } /** * Delegate for {@link VFXTableHelper#virtualMaxYProperty()} */ @Override public ReadOnlyDoubleProperty virtualMaxYProperty() { return getHelper().virtualMaxYProperty(); } /** * Delegate for {@link VFXTableHelper#maxVScrollProperty()}. */ @Override public ReadOnlyDoubleProperty maxVScrollProperty() { return getHelper().maxVScrollProperty(); } /** * Delegate for {@link VFXTableHelper#maxHScrollProperty()}. */ @Override public ReadOnlyDoubleProperty maxHScrollProperty() { return getHelper().maxHScrollProperty(); } /** * {@inheritDoc} *

* For the table this is a delegate to {@link #rowsCacheCapacityProperty()}, so that it can honor the * {@link VFXContainer} API. */ @Override public StyleableObjectProperty bufferSizeProperty() { return rowsBufferSize; } /** * Delegate for {@link VFXTableHelper#scrollBy(Orientation, double)} with vertical orientation as parameter. */ public void scrollVerticalBy(double pixels) { getHelper().scrollBy(Orientation.VERTICAL, pixels); } /** * Delegate for {@link VFXTableHelper#scrollBy(Orientation, double)} with horizontal orientation as parameter. */ public void scrollHorizontalBy(double pixels) { getHelper().scrollBy(Orientation.HORIZONTAL, pixels); } /** * Delegate for {@link VFXTableHelper#scrollToPixel(Orientation, double)} with vertical orientation as parameter. */ public void scrollToPixelVertical(double pixel) { getHelper().scrollToPixel(Orientation.VERTICAL, pixel); } /** * Delegate for {@link VFXTableHelper#scrollToPixel(Orientation, double)} with horizontal orientation as parameter. */ public void scrollToPixelHorizontal(double pixel) { getHelper().scrollToPixel(Orientation.HORIZONTAL, pixel); } /** * Delegate for {@link VFXTableHelper#scrollToIndex(Orientation, int)} with vertical orientation as parameter. */ public void scrollToRow(int index) { getHelper().scrollToIndex(Orientation.VERTICAL, index); } /** * Delegate for {@link VFXTableHelper#scrollToIndex(Orientation, int)} with horizontal orientation as parameter. */ public void scrollToColumn(int index) { getHelper().scrollToIndex(Orientation.HORIZONTAL, index); } /** * Delegate for {@link #scrollToRow(int)} with 0 as parameter. */ public void scrollToFirstRow() { scrollToRow(0); } /** * Delegate for {@link #scrollToRow(int)} with {@code size() - 1} as parameter. */ public void scrollToLastRow() { scrollToRow(size() - 1); } /** * Delegate for {@link #scrollToColumn(int)} with 0 as parameter. */ public void scrollToFirstColumn() { scrollToColumn(0); } /** * Delegate for {@link #scrollToColumn(int)} with {@code columns.size() - 1} as parameter. */ public void scrollToLastColumn() { scrollToColumn(columns.size() - 1); } //================================================================================ // Styleable Properties //================================================================================ private final StyleableDoubleProperty rowsHeight = new StyleableDoubleProperty( StyleableProperties.ROWS_HEIGHT, this, "rowsHeight", 32.0 ); private final StyleableSizeProperty columnsSize = new StyleableSizeProperty( StyleableProperties.COLUMNS_SIZE, this, "columnsSize", Size.of(100.0, 32.0) ); private final StyleableObjectProperty columnsLayoutMode = new StyleableObjectProperty<>( StyleableProperties.COLUMNS_LAYOUT_MODE, this, "columnsLayoutMode", ColumnsLayoutMode.FIXED ) { @Override protected void invalidated() { setHelper(getHelperFactory().apply(get())); } }; private final StyleableDoubleProperty extraAutosizeWidth = new StyleableDoubleProperty( StyleableProperties.EXTRA_AUTOSIZE_WIDTH, this, "extraAutosizeWidth", 0.0 ); private final StyleableObjectProperty columnsBufferSize = new StyleableObjectProperty<>( StyleableProperties.COLUMNS_BUFFER_SIZE, this, "columnsBufferSize", BufferSize.standard() ); private final StyleableObjectProperty rowsBufferSize = new StyleableObjectProperty<>( StyleableProperties.ROWS_BUFFER_SIZE, this, "rowsBufferSize", BufferSize.standard() ); private final StyleableDoubleProperty clipBorderRadius = new StyleableDoubleProperty( StyleableProperties.CLIP_BORDER_RADIUS, this, "clipBorderRadius", 0.0 ); private final StyleableIntegerProperty rowsCacheCapacity = new StyleableIntegerProperty( StyleableProperties.ROWS_CACHE_CAPACITY, this, "rowsCacheCapacity", 10 ) { @Override protected void invalidated() { cache.setCapacity(get()); } }; public double getRowsHeight() { return rowsHeight.get(); } /** * Specifies the fixed height for all the table's rows. *

* Note that the default {@link VFXTableHelper} implementations will also set the cells' height to this value, * however you can modify such behavior if needed by providing your custom implementation through the * {@link #helperFactoryProperty()}. *

* Can be set in CSS via the property: '-vfx-rows-height'. */ public StyleableDoubleProperty rowsHeightProperty() { return rowsHeight; } public void setRowsHeight(double rowsHeight) { this.rowsHeight.set(rowsHeight); } public Size getColumnsSize() { return columnsSize.get(); } /** * Specifies the columns' size as a {@link Size} object. *

* Note that the width specified by this property will be used differently depending on the {@link ColumnsLayoutMode}. * In {@code FIXED} mode, all columns will have the same width and height specified by the {@link Size} object. * In {@code VARIABLE} mode, the width value will be used as the minimum width all columns must have. * This behavior can also be modified as it is defined by the default {@link VFXTableHelper} implementations. *

* Can be set in CSS via the property: '-vfx-columns-size'. * * @see SizeConverter */ public StyleableSizeProperty columnsSizeProperty() { return columnsSize; } public void setColumnsSize(Size columnsSize) { this.columnsSize.set(columnsSize); } /** * Convenience method to create a new {@link Size} object and set the {@link #columnsSizeProperty()}. */ public void setColumnsSize(double w, double h) { setColumnsSize(Size.of(w, h)); } /** * Convenience method to create a new {@link Size} object and set the {@link #columnsSizeProperty()}. * The old height will be kept. */ public void setColumnsWidth(double w) { setColumnsSize(Size.of(w, getColumnsSize().getHeight())); } /** * Convenience method to create a new {@link Size} object and set the {@link #columnsSizeProperty()}. * The old width will be kept. */ public void setColumnsHeight(double h) { setColumnsSize(Size.of(getColumnsSize().getWidth(), h)); } public ColumnsLayoutMode getColumnsLayoutMode() { return columnsLayoutMode.get(); } /** * Specifies the layout mode for the table's columns. See {@link ColumnsLayoutMode}. *

* Can be set in CSS via the property: '-vfx-columns-layout-mode'. */ public StyleableObjectProperty columnsLayoutModeProperty() { return columnsLayoutMode; } public void setColumnsLayoutMode(ColumnsLayoutMode columnsLayoutMode) { this.columnsLayoutMode.set(columnsLayoutMode); } /** * Convenience method to switch the table's {@link ColumnsLayoutMode}. */ public void switchColumnsLayoutMode() { this.columnsLayoutMode.set(ColumnsLayoutMode.next(getColumnsLayoutMode())); } public double getExtraAutosizeWidth() { return extraAutosizeWidth.get(); } /** * Specifies an extra number of pixels a column will have when it is auto-sized by the {@link VFXTableHelper}. *

* In some occasions auto-sizing all the columns may result in the text of each cell being very close to each other, * which is rather unpleasant to see. This extra amount acts like a "spacing" property between the columns * when auto-sizing. *

* Can be set in CSS via the property: '-fx-extra-autosize-width'. */ public StyleableDoubleProperty extraAutosizeWidthProperty() { return extraAutosizeWidth; } public void setExtraAutosizeWidth(double extraAutosizeWidth) { this.extraAutosizeWidth.set(extraAutosizeWidth); } public BufferSize getColumnsBufferSize() { return columnsBufferSize.get(); } /** * Specifies the number of extra columns to add to the viewport to make scrolling smoother. * See also {@link VFXContainer#bufferSizeProperty()} and {@link VFXTableHelper#totalRows()} *

* Can be set in CSS via the property: '-vfx-columns-buffer-size'. */ public StyleableObjectProperty columnsBufferSizeProperty() { return columnsBufferSize; } public void setColumnsBufferSize(BufferSize columnsBufferSize) { this.columnsBufferSize.set(columnsBufferSize); } public BufferSize getRowsBufferSize() { return rowsBufferSize.get(); } /** * Specifies the number of extra rows to add to the viewport to make scrolling smoother. * See also {@link VFXContainer#bufferSizeProperty()} and {@link VFXTableHelper#totalRows()}. *

* Can be set in CSS via the property: '-vfx-columns-buffer-size'. */ public StyleableObjectProperty rowsBufferSizeProperty() { return rowsBufferSize; } public void setRowsBufferSize(BufferSize rowsBufferSize) { this.rowsBufferSize.set(rowsBufferSize); } public double getClipBorderRadius() { return clipBorderRadius.get(); } /** * Used by the viewport's clip to set its border radius. * This is useful when you want to make a rounded container, this prevents the content from going outside the view. *

* Side note: the clip is a {@link Rectangle}, now for some fucking reason, the rectangle's arcWidth and arcHeight * values used to make it round do not act like the border-radius or background-radius properties, * instead their value is usually 2 / 2.5 times the latter. * So, for a border radius of 5, you want this value to be at least 10/13. *

* Can be set in CSS via the property: '-vfx-clip-border-radius'. */ public StyleableDoubleProperty clipBorderRadiusProperty() { return clipBorderRadius; } public void setClipBorderRadius(double clipBorderRadius) { this.clipBorderRadius.set(clipBorderRadius); } public int getRowsCacheCapacity() { return rowsCacheCapacity.get(); } /** * Specifies the maximum number of rows the cache can contain at any time. Excess will not be added to the queue and * disposed immediately. *

* Can be set in CSS via the property: '-vfx-rows-cache-capacity'. */ public StyleableIntegerProperty rowsCacheCapacityProperty() { return rowsCacheCapacity; } public void setRowsCacheCapacity(int rowsCacheCapacity) { this.rowsCacheCapacity.set(rowsCacheCapacity); } //================================================================================ // CssMetaData //================================================================================ private static class StyleableProperties { private static final StyleablePropertyFactory> FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData()); private static final List> cssMetaDataList; private static final CssMetaData, Number> ROWS_HEIGHT = FACTORY.createSizeCssMetaData( "-vfx-rows-height", VFXTable::rowsHeightProperty, 32.0 ); private static final CssMetaData, Size> COLUMNS_SIZE = new CssMetaData<>("-vfx-columns-size", SizeConverter.getInstance(), Size.of(100, 32)) { @Override public boolean isSettable(VFXTable styleable) { return !styleable.columnsSizeProperty().isBound(); } @Override public StyleableProperty getStyleableProperty(VFXTable styleable) { return styleable.columnsSizeProperty(); } }; private static final CssMetaData, ColumnsLayoutMode> COLUMNS_LAYOUT_MODE = FACTORY.createEnumCssMetaData( ColumnsLayoutMode.class, "-vfx-columns-layout-mode", VFXTable::columnsLayoutModeProperty, ColumnsLayoutMode.FIXED ); private static final CssMetaData, Number> EXTRA_AUTOSIZE_WIDTH = FACTORY.createSizeCssMetaData( "-vfx-extra-autosize-width", VFXTable::extraAutosizeWidthProperty, 0.0 ); private static final CssMetaData, BufferSize> COLUMNS_BUFFER_SIZE = FACTORY.createEnumCssMetaData( BufferSize.class, "-vfx-columns-buffer-size", VFXTable::columnsBufferSizeProperty, BufferSize.standard() ); private static final CssMetaData, BufferSize> ROWS_BUFFER_SIZE = FACTORY.createEnumCssMetaData( BufferSize.class, "-vfx-rows-buffer-size", VFXTable::rowsBufferSizeProperty, BufferSize.standard() ); private static final CssMetaData, Number> CLIP_BORDER_RADIUS = FACTORY.createSizeCssMetaData( "-vfx-clip-border-radius", VFXTable::clipBorderRadiusProperty, 0.0 ); private static final CssMetaData, Number> ROWS_CACHE_CAPACITY = FACTORY.createSizeCssMetaData( "-vfx-rows-cache-capacity", VFXTable::rowsCacheCapacityProperty, 10 ); static { cssMetaDataList = StyleUtils.cssMetaDataList( Control.getClassCssMetaData(), ROWS_HEIGHT, COLUMNS_SIZE, COLUMNS_LAYOUT_MODE, EXTRA_AUTOSIZE_WIDTH, COLUMNS_BUFFER_SIZE, ROWS_BUFFER_SIZE, CLIP_BORDER_RADIUS, ROWS_CACHE_CAPACITY ); } } @Override protected List> getControlCssMetaData() { return getClassCssMetaData(); } public static List> getClassCssMetaData() { return StyleableProperties.cssMetaDataList; } //================================================================================ // Getters/Setters //================================================================================ /** * @return the rows' cache instance used by this container */ protected VFXCellsCache> getCache() { return cache; } @Override public ListProperty itemsProperty() { return items; } public Function> getRowFactory() { return rowFactory.getValue(); } /** * Specifies the function used to build the table's rows. * See also {@link #defaultRowFactory()}. */ public CellFactory> rowFactoryProperty() { return rowFactory; } public void setRowFactory(Function> rowFactory) { this.rowFactory.setValue(rowFactory); } /** * This is the observable list containing all the table's columns. */ public ObservableList>> getColumns() { return columns; } public VFXTableHelper getHelper() { return helper.get(); } /** * Specifies the instance of the {@link VFXTableHelper} built by the {@link #helperFactoryProperty()}. */ public ReadOnlyObjectWrapper> helperProperty() { return helper; } public void setHelper(VFXTableHelper helper) { this.helper.set(helper); } public Function> getHelperFactory() { return helperFactory.get(); } /** * Specifies the function used to build a {@link VFXTableHelper} instance. * See also {@link #defaultHelperFactory()}. */ public FunctionProperty> helperFactoryProperty() { return helperFactory; } public void setHelperFactory(Function> helperFactory) { this.helperFactory.set(helperFactory); } @Override public DoubleProperty vPosProperty() { return vPos; } @Override public DoubleProperty hPosProperty() { return hPos; } public VFXTableState getState() { return state.get(); } /** * Specifies the container's current state. The state carries useful information such as the range of rows and columns * and the rows ordered by index, or by item (not ordered). */ public ReadOnlyObjectProperty> stateProperty() { return state.getReadOnlyProperty(); } protected void setState(VFXTableState state) { this.state.set(state); } /** * Delegate for {@link ViewportLayoutRequest#isValid()}. */ public boolean isNeedsViewportLayout() { return needsViewportLayout.isValid(); } public ViewportLayoutRequest getViewportLayoutRequest() { return needsViewportLayout.get(); } /** * Specifies whether the viewport needs to compute the layout of its content. *

* Since this is read-only, layout requests must be sent by using {@link #requestViewportLayout()}. */ public ReadOnlyObjectProperty> needsViewportLayoutProperty() { return needsViewportLayout.getReadOnlyProperty(); } protected void setNeedsViewportLayout(ViewportLayoutRequest needsViewportLayout) { this.needsViewportLayout.set(needsViewportLayout); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy