org.apache.pivot.wtk.skin.CardPaneSkin Maven / Gradle / Ivy
/*
* 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();
}
}
}
}