org.fxmisc.flowless.CellListManager Maven / Gradle / Ivy
package org.fxmisc.flowless;
import java.util.Optional;
import java.util.function.Function;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.input.ScrollEvent;
import org.reactfx.EventStreams;
import org.reactfx.Subscription;
import org.reactfx.collection.LiveList;
import org.reactfx.collection.MemoizationList;
import org.reactfx.collection.QuasiListModification;
/**
* Tracks all of the cells that the viewport can display ({@link #cells}) and which cells the viewport is currently
* displaying ({@link #presentCells}).
*/
final class CellListManager> {
private final Node owner;
private final CellPool cellPool;
private final MemoizationList cells;
private final LiveList presentCells;
private final LiveList cellNodes;
private final Subscription presentCellsSubscription;
public CellListManager(
Node owner,
ObservableList items,
Function super T, ? extends C> cellFactory) {
this.owner = owner;
this.cellPool = new CellPool<>(cellFactory);
this.cells = LiveList.map(items, this::cellForItem).memoize();
this.presentCells = cells.memoizedItems();
this.cellNodes = presentCells.map(Cell::getNode);
this.presentCellsSubscription = presentCells.observeQuasiModifications(this::presentCellsChanged);
}
public void dispose() {
// return present cells to pool *before* unsubscribing,
// because stopping to observe memoized items may clear memoized items
presentCells.forEach(cellPool::acceptCell);
presentCellsSubscription.unsubscribe();
cellPool.dispose();
}
/** Gets the list of nodes that the viewport is displaying */
public ObservableList getNodes() {
return cellNodes;
}
public MemoizationList getLazyCellList() {
return cells;
}
public boolean isCellPresent(int itemIndex) {
return cells.isMemoized(itemIndex);
}
public C getPresentCell(int itemIndex) {
// both getIfMemoized() and get() may throw
return cells.getIfMemoized(itemIndex).get();
}
public Optional getCellIfPresent(int itemIndex) {
return cells.getIfMemoized(itemIndex); // getIfMemoized() may throw
}
public C getCell(int itemIndex) {
return cells.get(itemIndex);
}
/**
* Updates the list of cells to display
*
* @param fromItem the index of the first item to display
* @param toItem the index of the last item to display
*/
public void cropTo(int fromItem, int toItem) {
fromItem = Math.max(fromItem, 0);
toItem = Math.min(toItem, cells.size());
cells.forget(0, fromItem);
cells.forget(toItem, cells.size());
}
private C cellForItem(T item) {
C cell = cellPool.getCell(item);
// apply CSS when the cell is first added to the scene
Node node = cell.getNode();
EventStreams.nonNullValuesOf(node.sceneProperty())
.subscribeForOne(scene -> {
node.applyCss();
});
// Make cell initially invisible.
// It will be made visible when it is positioned.
node.setVisible(false);
if (cell.isReusable()) {
// if cell is reused i think adding event handler
// would cause resource leakage.
node.setOnScroll(this::pushScrollEvent);
node.setOnScrollStarted(this::pushScrollEvent);
node.setOnScrollFinished(this::pushScrollEvent);
} else {
node.addEventHandler(ScrollEvent.ANY, this::pushScrollEvent);
}
return cell;
}
/**
* Push scroll events received by cell nodes directly to
* the 'owner' Node. (Generally likely to be a VirtualFlow
* but not required.)
*
* Normal bubbling of scroll events gets interrupted during
* a scroll gesture when the Cell's Node receiving the event
* has moved out of the viewport and is thus removed from
* the Navigator's children list. This breaks expected trackpad
* scrolling behaviour, at least on macOS.
*
* So here we take over event-bubbling duties for ScrollEvent
* and push them ourselves directly to the given owner.
*/
private void pushScrollEvent(ScrollEvent se) {
owner.fireEvent(se);
se.consume();
}
private void presentCellsChanged(QuasiListModification extends C> mod) {
// add removed cells back to the pool
for(C cell: mod.getRemoved()) {
cellPool.acceptCell(cell);
}
// update indices of added cells and cells after the added cells
for(int i = mod.getFrom(); i < presentCells.size(); ++i) {
presentCells.get(i).updateIndex(cells.indexOfMemoizedItem(i));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy