package com.project.website.canvas.client.shared.widgets;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HumanInputEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.project.shared.client.events.SimpleEvent.Handler;
import com.project.shared.client.handlers.RegistrationsManager;
import com.project.shared.client.utils.ElementUtils;
import com.project.shared.client.utils.EventUtils;
import com.project.shared.client.utils.SchedulerUtils;
import com.project.shared.client.utils.widgets.WidgetUtils;
import com.project.shared.data.Point2D;
import com.project.shared.data.Rectangle;
public class Slider extends Composite implements HasValueChangeHandlers<Double>, HasValue<Double> {
interface SliderUiBinder extends UiBinder<Widget, Slider> {
}
private static final int SCALE_PANEL_CHANGE_DELAY = 10;
private static final int MORE_LESS_BUTTON_STEPS = 10;
private static SliderUiBinder uiBinder = GWT.create(SliderUiBinder.class);
@UiField
Button lessButton;
@UiField
Button moreButton;
@UiField
Button dragButton;
@UiField
FlowPanel scalePanel;
@UiField
TextBox valueText;
@UiField
HTMLPanel sliderPanel;
private double _maxValue = 100;
private double _minValue = 0;
private double _value = 0;
private final RegistrationsManager dragRegs = new RegistrationsManager();
private final RegistrationsManager scalePanelPressedRegs = new RegistrationsManager();
private boolean _scalePanelPressed;
private boolean _isDragging;
/**
* Amount of value that is occupied in the scale panel by the width of the drag cutton
*/
private int _valuesPerDragButtonWidth = 0; // will be initialized after onLoad
public Slider() {
initWidget(uiBinder.createAndBindUi(this));
final Slider that = this;
this.setShowText(false);
WidgetUtils.addMovementStartHandler(dragButton, new Handler<HumanInputEvent<?>>() {
@Override public void onFire(HumanInputEvent<?> arg) {
startDragging();
}});
WidgetUtils.addMovementMoveHandler(dragButton, new Handler<HumanInputEvent<?>>() {
@Override public void onFire(HumanInputEvent<?> arg) {
double oldValue = that.getValue();
ValueChangeEvent.fireIfNotEqual(that, oldValue, that.getValue());
}});
this.lessButton.addClickHandler(new ClickHandler() {
@Override public void onClick(ClickEvent event) {
that.setValue(that.getValue() - that.getRange() / MORE_LESS_BUTTON_STEPS);
}});
this.moreButton.addClickHandler(new ClickHandler() {
@Override public void onClick(ClickEvent event) {
that.setValue(that.getValue() + that.getRange() / MORE_LESS_BUTTON_STEPS);
}});
this.scalePanel.addDomHandler(new MouseOutHandler() {
@Override public void onMouseOut(MouseOutEvent event) {
stopScalePanelDrag();
}
}, MouseOutEvent.getType());
WidgetUtils.addMovementStartHandler(this.scalePanel, new Handler<HumanInputEvent<?>>() {
@Override public void onFire(HumanInputEvent<?> arg) {
if (false == that._isDragging) {
handleScalePanelPressed();
}
}});
WidgetUtils.addMovementStopHandler(this.scalePanel, new Handler<HumanInputEvent<?>>() {
@Override public void onFire(HumanInputEvent<?> arg) {
stopScalePanelDrag();
}});
WidgetUtils.addMovementMoveHandler(this.scalePanel, new Handler<HumanInputEvent<?>>() {
@Override public void onFire(HumanInputEvent<?> arg) {
if ((that._scalePanelPressed) && (false == that._isDragging)) {
handleScalePanelPressed();
}
}});
this.valueText.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override public void onValueChange(ValueChangeEvent<String> event) {
that.setValue(Double.valueOf(event.getValue()), true, true, false);
}
});
}
@Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Double> handler) {
return this.addHandler(handler, ValueChangeEvent.getType());
}
public double getMaxValue() {
return _maxValue;
}
public double getMinValue() {
return _minValue;
}
@Override
public Double getValue() {
return this._value;
}
public boolean isShowText()
{
return this.valueText.isVisible();
}
public void setMaxValue(double maxValue) {
this._maxValue = maxValue;
}
public void setMinValue(double minValue) {
this._minValue = minValue;
}
public void setShowText(boolean showText)
{
this.valueText.setVisible(showText);
}
public void setSliderWidth(String width)
{
this.sliderPanel.setWidth(width);
}
@Override
public void setValue(Double value) {
this.setValue(value, true);
}
@Override
public void setValue(Double value, boolean fireEvents) {
this.setValue(value, fireEvents, true, true);
}
private void setValue(Double value, boolean fireEvents, boolean updateDragPosition, boolean updateText) {
value = Math.min(this._maxValue, Math.max(this._minValue, value));
Double oldValue = this._value;
this._value = value;
if (updateDragPosition) {
double newDragX = (this._value - this._minValue) / this.getRange() * this.getMaxAllowedDragPosX();
Point2D pos = new Point2D((int)Math.round(newDragX), 0);
ElementUtils.setElementCSSPosition(this.dragButton.getElement(), pos);
}
if (updateText) {
this.valueText.setText(value.toString());
}
if (fireEvents) {
ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
}
}
@Override
protected void onLoad()
{
super.onLoad();
final int scalePanelWidth = this.scalePanel.getOffsetWidth();
final int dragButtonWidth = this.dragButton.getOffsetWidth();
this._valuesPerDragButtonWidth = Math.max(1, (int) Math.round((this.getRange() / scalePanelWidth) * dragButtonWidth));
}
private void changeOnScalePanelPress(final Point2D pos)
{
Rectangle dragButtonRect = ElementUtils.getElementAbsoluteRectangle(this.dragButton.getElement());
if (dragButtonRect.contains(pos)) {
return;
}
// we must use topLeft corner to handle working on the panel when it is rotated
Point2D scalePanelTopLeft = ElementUtils.getElementAbsoluteRectangle(this.scalePanel.getElement()).getCorners().topLeft;
Point2D dragButtonTopLeft = dragButtonRect.getCorners().topLeft;
boolean isIncrease = 0 < (pos.minus(scalePanelTopLeft).getRadius() - dragButtonTopLeft.minus(scalePanelTopLeft).getRadius());
int direction = isIncrease ? 1 : -1;
setValue(getValue() + direction * this._valuesPerDragButtonWidth);
}
private Point2D getDragPositionRelativeToScalePanel() {
return ElementUtils.getElementAbsolutePosition(this.dragButton.getElement()).minus(ElementUtils.getElementAbsolutePosition(this.scalePanel.getElement()));
}
private int getMaxAllowedDragPosX() {
return this.scalePanel.getOffsetWidth() - this.dragButton.getOffsetWidth();
}
private double getRange() {
return this._maxValue - this._minValue;
}
private void handleScalePanelPressed()
{
stopScalePanelDrag();
_scalePanelPressed = true;
final Point2D pos = EventUtils.getCurrentMousePos();
changeOnScalePanelPress(pos);
this.scalePanelPressedRegs.add(SchedulerUtils.scheduleFixedPeriod(new RepeatingCommand() {
@Override public boolean execute() {
changeOnScalePanelPress(pos);
return _scalePanelPressed;
}
}, SCALE_PANEL_CHANGE_DELAY));
}
private void startDragging() {
this._isDragging = true;
this.dragRegs.add(Event.addNativePreviewHandler(new NativePreviewHandler() {
@Override public void onPreviewNativeEvent(NativePreviewEvent event) {
if (EventUtils.nativePreviewEventTypeIsAny(event, MouseMoveEvent.getType(), TouchMoveEvent.getType()))
{
updateDragPosition(new Point2D(event.getNativeEvent().getClientX(), event.getNativeEvent().getClientY()));
return;
}
if (EventUtils.nativePreviewEventTypeIsAny(event, MouseUpEvent.getType(), TouchEndEvent.getType()))
{
_isDragging = false;
dragRegs.clear();
return;
}
}
}));
}
private void stopScalePanelDrag()
{
_scalePanelPressed = false;
this.scalePanelPressedRegs.clear();
}
private void updateDragPosition(Point2D mousePos) {
Point2D pos = mousePos.minus(ElementUtils.getElementAbsolutePosition(this.scalePanel.getElement()));
Point2D maxPos = new Point2D(this.getMaxAllowedDragPosX(), 0);
pos = Point2D.min(maxPos, Point2D.max(Point2D.zero, pos));
ElementUtils.setElementCSSPosition(this.dragButton.getElement(), pos);
updateValueFromDragPosition();
}
private void updateValueFromDragPosition() {
double range = getRange();
double maxDragOffsetX = Math.max(1, ElementUtils.getElementOffsetSize(this.scalePanel.getElement()).getX());
double normalizedValue = getDragPositionRelativeToScalePanel().getX() / maxDragOffsetX;
this.setValue((normalizedValue * range) + this._minValue, true, false, true);
}
}