package org.cruxframework.crux.smartfaces.client.slider;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.cruxframework.crux.core.client.css.transition.Transition;
import org.cruxframework.crux.core.client.css.transition.Transition.Callback;
import org.cruxframework.crux.core.client.event.HasSelectHandlers;
import org.cruxframework.crux.core.client.event.SelectEvent;
import org.cruxframework.crux.core.client.event.SelectHandler;
import org.cruxframework.crux.core.client.event.swap.HasSwapHandlers;
import org.cruxframework.crux.core.client.event.swap.SwapEvent;
import org.cruxframework.crux.core.client.event.swap.SwapHandler;
import org.cruxframework.crux.core.client.screen.DeviceAdaptive.Input;
import org.cruxframework.crux.core.client.screen.Screen;
import org.cruxframework.crux.core.client.screen.views.OrientationChangeEvent;
import org.cruxframework.crux.core.client.screen.views.OrientationChangeHandler;
import org.cruxframework.crux.smartfaces.client.backbone.common.FacesBackboneResourcesCommon;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.PartialSupport;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.IndexedPanel;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A panel that swaps its contents using slide animations.
* @author Thiago da Rosa de Bustamante
*/
@PartialSupport
public class Slider extends Composite implements HasSwapHandlers, HasSlideStartHandlers,
HasSlideEndHandlers, HasSelectHandlers,
HasWidgets.ForIsWidget, IndexedPanel.ForIsWidget
{
public static final String DEFAULT_STYLE_NAME = "faces-Slider";
private static Boolean supported = null;
private static final String TOUCH_SLIDER_ITEM_STYLE_NAME = "item";
protected boolean circularShowing = false;
protected FlowPanel contentPanel;
protected int currentWidget = -1;
protected int slideTransitionDuration = 500;
protected boolean sliding = false;
protected FocusPanel touchPanel;
/**
* Constructor
*/
public Slider()
{
FacesBackboneResourcesCommon.INSTANCE.css().ensureInjected();
touchPanel = new FocusPanel();
contentPanel = new FlowPanel();
touchPanel.add(contentPanel);
initWidget(touchPanel);
setStyleName(DEFAULT_STYLE_NAME);
contentPanel.setStyleName(FacesBackboneResourcesCommon.INSTANCE.css().facesSliderContentPanel());
SliderEventHandlers eventHandlers = GWT.create(SliderEventHandlers.class);
eventHandlers.setSlider(this);
eventHandlers.handleSliderEvents();
}
@Override
public void add(IsWidget w)
{
add(w.asWidget());
}
@Override
public void add(Widget widget)
{
SimplePanel itemWrapper = new SimplePanel();
itemWrapper.add(widget);
wrapItem(itemWrapper);
}
@Override
public HandlerRegistration addSelectHandler(SelectHandler handler)
{
return addHandler(handler, SelectEvent.getType());
}
@Override
public HandlerRegistration addSlideEndHandler(SlideEndHandler handler)
{
return addHandler(handler, SlideEndEvent.getType());
}
@Override
public HandlerRegistration addSlideStartHandler(SlideStartHandler handler)
{
return addHandler(handler, SlideStartEvent.getType());
}
@Override
public HandlerRegistration addSwapHandler(SwapHandler handler)
{
return addHandler(handler, SwapEvent.getType());
}
@Override
public void clear()
{
contentPanel.clear();
}
/**
* Retrieve the current widget being shown on this slider.
* @return current widget
*/
public int getCurrentWidget()
{
return currentWidget;
}
/**
* Gets the duration of the slide animations in milliseconds.
* @return animations duration
*/
public int getSlideTransitionDuration()
{
return slideTransitionDuration;
}
@Override
public Widget getWidget(int index)
{
return contentPanel.getWidget(index);
}
@Override
public int getWidgetCount()
{
return contentPanel.getWidgetCount();
}
@Override
public int getWidgetIndex(IsWidget child)
{
return contentPanel.getWidgetIndex(child.asWidget().getParent());
}
@Override
public int getWidgetIndex(Widget child)
{
return contentPanel.getWidgetIndex(child.getParent());
}
/**
* Retrieve the circularShowing property value. If this property is true, the slider will start
* again on first item when the end of widgets collection is reached.
* @return true if enabled.
*/
public boolean isCircularShowing()
{
return circularShowing;
}
/**
* Check if the panel is slinding any widget
* @return true if sliding
*/
public boolean isSliding()
{
return sliding;
}
@Override
public Iterator<Widget> iterator()
{
return new Iterator<Widget>()
{
private int index = -1;
@Override
public boolean hasNext()
{
return index < (getWidgetCount() - 1);
}
@Override
public Widget next()
{
if (index >= getWidgetCount())
{
throw new NoSuchElementException();
}
return getWidget(++index);
}
@Override
public void remove()
{
if ((index < 0) || (index >= getWidgetCount()))
{
throw new IllegalStateException();
}
Slider.this.remove(index--);
}
};
}
/**
* Show the next widget, sliding horizontally to it.
*/
public void next()
{
if (currentWidget < 0)
{
showFirstWidget();
}
else if (hasNextWidget())
{
slide(-contentPanel.getElement().getOffsetWidth(), true);
}
}
/**
* Show the previous widget, sliding back horizontally to it.
*/
public void previous()
{
if (currentWidget < 0)
{
showFirstWidget();
}
else if (hasPreviousWidget())
{
slide(contentPanel.getElement().getOffsetWidth(), true);
}
}
@Override
public boolean remove(int index)
{
return contentPanel.remove(index);
}
@Override
public boolean remove(IsWidget w)
{
return remove(w.asWidget());
}
@Override
public boolean remove(Widget w)
{
int index = getWidgetIndex(w);
if (index < 0)
{
return false;
}
return remove(index);
}
/**
* set the circularShowing property value. If this property is true, the slider will start
* again on first item when the end of widgets collection is reached.
* @param circularShowing true to enable.
*/
public void setCircularShowing(boolean circularShowing)
{
this.circularShowing = circularShowing;
}
/**
* Sets the duration of the slide animations in milliseconds.
* @param slideTransitionDuration
*/
public void setSlideTransitionDuration(int transitionDuration)
{
this.slideTransitionDuration = transitionDuration;
}
/**
* Show the first widget into this slider
*/
public void showFirstWidget()
{
if (getWidgetCount() > 0)
{
showWidget(0);
}
}
/**
* Set the current widget to the widget at the given index on widgets collection.
* @param index widget position
*/
public void showWidget(int index)
{
setCurrentWidget(index);
}
/**
* Adopt the given panel as a new item of this slider.
* @param itemPanel panel to adopt.
*/
public void wrapItem(SimplePanel itemPanel)
{
itemPanel.setStyleName(TOUCH_SLIDER_ITEM_STYLE_NAME);
itemPanel.addStyleName(FacesBackboneResourcesCommon.INSTANCE.css().facesSliderItem());
itemPanel.setVisible(false);
contentPanel.add(itemPanel);
}
Widget getCurrentPanel()
{
return contentPanel.getWidget(currentWidget);
}
Widget getNextPanel()
{
int widgetCount = getWidgetCount();
if (widgetCount > 0)
{
int index = currentWidget+1;
if (index >= widgetCount)
{
if (isCircularShowingEnabled())
{
index = 0;
}
else
{
return null;
}
}
return contentPanel.getWidget(index);
}
return null;
}
Widget getPreviousPanel()
{
int widgetCount = getWidgetCount();
if (widgetCount > 0)
{
int index = currentWidget-1;
if (index < 0)
{
if (isCircularShowingEnabled())
{
index = widgetCount-1;
}
else
{
return null;
}
}
return contentPanel.getWidget(index);
}
return null;
}
/**
* Verify if the current panel has a next widget to show.
* @return true if has next widget
*/
boolean hasNextWidget()
{
return isCircularShowingEnabled() || (currentWidget < getWidgetCount()-1);
}
/**
* Verify if the current panel has a previous widget to show.
* @return true if has previous widget
*/
boolean hasPreviousWidget()
{
return isCircularShowingEnabled() || (currentWidget > 0);
}
void slide(final int slideBy, boolean fireSlidingStartEvent)
{
sliding = true;
if (fireSlidingStartEvent)
{
SlideStartEvent.fire(this);
}
Transition.translateX(getCurrentPanel(), slideBy, slideTransitionDuration, new Callback()
{
@Override
public void onTransitionCompleted()
{
int nextIndex = getNextIndexAfterSlide(slideBy);
sliding = false;
setCurrentWidget(nextIndex);
SlideEndEvent.fire(Slider.this);
}
});
if (hasPreviousWidget())
{
Widget previousPanel = getPreviousPanel();
Transition.translateX(previousPanel, slideBy-previousPanel.getOffsetWidth(), slideTransitionDuration, null);
}
if (hasNextWidget())
{
Widget nextPanel = getNextPanel();
Transition.translateX(nextPanel, slideBy+nextPanel.getOffsetWidth(), slideTransitionDuration, null);
}
}
private void configureCurrentPanel()
{
Widget currentPanel = getCurrentPanel();
Transition.resetTransition(currentPanel);
currentPanel.setVisible(true);
}
private void configureHiddenPanel(Widget panel, boolean forward)
{
panel.setVisible(true);
int width = panel.getOffsetWidth();
Transition.translateX(panel, forward?width:-width, null);
}
private void configureNextPanel()
{
configureHiddenPanel(getNextPanel(), true);
}
private void configurePanels()
{
if (currentWidget >=0 && currentWidget < getWidgetCount())
{
configureCurrentPanel();
if (hasPreviousWidget())
{
configurePreviousPanel();
}
if (hasNextWidget())
{
configureNextPanel();
}
}
}
private void configurePreviousPanel()
{
configureHiddenPanel(getPreviousPanel(), false);
}
private int getNextIndexAfterSlide(final int slideBy)
{
int index = currentWidget + (slideBy==0?0:slideBy<0?1:-1);
if (isCircularShowingEnabled())
{
int widgetCount = getWidgetCount();
if (index >= widgetCount)
{
index = 0;
}
else if (index < 0)
{
index = widgetCount -1;
}
}
return index;
}
private boolean isCircularShowingEnabled()
{
return circularShowing && getWidgetCount() > 2;
}
/**
* Sets the widget that will be visible on this panel.
* @param index
*/
private void setCurrentWidget(final int index)
{
assert(index >=0 && index < getWidgetCount()):"Invalid index";
if (currentWidget != index)
{
this.currentWidget = index;
configurePanels();
SwapEvent.fire(this);
}
}
public static Slider createIfSupported()
{
if (isSupported())
{
return new Slider();
}
return null;
}
public static boolean isSupported()
{
if (supported == null)
{
supported = supportDetection();
}
return (supported);
}
private static Boolean supportDetection()
{
return Screen.getCurrentDevice().getInput().equals(Input.touch);
}
static class SliderEventHandlers implements OrientationChangeHandler
{
protected Slider slider;
@Override
public void onOrientationChange(OrientationChangeEvent event)
{
if (slider.getWidgetCount() > 0)
{
boolean hasNextPanel = slider.hasNextWidget();
boolean hasPreviousPanel = slider.hasPreviousWidget();
if (hasNextPanel || hasPreviousPanel)
{
Transition.translateX(slider.getCurrentPanel(), 0, null);
if (hasPreviousPanel)
{
Widget previousPanel = slider.getPreviousPanel();
Transition.translateX(previousPanel, -previousPanel.getOffsetWidth(), null);
}
if (hasNextPanel)
{
Widget nextPanel = slider.getNextPanel();
Transition.translateX(nextPanel, nextPanel.getOffsetWidth(), null);
}
}
}
}
protected void handleSliderEvents()
{
slider.addAttachHandler(new Handler()
{
private HandlerRegistration orientationHandlerRegistration;
@Override
public void onAttachOrDetach(AttachEvent event)
{
if (event.isAttached())
{
orientationHandlerRegistration = Screen.addOrientationChangeHandler(SliderEventHandlers.this);
}
else if (orientationHandlerRegistration != null)
{
orientationHandlerRegistration.removeHandler();
orientationHandlerRegistration = null;
}
}
});
}
void setSlider(Slider slider)
{
this.slider = slider;
}
}
}