All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.pivot.wtk.skin.CardPaneSkin Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.pivot.wtk.skin;

import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.Vote;
import org.apache.pivot.wtk.CardPane;
import org.apache.pivot.wtk.CardPaneListener;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Container;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Orientation;
import org.apache.pivot.wtk.effects.FadeDecorator;
import org.apache.pivot.wtk.effects.ScaleDecorator;
import org.apache.pivot.wtk.effects.Transition;
import org.apache.pivot.wtk.effects.TransitionListener;
import org.apache.pivot.wtk.effects.easing.Easing;
import org.apache.pivot.wtk.effects.easing.Quartic;

/**
 * Card pane skin.
 */
public class CardPaneSkin extends ContainerSkin implements CardPaneListener {
    /**
     * Defines the supported selection change effects.
     */
    public enum SelectionChangeEffect {
        CROSSFADE,
        HORIZONTAL_SLIDE,
        VERTICAL_SLIDE,
        HORIZONTAL_FLIP,
        VERTICAL_FLIP,
        ZOOM
    }

    /**
     * Abstract base class for selection change transitions.
     */
    public abstract class SelectionChangeTransition extends Transition {
        public final int from;
        public final int to;
        public final Component fromCard;
        public final Component toCard;
        public final int direction;

        public SelectionChangeTransition(int from, int to) {
            super(selectionChangeDuration, selectionChangeRate, false);

            this.from = from;
            this.to = to;

            CardPane cardPane = (CardPane)getComponent();
            fromCard = (from == -1) ? null : cardPane.get(from);
            toCard = (to == -1) ? null : cardPane.get(to);

            int length = cardPane.getLength();
            if (circular
                && length >= 3) {
                if (from == length - 1
                    && to == 0) {
                    direction = 1;
                } else if (from == 0
                    && to == length - 1) {
                    direction = -1;
                } else {
                    direction = Integer.signum(to - from);
                }
            } else {
                direction = Integer.signum(to - from);
            }
        }
    }

    /**
     * Class that performs cross-fade selection change transitions.
     */
    public class CrossfadeTransition extends SelectionChangeTransition {
        private FadeDecorator fadeOutDecorator = new FadeDecorator();
        private FadeDecorator fadeInDecorator = new FadeDecorator();

        public CrossfadeTransition(int from, int to) {
            super(from, to);
        }

        @Override
        public void start(TransitionListener transitionListener) {
            if (fromCard != null) {
                fromCard.getDecorators().add(fadeOutDecorator);
            }

            if (toCard != null) {
                toCard.getDecorators().add(fadeInDecorator);
                toCard.setVisible(true);
            }

            super.start(transitionListener);
        }

        @Override
        public void stop() {
            super.stop();

            if (fromCard != null) {
                fromCard.getDecorators().remove(fadeOutDecorator);
                fromCard.setVisible(false);
            }

            if (toCard != null) {
                toCard.getDecorators().remove(fadeInDecorator);
            }
        }

        @Override
        protected void update() {
            float percentComplete = getPercentComplete();

            fadeOutDecorator.setOpacity(1.0f - percentComplete);
            fadeInDecorator.setOpacity(percentComplete);

            if (sizeToSelection) {
                invalidateComponent();
            } else {
                repaintComponent();
            }
        }
    }

    /**
     * Class that performs slide selection change transitions.
     */
    public class SlideTransition extends SelectionChangeTransition {
        private Easing slideEasing = new Quartic();

        public SlideTransition(int from, int to) {
            super(from, to);
        }

        @Override
        public void start(TransitionListener transitionListener) {
            toCard.setVisible(true);

            super.start(transitionListener);
        }

        @Override
        public void stop() {
            fromCard.setVisible(false);

            super.stop();
        }

        @Override
        protected void update() {
            int width = getWidth();
            int height = getHeight();

            float percentComplete = slideEasing.easeOut(getElapsedTime(), 0, 1, getDuration());

            int dx = (int)(width * percentComplete) * -direction;
            int dy = (int)(height * percentComplete) * -direction;

            if (selectionChangeEffect == SelectionChangeEffect.HORIZONTAL_SLIDE) {
                fromCard.setLocation(padding.left + dx, padding.top);
                toCard.setLocation(padding.left - (width * -direction) + dx, padding.top);
            } else {
                fromCard.setLocation(padding.left, padding.top + dy);
                toCard.setLocation(padding.left, padding.top - (height * -direction) + dy);
            }
        }
    }

