ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
325 lines (315 loc) • 9.1 kB
JavaScript
/**
_enyo.Slideable_ is a control that can be dragged either horizontally or
vertically between a minimum and a maximum value. When released from
dragging, a Slideable will animate to its minimum or maximum position,
depending on the direction of the drag.
The _min_ value specifies a position to the left of, or above, the initial
position, to which the Slideable may be dragged.
The _max_ value specifies a position to the right of, or below, the initial
position, to which the Slideable may be dragged.
The _value_ property specifies the current position of the Slideable,
between the minimum and maximum positions.
_min_, _max_, and _value_ may be specified in units of "px" or "%".
The _axis_ property determines whether the Slideable slides left-to-right
("h") or up-and-down ("v").
The following control is placed 90% off the screen to the right, and slides
to its natural position.
{kind: "enyo.Slideable", value: -90, min: -90, unit: "%",
classes: "enyo-fit", style: "width: 300px;",
components: [
{content: "stuff"}
]
}
*/
enyo.kind({
name: "enyo.Slideable",
kind: "Control",
published: {
//* Direction of sliding; can be "h" or "v"
axis: "h",
//* Current position of the Slideable (a value between _min_ and _max_)
value: 0,
//* Unit for _min_, _max_, and _value_; can be "px" or "%"
unit: "px",
//* A minimum value to slide to
min: 0,
//* A maximum value to slide to
max: 0,
//* When truthy, apply CSS styles to allow GPU compositing of slideable content
//* if the platform allows.
accelerated: "auto",
//* Set to false to prevent the Slideable from dragging with elasticity
//* past its _min_ or _max_ value
overMoving: true,
//* Set to false to disable dragging
draggable: true
},
events: {
//* Fires when the Slideable finishes animating.
onAnimateFinish: "",
//* Fires when the position (i.e., _value_) of the Slideable changes.
onChange: ""
},
//* Set to true to prevent a drag from bubbling beyond the Slideable
preventDragPropagation: false,
//* @protected
tools: [
{kind: "Animator", onStep: "animatorStep", onEnd: "animatorComplete"}
],
handlers: {
ondragstart: "dragstart",
ondrag: "drag",
ondragfinish: "dragfinish"
},
kDragScalar: 1,
dragEventProp: "dx",
unitModifier: false,
canTransform: false,
//* @protected
create: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
this.acceleratedChanged();
this.transformChanged();
this.axisChanged();
this.valueChanged();
this.addClass("enyo-slideable");
};
}),
initComponents: enyo.inherit(function(sup) {
return function() {
this.createComponents(this.tools);
sup.apply(this, arguments);
};
}),
rendered: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
this.canModifyUnit();
this.updateDragScalar();
};
}),
resizeHandler: enyo.inherit(function(sup) {
return function() {
sup.apply(this, arguments);
this.updateDragScalar();
};
}),
canModifyUnit: function() {
if (!this.canTransform) {
var b = this.getInitialStyleValue(this.hasNode(), this.boundary);
// If inline style of "px" exists, while unit is "%"
if (b.match(/px/i) && (this.unit === "%")) {
// Set unitModifier - used to over-ride "%"
this.unitModifier = this.getBounds()[this.dimension];
}
}
},
getInitialStyleValue: function(inNode, inBoundary) {
var s = enyo.dom.getComputedStyle(inNode);
if (s) {
return s.getPropertyValue(inBoundary);
} else if (inNode && inNode.currentStyle) {
return inNode.currentStyle[inBoundary];
}
return "0";
},
updateBounds: function(inValue, inDimensions) {
var inBounds = {};
inBounds[this.boundary] = inValue;
this.setBounds(inBounds, this.unit);
this.setInlineStyles(inValue, inDimensions);
},
updateDragScalar: function() {
if (this.unit == "%") {
var d = this.getBounds()[this.dimension];
this.kDragScalar = d ? 100 / d : 1;
if (!this.canTransform) {
this.updateBounds(this.value, 100);
}
}
},
transformChanged: function() {
this.canTransform = enyo.dom.canTransform();
},
acceleratedChanged: function() {
if (!enyo.platform.android || enyo.platform.android <= 2) {
enyo.dom.accelerate(this, this.accelerated);
}
},
axisChanged: function() {
var h = this.axis == "h";
this.dragMoveProp = h ? "dx" : "dy";
this.shouldDragProp = h ? "horizontal" : "vertical";
this.transform = h ? "translateX" : "translateY";
this.dimension = h ? "width" : "height";
this.boundary = h ? "left" : "top";
},
setInlineStyles: function(inValue, inDimensions) {
var inBounds = {};
if (this.unitModifier) {
inBounds[this.boundary] = this.percentToPixels(inValue, this.unitModifier);
inBounds[this.dimension] = this.unitModifier;
this.setBounds(inBounds);
} else {
if (inDimensions) {
inBounds[this.dimension] = inDimensions;
} else {
inBounds[this.boundary] = inValue;
}
this.setBounds(inBounds, this.unit);
}
},
valueChanged: function(inLast) {
var v = this.value;
if (this.isOob(v) && !this.isAnimating()) {
this.value = this.overMoving ? this.dampValue(v) : this.clampValue(v);
}
// FIXME: android cannot handle nested compositing well so apply acceleration only if needed
// desktop chrome doesn't like this code path so avoid...
if (enyo.platform.android > 2) {
if (this.value) {
if (inLast === 0 || inLast === undefined) {
enyo.dom.accelerate(this, this.accelerated);
}
} else {
enyo.dom.accelerate(this, false);
}
}
// If platform supports transforms
if (this.canTransform) {
enyo.dom.transformValue(this, this.transform, this.value + this.unit);
// else update inline styles
} else {
this.setInlineStyles(this.value, false);
}
this.doChange();
},
getAnimator: function() {
return this.$.animator;
},
isAtMin: function() {
return this.value <= this.calcMin();
},
isAtMax: function() {
return this.value >= this.calcMax();
},
calcMin: function() {
return this.min;
},
calcMax: function() {
return this.max;
},
clampValue: function(inValue) {
var min = this.calcMin();
var max = this.calcMax();
return Math.max(min, Math.min(inValue, max));
},
dampValue: function(inValue) {
return this.dampBound(this.dampBound(inValue, this.min, 1), this.max, -1);
},
dampBound: function(inValue, inBoundary, inSign) {
var v = inValue;
if (v * inSign < inBoundary * inSign) {
v = inBoundary + (v - inBoundary) / 4;
}
return v;
},
percentToPixels: function(value, dimension) {
return Math.floor((dimension / 100) * value);
},
pixelsToPercent: function(value) {
var boundary = this.unitModifier ? this.getBounds()[this.dimension] : this.container.getBounds()[this.dimension];
return (value / boundary) * 100;
},
// dragging
shouldDrag: function(inEvent) {
return this.draggable && inEvent[this.shouldDragProp];
},
isOob: function(inValue) {
return inValue > this.calcMax() || inValue < this.calcMin();
},
dragstart: function(inSender, inEvent) {
if (this.shouldDrag(inEvent)) {
inEvent.preventDefault();
this.$.animator.stop();
inEvent.dragInfo = {};
this.dragging = true;
this.drag0 = this.value;
this.dragd0 = 0;
return this.preventDragPropagation;
}
},
drag: function(inSender, inEvent) {
if (this.dragging) {
inEvent.preventDefault();
var d = this.canTransform ? inEvent[this.dragMoveProp] * this.kDragScalar : this.pixelsToPercent(inEvent[this.dragMoveProp]);
var v = this.drag0 + d;
var dd = d - this.dragd0;
this.dragd0 = d;
if (dd) {
inEvent.dragInfo.minimizing = dd < 0;
}
this.setValue(v);
return this.preventDragPropagation;
}
},
dragfinish: function(inSender, inEvent) {
if (this.dragging) {
this.dragging = false;
this.completeDrag(inEvent);
inEvent.preventTap();
return this.preventDragPropagation;
}
},
completeDrag: function(inEvent) {
if (this.value !== this.calcMax() && this.value != this.calcMin()) {
this.animateToMinMax(inEvent.dragInfo.minimizing);
}
},
// animation
isAnimating: function() {
return this.$.animator.isAnimating();
},
play: function(inStart, inEnd) {
this.$.animator.play({
startValue: inStart,
endValue: inEnd,
node: this.hasNode()
});
},
//* @public
//* Animates to the given value.
animateTo: function(inValue) {
this.play(this.value, inValue);
},
//* Animates to the minimum value.
animateToMin: function() {
this.animateTo(this.calcMin());
},
//* Animates to the maximum value.
animateToMax: function() {
this.animateTo(this.calcMax());
},
//* @protected
animateToMinMax: function(inMin) {
if (inMin) {
this.animateToMin();
} else {
this.animateToMax();
}
},
animatorStep: function(inSender) {
this.setValue(inSender.value);
return true;
},
animatorComplete: function(inSender) {
this.doAnimateFinish(inSender);
return true;
},
//* @public
//* Toggles between _min_ and _max_ values with animation.
toggleMinMax: function() {
this.animateToMinMax(!this.isAtMin());
}
});