Skip to content

Commit

Permalink
Major refactoring of VirtualFlow internals.
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasMikula committed Feb 9, 2015
1 parent fffb5fe commit ab10be6
Show file tree
Hide file tree
Showing 14 changed files with 1,781 additions and 1,694 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ As noted before, for the purposes of virtual flow in Flowless the cells are just
* `computeMinHeight(-1)`
* `computePrefHeight(-1)`
* `computePrefWidth(height)`
* Cells are not expected to change their size _spontaneously_, i.e. without changing the item they are holding. Changes to their min/pref/max size will be ignored.

Include Flowless in your project
--------------------------------
Expand All @@ -70,25 +69,25 @@ Include Flowless in your project

| Group ID | Artifact ID | Version |
| :---------: | :---------: | :-----: |
| org.fxmisc.flowless | flowless | 0.3 |
| org.fxmisc.flowless | flowless | 0.4 |

#### Gradle example

```groovy
dependencies {
compile group: 'org.fxmisc.flowless', name: 'flowless', version: '0.3'
compile group: 'org.fxmisc.flowless', name: 'flowless', version: '0.4'
}
```

#### Sbt example

```scala
libraryDependencies += "org.fxmisc.flowless" % "flowless" % "0.3"
libraryDependencies += "org.fxmisc.flowless" % "flowless" % "0.4"
```

#### Manual download