    /**
     * Class that performs flip selection change transitions.
     */
    public class FlipTransition extends SelectionChangeTransition {
        private Orientation orientation;
        private double theta;
        private ScaleDecorator scaleDecorator = new ScaleDecorator();

        public FlipTransition(Orientation orientation, int from, int to) {
            super(from, to);
            this.orientation = orientation;
        }

        @Override
        public void start(TransitionListener transitionListener) {
            theta = 0;
            getComponent().getDecorators().add(scaleDecorator);

            super.start(transitionListener);
        }

        @Override
        public void stop() {
            getComponent().getDecorators().remove(scaleDecorator);

            super.stop();
        }

        @Override
        protected void update() {
            float percentComplete = getPercentComplete();

            if (percentComplete < 1f) {
                theta = Math.PI * percentComplete;

                float scale = (float)Math.abs(Math.cos(theta));

                if (orientation == Orientation.HORIZONTAL) {
                    scaleDecorator.setScale(scale, 1.0f);
                } else {
                    scaleDecorator.setScale(1.0f, scale);
                }

                fromCard.setVisible(theta < Math.PI / 2);
                toCard.setVisible(theta >= Math.PI / 2);

                repaintComponent();
            }
        }
    }

    /**
     * Class that performs zoom change transitions.
     */
    public class ZoomTransition extends CrossfadeTransition {
        private ScaleDecorator fromScaleDecorator = new ScaleDecorator();
        private ScaleDecorator toScaleDecorator = new ScaleDecorator();

        public ZoomTransition(int from, int to) {
            super(from, to);
        }

        @Override
        public void start(TransitionListener transitionListener) {
            if (fromCard != null) {
                fromCard.getDecorators().add(fromScaleDecorator);
            }

            if (toCard != null) {
                toCard.getDecorators().add(toScaleDecorator);
                toCard.setVisible(true);
            }

            super.start(transitionListener);
        }

        @Override
        public void stop() {
            super.stop();

            if (fromCard != null) {
                fromCard.getDecorators().remove(fromScaleDecorator);
                fromCard.setVisible(false);
            }

            if (toCard != null) {
                toCard.getDecorators().remove(toScaleDecorator);
            }
        }

        @Override
        protected void update() {
            float percentComplete = getPercentComplete();

            if (direction == 1) {
                fromScaleDecorator.setScale(1.0f + percentComplete);
                toScaleDecorator.setScale(percentComplete);
            } else {
                fromScaleDecorator.setScale(1.0f - percentComplete);
                toScaleDecorator.setScale(2.0f - percentComplete);
            }

            super.update();
        }
    }

    private Insets padding = Insets.NONE;
    private boolean sizeToSelection = false;
    private SelectionChangeEffect selectionChangeEffect = null;
    private int selectionChangeDuration = DEFAULT_SELECTION_CHANGE_DURATION;
    private int selectionChangeRate = DEFAULT_SELECTION_CHANGE_RATE;
    private boolean circular = false;

    private SelectionChangeTransition selectionChangeTransition = null;

    private static final int DEFAULT_SELECTION_CHANGE_DURATION = 250;
    private static final int DEFAULT_SELECTION_CHANGE_RATE = 30;

    @Override
    public void install(Component component) {
        super.install(component);

        CardPane cardPane = (CardPane)component;
        cardPane.getCardPaneListeners().add(this);
    }

