package org.fxmisc.easybind; import java.util.List; import java.util.function.Supplier; import javafx.beans.property.Property; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; /** * Starting point for creation of conditional bindings. Encapsulates a boolean * condition and provides fluent API to create conditional bindings based on * that condition. */ public final class When { private final ObservableValue<Boolean> condition; When(ObservableValue<Boolean> condition) { this.condition = condition; } /** * Sets up automatic binding and unbinding of {@code target} to/from * {@code source}, based on the changing value of the encapsulated * condition. In other words, whenever the encapsulated condition is * {@code true}, {@code target} is bound to {@code source}. Whenever the * encapsulated condition is {@code false}, {@code target} is unbound. * This keeps happening until {@code unsubscribe()} is called on the * returned subscription. Unsubscribing the returned subscription may be * skipped safely only when the lifetimes of all the encapsulated condition, * {@code source} and {@code target} are the same. * @param target target of the conditional binding * @param source source of the conditional binding * @return a subscription that can be used to dispose the conditional * binding set up by this method, i.e. to stop observing the encapsulated * condition and, if the last observed value of the encapsulated condition * was {@code true}, unbind {@code target} from {@code source}. */ public <T> Subscription bind( Property<T> target, ObservableValue<? extends T> source) { return bind(() -> { target.bind(source); return target::unbind; }); } /** * Sets up automatic binding and unbinding of {@code target}'s items to/from * {@code source}'s items, based on the changing value of the encapsulated * condition. In other words, whenever the encapsulated condition is * {@code true}, {@code target}'s content is synced with {@code source}. * Whenever the encapsulated condition is {@code false}, the sync is * interrupted. This keeps happening until {@code unsubscribe()} is called * on the returned subscription. Unsubscribing the returned subscription may * be skipped safely only when the lifetimes of all the encapsulated * condition, {@code source} and {@code target} are the same. * @param target target of the conditional binding * @param source source of the conditional binding * @return a subscription that can be used to dispose the conditional * binding set up by this method, i.e. to stop observing the encapsulated * condition and, if the last observed value of the encapsulated condition * was {@code true}, stop the synchronization {@code target}'s content with * {@code source}'s content. */ public <T> Subscription listBind( List<? super T> target, ObservableList<? extends T> source) { return bind(() -> EasyBind.listBind(target, source)); } Subscription bind(Supplier<? extends Subscription> bindFn) { return new ConditionalSubscription(condition, bindFn); } } class ConditionalSubscription implements Subscription { private final Supplier<? extends Subscription> bindFn; private final ObservableValue<Boolean> condition; private final ChangeListener<Boolean> conditionListener = this::conditionChanged; private Subscription subscription = null; public ConditionalSubscription( ObservableValue<Boolean> condition, Supplier<? extends Subscription> bindFn) { this.condition = condition; this.bindFn = bindFn; condition.addListener(conditionListener); if(condition.getValue()) { subscription = bindFn.get(); } } private void conditionChanged(ObservableValue<? extends Boolean> cond, Boolean wasTrue, Boolean isTrue) { if(isTrue) { assert subscription == null; subscription = bindFn.get(); } else { assert subscription != null; subscription.unsubscribe(); subscription = null; } } @Override public void unsubscribe() { condition.removeListener(conditionListener); if(subscription != null) { subscription.unsubscribe(); subscription = null; } } }