From 8b82d596e68e97470bdd09770f9a963722180694 Mon Sep 17 00:00:00 2001 From: Jurgen Date: Thu, 3 Sep 2020 11:32:36 +0200 Subject: [PATCH] fix estimated scroll bidirectional binding (#79) --- .../flowless/StableBidirectionalVar.java | 95 +++++++++++++++++++ .../java/org/fxmisc/flowless/VirtualFlow.java | 2 +- 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/fxmisc/flowless/StableBidirectionalVar.java diff --git a/src/main/java/org/fxmisc/flowless/StableBidirectionalVar.java b/src/main/java/org/fxmisc/flowless/StableBidirectionalVar.java new file mode 100644 index 0000000..65abb09 --- /dev/null +++ b/src/main/java/org/fxmisc/flowless/StableBidirectionalVar.java @@ -0,0 +1,95 @@ +package org.fxmisc.flowless; + +import java.util.function.Consumer; + +import org.reactfx.Subscription; +import org.reactfx.value.ProxyVal; +import org.reactfx.value.Val; +import org.reactfx.value.Var; + +import javafx.application.Platform; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +/** + * This class overrides the Var.bindBidirectional method implementing a mechanism to prevent looping. + *
By default bindBidirectional delegates to Bindings.bindBidirectional + * which isn't always stable for the Val -> Var paradigm, sometimes producing a continuous feedback loop.
+ */ +class StableBidirectionalVar extends ProxyVal implements Var +{ + private final Consumer setval; + private Subscription binding = null; + private ChangeListener left, right; + private T last = null; + + StableBidirectionalVar( Val underlying, Consumer setter ) + { + super( underlying ); + setval = setter; + } + + @Override + public T getValue() + { + return getUnderlyingObservable().getValue(); + } + + @Override + protected Consumer adaptObserver( Consumer observer ) + { + return observer; // no adaptation needed + } + + @Override + public void bind( ObservableValue observable ) + { + unbind(); + binding = Val.observeChanges( observable, (ob,ov,nv) -> setValue( nv ) ); + setValue( observable.getValue() ); + } + + @Override + public boolean isBound() + { + return binding != null; + } + + @Override + public void unbind() + { + if( binding != null ) binding.unsubscribe(); + binding = null; + } + + @Override + public void setValue( T newVal ) + { + setval.accept( newVal ); + } + + @Override + public void unbindBidirectional( Property prop ) + { + if ( right != null ) prop.removeListener( right ); + if ( left != null ) removeListener( left ); + left = null; right = null; + } + + @Override + public void bindBidirectional( Property prop ) + { + unbindBidirectional( prop ); + prop.addListener( right = (ob,ov,nv) -> adjustOther( this, nv ) ); + addListener( left = (ob,ov,nv) -> adjustOther( prop, nv ) ); + } + + private void adjustOther( Property other, T nv ) + { + if ( ! nv.equals( last ) ) + { + Platform.runLater( () -> other.setValue( nv ) ); + last = nv; + } + } +} diff --git a/src/main/java/org/fxmisc/flowless/VirtualFlow.java b/src/main/java/org/fxmisc/flowless/VirtualFlow.java index 3397f96..d72caf6 100644 --- a/src/main/java/org/fxmisc/flowless/VirtualFlow.java +++ b/src/main/java/org/fxmisc/flowless/VirtualFlow.java @@ -180,7 +180,7 @@ private VirtualFlow( layoutBoundsProperty(), b -> new Rectangle(b.getWidth(), b.getHeight()))); - lengthOffsetEstimate = sizeTracker.lengthOffsetEstimateProperty().asVar(this::setLengthOffset); + lengthOffsetEstimate = new StableBidirectionalVar<>( sizeTracker.lengthOffsetEstimateProperty(), this::setLengthOffset ); // scroll content by mouse scroll this.addEventHandler(ScrollEvent.ANY, se -> {