    @Override
    public int getPreferredWidth(int height) {
        int preferredWidth = 0;

        CardPane cardPane = (CardPane)getComponent();

        if (sizeToSelection) {
            if (selectionChangeTransition == null) {
                Component selectedCard = cardPane.getSelectedCard();

                if (selectedCard != null) {
                    preferredWidth = selectedCard.getPreferredWidth(height);
                }
            } else {
                float percentComplete = selectionChangeTransition.getPercentComplete();

                int previousWidth;
                if (selectionChangeTransition.fromCard == null) {
                    previousWidth = 0;
                } else {
                    previousWidth = selectionChangeTransition.fromCard.getPreferredWidth(height);
                }

                int width;
                if (selectionChangeTransition.toCard == null) {
                    width = 0;
                } else {
                    width = selectionChangeTransition.toCard.getPreferredWidth(height);
                }

                preferredWidth = previousWidth + (int)((width - previousWidth) * percentComplete);
            }
        } else {
            for (Component card : cardPane) {
                preferredWidth = Math.max(preferredWidth, card.getPreferredWidth(height));
            }

            preferredWidth += (padding.left + padding.right);
        }

        return preferredWidth;
    }

    @Override
    public int getPreferredHeight(int width) {
        int preferredHeight = 0;

        CardPane cardPane = (CardPane)getComponent();

        if (sizeToSelection) {
            if (selectionChangeTransition == null) {
                Component selectedCard = cardPane.getSelectedCard();

                if (selectedCard != null) {
                    preferredHeight = selectedCard.getPreferredHeight(width);
                }
            } else {
                float percentComplete = selectionChangeTransition.getPercentComplete();

                int previousHeight;
                if (selectionChangeTransition.fromCard == null) {
                    previousHeight = 0;
                } else {
                    previousHeight = selectionChangeTransition.fromCard.getPreferredHeight(width);
                }

                int height;
                if (selectionChangeTransition.toCard == null) {
                    height = 0;
                } else {
                    height = selectionChangeTransition.toCard.getPreferredHeight(width);
                }

                preferredHeight = previousHeight + (int)((height - previousHeight) * percentComplete);
            }
        } else {
            for (Component card : cardPane) {
                preferredHeight = Math.max(preferredHeight, card.getPreferredHeight(width));
            }

            preferredHeight += (padding.top + padding.bottom);
        }

        return preferredHeight;
    }

    @Override
    public Dimensions getPreferredSize() {
        int preferredWidth = 0;
        int preferredHeight = 0;

        CardPane cardPane = (CardPane)getComponent();

        if (sizeToSelection) {
            if (selectionChangeTransition == null) {
                Component selectedCard = cardPane.getSelectedCard();

                if (selectedCard != null) {
                    Dimensions cardSize = selectedCard.getPreferredSize();
                    preferredWidth = cardSize.width;
                    preferredHeight = cardSize.height;
                }
            } else {
                float percentComplete = selectionChangeTransition.getPercentComplete();

                int previousWidth;
                int previousHeight;
                if (selectionChangeTransition.fromCard == null) {
                    previousWidth = 0;
                    previousHeight = 0;
                } else {
                    Dimensions fromSize = selectionChangeTransition.fromCard.getPreferredSize();
                    previousWidth = fromSize.width;
                    previousHeight = fromSize.height;
                }

                int width;
                int height;
                if (selectionChangeTransition.toCard == null) {
                    width = 0;
                    height = 0;
                } else {
                    Dimensions toSize = selectionChangeTransition.toCard.getPreferredSize();
                    width = toSize.width;
                    height = toSize.height;
                }

                preferredWidth = previousWidth + (int)((width - previousWidth) * percentComplete);
                preferredHeight = previousHeight + (int)((height - previousHeight) * percentComplete);
            }
        } else {
            for (Component card : cardPane) {
                Dimensions cardSize = card.getPreferredSize();

                preferredWidth = Math.max(cardSize.width, preferredWidth);
                preferredHeight = Math.max(cardSize.height, preferredHeight);
            }
        }

        preferredWidth += (padding.left + padding.right);
        preferredHeight += (padding.top + padding.bottom);

        return new Dimensions(preferredWidth, preferredHeight);
    }

    @Override
    public int getBaseline(int width, int height) {
        int baseline = -1;

        if (sizeToSelection) {
            CardPane cardPane = (CardPane)getComponent();
            Component selectedCard = cardPane.getSelectedCard();

            if (selectedCard != null) {
                int cardWidth = Math.max(width - (padding.left + padding.right), 0);
                int cardHeight = Math.max(height - (padding.top + padding.bottom), 0);

                baseline = selectedCard.getBaseline(cardWidth, cardHeight);

                if (baseline != -1) {
                    baseline += padding.top;
                }
            }
        }

        return baseline;
    }