Download the [0.3 jar](https://github.com/TomasMikula/Flowless/releases/tag/v0.3) and place it on your classpath.
Download the [0.4 jar](https://github.com/TomasMikula/Flowless/releases/tag/v0.4) and place it on your classpath.

Documentation
-------------
Expand Down
7 changes: 4 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = '0.4-SNAPSHOT'
version = '0.4'

apply plugin: 'java'
apply plugin: 'eclipse'
Expand All @@ -15,8 +15,9 @@ targetCompatibility = '1.8'
group = 'org.fxmisc.flowless'

dependencies {
compile group: 'org.fxmisc.easybind', name: 'easybind', version: '[1.0.2,)'
testCompile group: 'junit', name: 'junit', version: '[4.0,)'
compile 'org.reactfx:reactfx:2.0-M2u1'
compile 'org.fxmisc.easybind:easybind:[1.0.3,)'
testCompile 'junit:junit:[4.0,)'
}

javadoc {
Expand Down
24 changes: 15 additions & 9 deletions src/main/java/org/fxmisc/flowless/Cell.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package org.fxmisc.flowless;

import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntConsumer;

import javafx.scene.Node;

@FunctionalInterface
public interface Cell<T, N extends Node> {
static <T, N extends Node> Cell<T, N> wrapNode(N node) {
return () -> node;
return new Cell<T, N>() {

@Override
public N getNode() { return node; }

@Override
public String toString() { return node.toString(); }
};
}

N getNode();
Expand All @@ -23,17 +30,16 @@ default boolean isReusable() {
}

/**
* If this cell is reusable ({@link #isReusable()} returns {@code true}),
* If this cell is reusable (as indicated by {@link #isReusable()}),
* this method is called to display a different item. {@link #reset()}
* will have been called before a call to this method.
*
* <p>The default implementation throws
* {@link UnsupportedOperationException}.
*
* @param index index of the new item
* @param item the new item to display
*/
default void updateItem(int index, T item) {
default void updateItem(T item) {
throw new UnsupportedOperationException();
}

Expand All @@ -49,7 +55,7 @@ default void updateIndex(int index) {
/**
* Called when this cell is no longer used to display its item.
* If this cell is reusable, it may later be asked to display a different
* item by a call to {@link #updateItem(int, Object)}.
* item by a call to {@link #updateItem(Object)}.
*
* <p>Default implementation does nothing.
*/
Expand Down Expand Up @@ -83,11 +89,11 @@ default Cell<T, N> afterReset(Runnable action) {
return CellWrapper.afterReset(this, action);
}

default Cell<T, N> beforeUpdateItem(BiConsumer<Integer, T> action) {
default Cell<T, N> beforeUpdateItem(Consumer<? super T> action) {
return CellWrapper.beforeUpdateItem(this, action);
}

default Cell<T, N> afterUpdateItem(BiConsumer<Integer, T> action) {
default Cell<T, N> afterUpdateItem(Consumer<? super T> action) {
return CellWrapper.afterUpdateItem(this, action);
}

Expand All @@ -98,4 +104,4 @@ default Cell<T, N> beforeUpdateIndex(IntConsumer action) {
default Cell<T, N> afterUpdateIndex(IntConsumer action) {
return CellWrapper.afterUpdateIndex(this, action);
}
}
}
147 changes: 147 additions & 0 deletions src/main/java/org/fxmisc/flowless/CellListManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package org.fxmisc.flowless;

import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Function;

import javafx.collections.ObservableList;
import javafx.scene.Node;

import org.reactfx.Subscription;
import org.reactfx.collection.LiveList;
import org.reactfx.collection.MemoizationList;
import org.reactfx.collection.QuasiListModification;

final class CellListManager<T, C extends Cell<T, ?>> {

private final CellPool<T, C> cellPool;
private final MemoizationList<C> cells;
private final LiveList<C> presentCells;
private final LiveList<Node> cellNodes;

private final Subscription presentCellsSubscription;

public CellListManager(
ObservableList<T> items,
Function<? super T, ? extends C> cellFactory) {
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();
}

public ObservableList<Node> getNodes() {
return cellNodes;
}

public MemoizationList<C> 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<C> getCellIfPresent(int itemIndex) {
return cells.getIfMemoized(itemIndex); // getIfMemoized() may throw
}

public C getCell(int itemIndex) {
return cells.get(itemIndex);
}

public OptionalInt lastPresentBefore(int position) {
int presentBefore = cells.getMemoizedCountBefore(position);
return presentBefore > 0
? OptionalInt.of(cells.indexOfMemoizedItem(presentBefore - 1))
: OptionalInt.empty();
}

public OptionalInt firstPresentAfter(int position) {
int presentBefore = cells.getMemoizedCountBefore(position);
int presentAfter = cells.getMemoizedCountAfter(position);

return presentAfter > 0
? OptionalInt.of(cells.indexOfMemoizedItem(presentBefore))
: OptionalInt.empty();
}

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);
// Node node = cell.getNode();
//
// // apply CSS when the node is added to the scene
// EventStreams.nonNullValuesOf(node.sceneProperty())
// .subscribeForOne(scene -> {
//// applySkins(node);
// node.applyCss();
// });

return cell;
}

// private static final Method createDefaultSkin;
// static {
// try {
// createDefaultSkin = Control.class.getDeclaredMethod("createDefaultSkin");
// } catch (NoSuchMethodException | SecurityException e) {
// throw new RuntimeException("This is too bad", e);
// }
// createDefaultSkin.setAccessible(true);
// }
// private static void applySkins(Node node) {
// if(node instanceof Parent) {
// Parent parent = (Parent) node;
// if(parent instanceof Control) {
// Control control = (Control) parent;
// Skin<?> skin = control.getSkin();
// System.out.println("skin for " + control + " is " + skin);
// if(skin == null) {
// try {
// skin = (Skin<?>) createDefaultSkin.invoke(control);
// } catch (IllegalAccessException | IllegalArgumentException
// | InvocationTargetException e) {
// throw new RuntimeException("Oops!", e);
// }
// control.setSkin(skin);
// }
// }
// for(Node child: parent.getChildrenUnmodifiable()) {
// applySkins(child);
// }
// }
// }

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));
}
}
}
41 changes: 41 additions & 0 deletions src/main/java/org/fxmisc/flowless/CellPool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.fxmisc.flowless;

import java.util.LinkedList;
import java.util.Queue;
import java.util.function.Function;

final class CellPool<T, C extends Cell<T, ?>> {
private final Function<? super T, ? extends C> cellFactory;
private final Queue<C> pool = new LinkedList<>();

public CellPool(Function<? super T, ? extends C> cellFactory) {
this.cellFactory = cellFactory;
}

public C getCell(T item) {
C cell = pool.poll();
if(cell != null) {
cell.updateItem(item);
} else {
cell = cellFactory.apply(item);
}
return cell;
}

public void acceptCell(C cell) {
cell.reset();
if(cell.isReusable()) {
pool.add(cell);
} else {
cell.dispose();
}
}

public void dispose() {
for(C cell: pool) {
cell.dispose();
}

pool.clear();
}
}
Loading

0 comments on commit ab10be6

Please sign in to comment.