Add selectedItemPropertyFor(ToggleGroup)

This commit is contained in:
yushijinhun 2018-10-01 17:03:34 +08:00
parent 86765275c8
commit 83f7e61d37
No known key found for this signature in database
GPG Key ID: 5BC167F73EA558E4
9 changed files with 166 additions and 71 deletions

View File

@ -91,9 +91,9 @@ public final class Accounts {
}
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(accounts);
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts);
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>(Accounts.class, "selectedAccount") {
{
accounts.addListener(onInvalidating(this::invalidated));
}

View File

@ -47,7 +47,7 @@ import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.SelectionModelSelectedItemProperty;
import org.jackhuang.hmcl.util.javafx.SelectedItemProperties;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
@ -348,7 +348,7 @@ public final class FXUtils {
* @param comboBox the combo box being bound with {@code property}.
* @param property the property being bound with {@code combo box}.
* @see #unbindEnum(JFXComboBox)
* @deprecated Use {@link SelectionModelSelectedItemProperty#selectedItemPropertyFor(ComboBox)}
* @deprecated Use {@link SelectedItemProperties#selectedItemPropertyFor(ComboBox)}
*/
@SuppressWarnings("unchecked")
@Deprecated

View File

@ -27,8 +27,8 @@ import javafx.scene.control.Control;
import javafx.scene.control.Skin;
public abstract class ListPage<T extends Node> extends Control {
private final ListProperty<T> items = new SimpleListProperty<>(FXCollections.observableArrayList());
private final BooleanProperty loading = new SimpleBooleanProperty(false);
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
public abstract void add();

View File

@ -47,7 +47,7 @@ import java.util.Optional;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.SelectionModelSelectedItemProperty.selectedItemPropertyFor;
import static org.jackhuang.hmcl.util.javafx.SelectedItemProperties.selectedItemPropertyFor;
public final class SettingsPage extends SettingsView implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));

View File

@ -17,8 +17,6 @@
*/
package org.jackhuang.hmcl.util.javafx;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@ -37,20 +35,6 @@ public final class MappedObservableList {
private MappedObservableList() {
}
private static class ReferenceHolder implements InvalidationListener {
@SuppressWarnings("unused")
private Object ref;
ReferenceHolder(Object ref) {
this.ref = ref;
}
@Override
public void invalidated(Observable observable) {
// no-op
}
}
private static class MappedObservableListUpdater<T, U> implements ListChangeListener<T> {
private ObservableList<T> origin;
private ObservableList<U> target;

View File

@ -36,6 +36,11 @@ public class ReadWriteComposedProperty<T> extends SimpleObjectProperty<T> {
private ChangeListener<T> listener;
public ReadWriteComposedProperty(ObservableValue<T> readSource, Consumer<T> writeTarget) {
this(null, "", readSource, writeTarget);
}
public ReadWriteComposedProperty(Object bean, String name, ObservableValue<T> readSource, Consumer<T> writeTarget) {
super(bean, name);
this.readSource = readSource;
this.writeTarget = writeTarget;

View File

@ -0,0 +1,35 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* 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 javafx.beans.InvalidationListener;
import javafx.beans.Observable;
class ReferenceHolder implements InvalidationListener {
@SuppressWarnings("unused")
private Object ref;
public ReferenceHolder(Object ref) {
this.ref = ref;
}
@Override
public void invalidated(Observable observable) {
// no-op
}
}

View File

@ -0,0 +1,119 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* 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 org.jackhuang.hmcl.util.Pair.pair;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
/**
* @author yushijinhun
*/
public final class SelectedItemProperties {
private static final String PROP_PREFIX = SelectedItemProperties.class.getName();
@SuppressWarnings("unchecked")
public static <T> ObjectProperty<T> selectedItemPropertyFor(ComboBox<T> comboBox) {
return (ObjectProperty<T>) comboBox.getProperties().computeIfAbsent(
PROP_PREFIX + ".comboxBox.selectedItem",
any -> createPropertyForSelectionModel(comboBox, comboBox.selectionModelProperty()));
}
private static <T> ObjectProperty<T> createPropertyForSelectionModel(Object bean, Property<? extends SelectionModel<T>> modelProperty) {
return new ReadWriteComposedProperty<>(bean, "extra.selectedItem",
MultiStepBinding.of(modelProperty)
.flatMap(SelectionModel::selectedItemProperty),
modelProperty.getValue()::select);
}
@SuppressWarnings("unchecked")
public static ObjectProperty<Toggle> selectedTogglePropertyFor(ToggleGroup toggleGroup) {
return (ObjectProperty<Toggle>) toggleGroup.getProperties().computeIfAbsent(
PROP_PREFIX + ".toggleGroup.selectedToggle",
any -> createPropertyForToggleGroup(toggleGroup));
}
private static ObjectProperty<Toggle> createPropertyForToggleGroup(ToggleGroup toggleGroup) {
return new ReadWriteComposedProperty<>(toggleGroup, "extra.selectedToggle",
toggleGroup.selectedToggleProperty(),
toggleGroup::selectToggle);
}
@SuppressWarnings("unchecked")
public static <T> ObjectProperty<T> selectedItemPropertyFor(ToggleGroup toggleGroup, Class<T> userdataType) {
return (ObjectProperty<T>) toggleGroup.getProperties().computeIfAbsent(
pair(PROP_PREFIX + ".toggleGroup.selectedItem", userdataType),
any -> createMappedPropertyForToggleGroup(
toggleGroup,
toggle -> toggle == null ? null : userdataType.cast(toggle.getUserData())));
}
private static <T> ObjectProperty<T> createMappedPropertyForToggleGroup(ToggleGroup toggleGroup, Function<Toggle, T> mapper) {
ObjectProperty<Toggle> selectedToggle = selectedTogglePropertyFor(toggleGroup);
AtomicReference<Optional<T>> pendingItemHolder = new AtomicReference<>();
Consumer<T> itemSelector = newItem -> {
Optional<Toggle> toggleToSelect = toggleGroup.getToggles().stream()
.filter(toggle -> Objects.equals(newItem, mapper.apply(toggle)))
.findFirst();
if (toggleToSelect.isPresent()) {
pendingItemHolder.set(null);
selectedToggle.set(toggleToSelect.get());
} else {
// We are asked to select an nonexistent item.
// However, this item may become available in the future.
// So here we store it, and once the associated toggle becomes available, we will update the selection.
pendingItemHolder.set(Optional.ofNullable(newItem));
selectedToggle.set(null);
}
};
ReadWriteComposedProperty<T> property = new ReadWriteComposedProperty<>(toggleGroup, "extra.selectedItem",
MultiStepBinding.of(selectedTogglePropertyFor(toggleGroup))
.map(mapper),
itemSelector);
InvalidationListener onTogglesChanged = any -> {
Optional<T> pendingItem = pendingItemHolder.get();
if (pendingItem != null) {
itemSelector.accept(pendingItem.orElse(null));
}
};
toggleGroup.getToggles().addListener(new WeakInvalidationListener(onTogglesChanged));
property.addListener(new ReferenceHolder(onTogglesChanged));
return property;
}
private SelectedItemProperties() {
}
}

View File

@ -1,48 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* 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 javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SelectionModel;
/**
* @author yushijinhun
*/
public final class SelectionModelSelectedItemProperty {
private static final String NODE_PROPERTY = SelectionModelSelectedItemProperty.class.getName() + ".instance";
@SuppressWarnings("unchecked")
public static <T> ObjectProperty<T> selectedItemPropertyFor(ComboBox<T> comboBox) {
return (ObjectProperty<T>) comboBox.getProperties().computeIfAbsent(
NODE_PROPERTY,
any -> createSelectedItemProperty(comboBox.selectionModelProperty()));
}
private static <T> ObjectProperty<T> createSelectedItemProperty(Property<? extends SelectionModel<T>> modelProperty) {
return new ReadWriteComposedProperty<>(
MultiStepBinding.of(modelProperty)
.flatMap(SelectionModel::selectedItemProperty),
newValue -> modelProperty.getValue().select(newValue));
}
private SelectionModelSelectedItemProperty() {
}
}