    @Override
    public void layout() {
        // Set the size of all components to match the size of the card pane,
        // minus padding
        CardPane cardPane = (CardPane)getComponent();
        int width = Math.max(getWidth() - (padding.left + padding.right), 0);
        int height = Math.max(getHeight() - (padding.top + padding.bottom), 0);

        for (Component card : cardPane) {
            card.setLocation(padding.left, padding.top);
            card.setSize(width, height);
        }
    }

    /**
     * Returns the amount of space between the edge of the CardPane and its content.
     */
    public Insets getPadding() {
        return padding;
    }

    /**
     * Sets the amount of space to leave between the edge of the CardPane and its content.
     */
    public void setPadding(Insets padding) {
        if (padding == null) {
            throw new IllegalArgumentException("padding is null.");
        }

        this.padding = padding;
        invalidateComponent();
    }

    /**
     * Sets the amount of space to leave between the edge of the CardPane and its content.
     *
     * @param padding A dictionary with keys in the set {left, top, bottom, right}.
     */
    public final void setPadding(Dictionary padding) {
        if (padding == null) {
            throw new IllegalArgumentException("padding is null.");
        }

        setPadding(new Insets(padding));
    }

    /**
     * Sets the amount of space to leave between the edge of the CardPane and its content,
     * uniformly on all four edges.
     */
    public final void setPadding(int padding) {
        setPadding(new Insets(padding));
    }

    /**
     * Sets the amount of space to leave between the edge of the CardPane and its content,
     * uniformly on all four edges.
     */
    public void setPadding(Number padding) {
        if (padding == null) {
            throw new IllegalArgumentException("padding is null.");
        }

        setPadding(padding.intValue());
    }

    /**
     * Sets the amount of space to leave between the edge of the CardPane and its content.
     *
     * @param padding A string containing an integer or a JSON dictionary with keys
     * left, top, bottom, and/or right.
     */
    public final void setPadding(String padding) {
        if (padding == null) {
            throw new IllegalArgumentException("padding is null.");
        }

        setPadding(Insets.decode(padding));
    }

    public boolean getSizeToSelection() {
        return sizeToSelection;
    }

    public void setSizeToSelection(boolean sizeToSelection) {
        if (selectionChangeTransition != null) {
            selectionChangeTransition.end();
        }

        this.sizeToSelection = sizeToSelection;
        invalidateComponent();
    }

    public SelectionChangeEffect getSelectionChangeEffect() {
        return selectionChangeEffect;
    }

    public void setSelectionChangeEffect(SelectionChangeEffect selectionChangeEffect) {
        if (selectionChangeTransition != null) {
            selectionChangeTransition.end();
        }

        this.selectionChangeEffect = selectionChangeEffect;
    }

    public int getSelectionChangeDuration() {
        return selectionChangeDuration;
    }

    public void setSelectionChangeDuration(int selectionChangeDuration) {
        this.selectionChangeDuration = selectionChangeDuration;
    }

    public int getSelectionChangeRate() {
        return selectionChangeRate;
    }

