javafx 8 链接效果

javafx 8 chaining effects

使用 .setInput() 在 JavaFX 中链接两个效果很容易

if (isDropShadowEnabled)
    innerShadow.setInput(dropShadow);
content.setEffect(innerShadow);

但是当其中任何一种效果可能未启用时,我如何链接多个效果?

假设第三个效果是绽放。所以我想要一个 Dropshadow、InnerShadow 和 Bloom,但在其他时候只是一个 DropShadow 和 Bloom 而没有 InnerShadow。我试图避免大量的“如果”,并且我一直在寻找类似于 effect.getChildren().add('lots of effects') 的东西。但是经过几个小时的谷歌搜索,什么也没有跳出来。

也许您可以使用这段代码中的一些想法。抱歉有点复杂,我现在没时间解释。

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class Effector extends Application{

    @Override
    public void start(Stage stage) throws Exception {
        ImageView imageView = new ImageView(
                new Image(
                        "http://icons.iconarchive.com/icons/designbolts/smurfs-movie/128/smurfette-icon.png"
                )
        );

        VBox effectControls = new VBox(5);
        effectControls.setPadding(new Insets(5));
        effectControls.getChildren().setAll(
                new EffectController(
                        "Drop Shadow",
                        new DropShadow()
                ),
                new EffectController(
                        "Inner Shadow",
                        new InnerShadow()
                ),
                new EffectController(
                        "Bloom",
                        new Bloom()
                )
        );

        EffectPipeline pipeline = new EffectPipeline(
                effectControls.getChildren().stream().map(
                        node -> ((EffectController) node).getChainableEffect()
                ).toArray(ChainableEffect[]::new)
        );

        imageView.effectProperty().bind(
                pipeline.chainedEffectProperty()
        );

        VBox layout = new VBox(
                5,
                effectControls,
                imageView
        );
        layout.setPadding(new Insets(5));
        layout.setAlignment(
                Pos.CENTER
        );

        stage.setScene(
                new Scene(
                        layout
                )
        );
        stage.setResizable(false);
        stage.show();
    }

    class EffectController extends CheckBox {
        private final ChainableEffect chainableEffect;

        public EffectController(
                String text,
                Effect effect
        ) {
            super(text);

            chainableEffect = new ChainableEffect(
                    effect
            );

            this.setSelected(!chainableEffect.isDisabled());
            this.selectedProperty().addListener(
                    (observable, oldValue, newValue) -> {
                        chainableEffect.disabledProperty().set(!newValue);
                    }
            );
        }

        public ChainableEffect getChainableEffect() {
            return chainableEffect;
        }
    }

    class EffectPipeline {
        private List<ChainableEffect> effects;

        private ReadOnlyObjectWrapper<Effect> chainedEffect = new ReadOnlyObjectWrapper<>();

        public EffectPipeline(ChainableEffect... effects) {
            this.effects = Arrays.asList(effects);

            for (ChainableEffect chainableEffect: effects) {
                chainableEffect.disabledProperty().addListener((observable, oldValue, newValue) -> {
                    refreshChainedEffect();
                });
            }

            refreshChainedEffect();
        }

        public void refreshChainedEffect() {
            ChainableEffect firstEffect = null, lastEffect = null;

            for (ChainableEffect nextEffect : effects) {
                nextEffect.setInput(null);
                if (nextEffect.isDisabled()) {
                    continue;
                }

                if (firstEffect == null) {
                    firstEffect = nextEffect;
                    lastEffect = firstEffect;
                    continue;
                }

                lastEffect.setInput(nextEffect);
                lastEffect = nextEffect;
            }

            chainedEffect.setValue(
                    firstEffect == null
                            ? null
                            : firstEffect.getEffect()
            );
        }

        public Effect getChainedEffect() {
            return chainedEffect.get();
        }

        public ReadOnlyObjectProperty<Effect> chainedEffectProperty() {
            return chainedEffect.getReadOnlyProperty();
        }
    }

    class ChainableEffect {
        private final Effect effect;
        private final Method inputMethod;
        private final BooleanProperty disabled = new SimpleBooleanProperty(
                false
        );

        public ChainableEffect(Effect effect) {
            if (effect == null) {
                throw new IllegalArgumentException("Effect for chaining must not be null");
            }

            this.effect = effect;

            try {
                inputMethod = effect.getClass().getMethod(
                        "setInput",
                        Effect.class
                );
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("Effect for chaining must implement the setInput method", e);
            } catch (SecurityException e) {
                throw new IllegalStateException("Creating chainable effects requires a reflection capable security environment", e);
            }
        }

        public ChainableEffect setInput(ChainableEffect chainableEffect) {
            try {
                inputMethod.invoke(
                        this.getEffect(),
                        chainableEffect != null
                                ? chainableEffect.getEffect()
                                : null
                );

                return this;
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Chainable effect does not support access rights for setInput", e);
            } catch (InvocationTargetException e) {
                throw new IllegalStateException("Unable to set the input for a chainable effect", e);
            }
        }

        public Effect getEffect() {
            return effect;
        }

        public boolean isDisabled() {
            return disabled.get();
        }

        public BooleanProperty disabledProperty() {
            return disabled;
        }

        public void setDisabled(boolean disabled) {
            this.disabled.set(disabled);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}