From a01e15d841d584f286ee04a60e32c90b8161a24b Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Mon, 1 Oct 2018 15:35:39 +0800 Subject: [PATCH] Add MultiStepBinding&ReadWriteComposedProperty --- .../hmcl/util/javafx/MultiStepBinding.java | 122 ++++++++++++++++++ .../javafx/ReadWriteComposedProperty.java | 51 ++++++++ .../SelectionModelSelectedItemProperty.java | 31 +++-- 3 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MultiStepBinding.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MultiStepBinding.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MultiStepBinding.java new file mode 100644 index 000000000..b4e427fd7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/MultiStepBinding.java @@ -0,0 +1,122 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2018 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util.javafx; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Function; +import java.util.function.Supplier; + +import javafx.beans.binding.ObjectBinding; +import javafx.beans.value.ObservableValue; + +/** + * @author yushijinhun + */ +public abstract class MultiStepBinding extends ObjectBinding { + + public static MultiStepBinding of(ObservableValue property) { + return new SimpleBinding<>(property); + } + + protected final ObservableValue predecessor; + + public MultiStepBinding(ObservableValue predecessor) { + this.predecessor = requireNonNull(predecessor); + bind(predecessor); + } + + public MultiStepBinding map(Function mapper) { + return new MappedBinding<>(this, mapper); + } + + public MultiStepBinding flatMap(Function> mapper) { + return flatMap(mapper, null); + } + + public MultiStepBinding flatMap(Function> mapper, Supplier nullAlternative) { + return new FlatMappedBinding<>(map(mapper), nullAlternative); + } + + private static class SimpleBinding extends MultiStepBinding { + + public SimpleBinding(ObservableValue predecessor) { + super(predecessor); + } + + @Override + protected T computeValue() { + return predecessor.getValue(); + } + + @Override + public MultiStepBinding map(Function mapper) { + return new MappedBinding<>(predecessor, mapper); + } + } + + private static class MappedBinding extends MultiStepBinding { + + private final Function mapper; + + public MappedBinding(ObservableValue predecessor, Function mapper) { + super(predecessor); + this.mapper = mapper; + } + + @Override + protected U computeValue() { + return mapper.apply(predecessor.getValue()); + } + } + + private static class FlatMappedBinding, U> extends MultiStepBinding { + + private final Supplier nullAlternative; + private T lastObservable = null; + + public FlatMappedBinding(ObservableValue predecessor, Supplier nullAlternative) { + super(predecessor); + this.nullAlternative = nullAlternative; + } + + @Override + protected U computeValue() { + T currentObservable = predecessor.getValue(); + if (currentObservable != lastObservable) { + if (lastObservable != null) { + unbind(lastObservable); + } + if (currentObservable != null) { + bind(currentObservable); + } + lastObservable = currentObservable; + } + + if (currentObservable == null) { + if (nullAlternative == null) { + throw new NullPointerException(); + } else { + return nullAlternative.get(); + } + } else { + return currentObservable.getValue(); + } + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java new file mode 100644 index 000000000..b64e198ad --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/ReadWriteComposedProperty.java @@ -0,0 +1,51 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2018 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.util.javafx; + +import java.util.function.Consumer; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WeakChangeListener; + +/** + * @author yushijinhun + */ +public class ReadWriteComposedProperty extends SimpleObjectProperty { + + @SuppressWarnings("unused") + private final ObservableValue readSource; + private final Consumer writeTarget; + + private ChangeListener listener; + + public ReadWriteComposedProperty(ObservableValue readSource, Consumer writeTarget) { + this.readSource = readSource; + this.writeTarget = writeTarget; + + this.listener = (observable, oldValue, newValue) -> set(newValue); + readSource.addListener(new WeakChangeListener<>(listener)); + set(readSource.getValue()); + } + + @Override + protected void invalidated() { + writeTarget.accept(get()); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java index cea558dd0..4f324dc9b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/javafx/SelectionModelSelectedItemProperty.java @@ -17,26 +17,33 @@ */ package org.jackhuang.hmcl.util.javafx; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.control.ComboBox; import javafx.scene.control.SelectionModel; -public class SelectionModelSelectedItemProperty extends SimpleObjectProperty { +/** + * @author yushijinhun + */ +public final class SelectionModelSelectedItemProperty extends SimpleObjectProperty { - public static SelectionModelSelectedItemProperty selectedItemPropertyFor(ComboBox comboBox) { - return new SelectionModelSelectedItemProperty<>(comboBox.getSelectionModel()); + private static final String NODE_PROPERTY = SelectionModelSelectedItemProperty.class.getName() + ".instance"; + + @SuppressWarnings("unchecked") + public static ObjectProperty selectedItemPropertyFor(ComboBox comboBox) { + return (ObjectProperty) comboBox.getProperties().computeIfAbsent( + NODE_PROPERTY, + any -> createSelectedItemProperty(comboBox.selectionModelProperty())); } - private SelectionModel model; - - public SelectionModelSelectedItemProperty(SelectionModel model) { - this.model = model; - model.selectedItemProperty().addListener((observable, oldValue, newValue) -> set(newValue)); - set(model.getSelectedItem()); + private static ObjectProperty createSelectedItemProperty(Property> modelProperty) { + return new ReadWriteComposedProperty<>( + MultiStepBinding.of(modelProperty) + .flatMap(SelectionModel::selectedItemProperty), + newValue -> modelProperty.getValue().select(newValue)); } - @Override - protected void invalidated() { - model.select(get()); + private SelectionModelSelectedItemProperty() { } }