    public void setSelectionChangeRate(int selectionChangeRate) {
        this.selectionChangeRate = selectionChangeRate;
    }
    /**
     * Sets the circular style, which controls the direction of certain
     * transitions (transitions for which a direction makes sense) when looping
     * from the first index of a card pane to the last, or vice versa. When
     * this style is false (the default), directional transitions will
     * always appear to move forward when transitioning from a lower card index
     * to a higher card index, and vice versa. When this style is true,
     * directional transitions will appear to move forward when transitioning
     * from the last card to the first, and backward when they transition from
     * the first card to the last.
     * 

* Note: to avoid ambiguity, the circular style will be ignored if the card * pane has fewer than three cards. * * @return * true if directional transitions will be circular; * false otherwise */ public boolean isCircular() { return circular; } /** * Sets the circular style, which controls the direction of certain * transitions (transitions for which a direction makes sense) when looping * from the first index of a card pane to the last, or vice versa. When * this style is false (the default), directional transitions will * always appear to move forward when transitioning from a lower card index * to a higher card index, and vice versa. When this style is true, * directional transitions will appear to move forward when transitioning * from the last card to the first, and backward when they transition from * the first card to the last. *

* Note: to avoid ambiguity, the circular style will be ignored if the card * pane has fewer than three cards. * * @param circular * true if directional transitions should be circular; * false otherwise */ public void setCircular(boolean circular) { this.circular = circular; } @Override public void componentInserted(Container container, int index) { if (selectionChangeTransition != null) { selectionChangeTransition.end(); } super.componentInserted(container, index); CardPane cardPane = (CardPane)container; Component card = cardPane.get(index); card.setVisible(false); if (cardPane.getLength() == 1) { cardPane.setSelectedIndex(0); } invalidateComponent(); } @Override public void componentsRemoved(Container container, int index, Sequence removed) { if (selectionChangeTransition != null) { selectionChangeTransition.end(); } super.componentsRemoved(container, index, removed); for (int i = 0, n = removed.getLength(); i < n; i++){ Component card = removed.get(i); card.setVisible(true); } invalidateComponent(); } @Override public Vote previewSelectedIndexChange(CardPane cardPane, int selectedIndex) { Vote vote; if (cardPane.isShowing() && selectionChangeEffect != null && selectionChangeTransition == null) { int previousSelectedIndex = cardPane.getSelectedIndex(); switch (selectionChangeEffect) { case CROSSFADE: { selectionChangeTransition = new CrossfadeTransition(previousSelectedIndex, selectedIndex); break; } case HORIZONTAL_SLIDE: case VERTICAL_SLIDE: { if (previousSelectedIndex != -1 && selectedIndex != -1) { selectionChangeTransition = new SlideTransition(previousSelectedIndex, selectedIndex); } break; } case HORIZONTAL_FLIP: { if (previousSelectedIndex != -1 && selectedIndex != -1) { selectionChangeTransition = new FlipTransition(Orientation.HORIZONTAL, previousSelectedIndex, selectedIndex); } break; } case VERTICAL_FLIP: { if (previousSelectedIndex != -1 && selectedIndex != -1) { selectionChangeTransition = new FlipTransition(Orientation.VERTICAL, previousSelectedIndex, selectedIndex); } break; } case ZOOM: { if (previousSelectedIndex != -1 && selectedIndex != -1) { selectionChangeTransition = new ZoomTransition(previousSelectedIndex, selectedIndex); } break; } default: { break; } } if (selectionChangeTransition != null) { selectionChangeTransition.start(new TransitionListener() { @Override public void transitionCompleted(Transition transition) { CardPane cardPaneLocal = (CardPane)getComponent(); SelectionChangeTransition selectionChangeTransitionLocal = (SelectionChangeTransition)transition; int selectedIndexLocal = cardPaneLocal.indexOf(selectionChangeTransitionLocal.toCard); cardPaneLocal.setSelectedIndex(selectedIndexLocal); CardPaneSkin.this.selectionChangeTransition = null; } }); } } if (selectionChangeTransition == null || !selectionChangeTransition.isRunning()) { vote = Vote.APPROVE; } else { vote = Vote.DEFER; } return vote; } @Override public void selectedIndexChangeVetoed(CardPane cardPane, Vote reason) { if (reason == Vote.DENY && selectionChangeTransition != null) { // NOTE We stop, rather than end, the transition so the completion // event isn't fired; if the event fires, the listener will set // the selection state selectionChangeTransition.stop(); selectionChangeTransition = null; if (sizeToSelection) { invalidateComponent(); } } } @Override public void selectedIndexChanged(CardPane cardPane, int previousSelectedIndex) { int selectedIndex = cardPane.getSelectedIndex(); if (selectedIndex != previousSelectedIndex) { // This was not an indirect selection change if (selectedIndex != -1) { Component selectedCard = cardPane.get(selectedIndex); selectedCard.setVisible(true); } if (previousSelectedIndex != -1) { Component previousSelectedCard = cardPane.get(previousSelectedIndex); previousSelectedCard.setVisible(false); } if (selectedIndex == -1 || previousSelectedIndex == -1 || sizeToSelection) { invalidateComponent(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy