diff options
author | Lionel LASKE <llaske@c2s.fr> | 2012-09-18 19:16:20 (GMT) |
---|---|---|
committer | Lionel LASKE <llaske@c2s.fr> | 2012-09-18 19:16:20 (GMT) |
commit | 816de0918c28461cc2d1e3457348fd5b6e11950f (patch) | |
tree | 667959b6e32d998a5bf4ae927ea59d88b71bfd44 /html/lib/layout/panels |
Initial version
Diffstat (limited to 'html/lib/layout/panels')
-rw-r--r-- | html/lib/layout/panels/package.js | 3 | ||||
-rw-r--r-- | html/lib/layout/panels/source/Panels.css | 12 | ||||
-rw-r--r-- | html/lib/layout/panels/source/Panels.js | 386 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/Arranger.css | 29 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/Arranger.js | 226 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/CardArranger.js | 49 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/CardSlideInArranger.js | 62 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/CarouselArranger.js | 109 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/CollapsingArranger.js | 80 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/OtherArrangers.js | 202 | ||||
-rw-r--r-- | html/lib/layout/panels/source/arrangers/package.js | 9 | ||||
-rw-r--r-- | html/lib/layout/panels/source/package.js | 5 |
12 files changed, 1172 insertions, 0 deletions
diff --git a/html/lib/layout/panels/package.js b/html/lib/layout/panels/package.js new file mode 100644 index 0000000..f1de4f2 --- /dev/null +++ b/html/lib/layout/panels/package.js @@ -0,0 +1,3 @@ +enyo.depends( + "source/" +);
\ No newline at end of file diff --git a/html/lib/layout/panels/source/Panels.css b/html/lib/layout/panels/source/Panels.css new file mode 100644 index 0000000..2e79349 --- /dev/null +++ b/html/lib/layout/panels/source/Panels.css @@ -0,0 +1,12 @@ +.enyo-panels { +} + +.enyo-panels-fit-narrow { +} + +@media all and (max-width: 800px) { + .enyo-panels-fit-narrow > * { + min-width: 100%; + max-width: 100%; + } +}
\ No newline at end of file diff --git a/html/lib/layout/panels/source/Panels.js b/html/lib/layout/panels/source/Panels.js new file mode 100644 index 0000000..d2e2ecc --- /dev/null +++ b/html/lib/layout/panels/source/Panels.js @@ -0,0 +1,386 @@ +/** +The enyo.Panels kind is designed to satisfy a variety of common use cases for +application layout. Using enyo.Panels, controls may be arranged as (among other +things) a carousel, a set of collapsing panels, a card stack that fades between +panels, or a grid. + +Any Enyo control may be placed inside an enyo.Panels, but by convention we refer +to each of these controls as a "panel." From the set of panels in an enyo.Panels, +one is considered active. The active panel is set by index using the *setIndex* +method. The actual layout of the panels typically changes each time the active +panel is set, such that the new active panel has the most prominent position. + +For more information, see the [Panels documentation](https://github.com/enyojs/enyo/wiki/Panels) +in the Enyo Developer Guide. +*/ +enyo.kind({ + name: "enyo.Panels", + classes: "enyo-panels", + published: { + /** + The index of the active panel. The layout of panels is controlled by + the layoutKind, but as a rule, the active panel is displayed in the + most prominent position. For example, in the (default) CardArranger + layout, the active panel is shown and the other panels are hidden. + */ + index: 0, + //* Controls whether the user can drag between panels. + draggable: true, + //* Controls whether the panels animate when transitioning; for example, + //* when _setIndex_ is called. + animate: true, + //* Controls whether panels "wrap around" when moving past the end. Actual effect depends upon the arranger in use. + wrap: false, + //* Sets the arranger kind to be used for dynamic layout. + arrangerKind: "CardArranger", + //* By default, each panel will be sized to fit the Panels' width when + //* the screen size is narrow enough (less than ~800px). Set to false + //* to avoid this behavior. + narrowFit: true + }, + events: { + /** + Fires at the start of a panel transition. + This event fires when _setIndex_ is called and also during dragging. + */ + onTransitionStart: "", + /** + Fires at the end of a panel transition. + This event fires when _setIndex_ is called and also during dragging. + */ + onTransitionFinish: "" + }, + //* @protected + handlers: { + ondragstart: "dragstart", + ondrag: "drag", + ondragfinish: "dragfinish" + }, + tools: [ + {kind: "Animator", onStep: "step", onEnd: "completed"} + ], + fraction: 0, + create: function() { + this.transitionPoints = []; + this.inherited(arguments); + this.arrangerKindChanged(); + this.avoidFitChanged(); + this.indexChanged(); + }, + initComponents: function() { + this.createChrome(this.tools); + this.inherited(arguments); + }, + arrangerKindChanged: function() { + this.setLayoutKind(this.arrangerKind); + }, + avoidFitChanged: function() { + this.addRemoveClass("enyo-panels-fit-narrow", this.narrowFit); + }, + removeControl: function(inControl) { + this.inherited(arguments); + if (this.controls.length > 1 && this.isPanel(inControl)) { + this.setIndex(Math.max(this.index - 1, 0)); + this.flow(); + this.reflow(); + } + }, + isPanel: function() { + // designed to be overridden in kinds derived from Panels that have + // non-panel client controls + return true; + }, + flow: function() { + this.arrangements = []; + this.inherited(arguments); + }, + reflow: function() { + this.arrangements = []; + this.inherited(arguments); + this.refresh(); + }, + //* @public + /** + Returns an array of contained panels. + Subclasses can override this if they don't want the arranger to layout all of their children + */ + getPanels: function() { + var p = this.controlParent || this; + return p.children; + }, + //* Returns a reference to the active panel--i.e., the panel at the specified index. + getActive: function() { + var p$ = this.getPanels(); + return p$[this.index]; + }, + /** + Returns a reference to the <a href="#enyo.Animator">enyo.Animator</a> + instance used to animate panel transitions. The Panels' animator can be used + to set the duration of panel transitions, e.g.: + + this.getAnimator().setDuration(1000); + */ + getAnimator: function() { + return this.$.animator; + }, + /** + Sets the active panel to the panel specified by the given index. + Note that if the _animate_ property is set to true, the active panel + will animate into view. + */ + setIndex: function(inIndex) { + // override setIndex so that indexChanged is called + // whether this.index has actually changed or not + this.setPropertyValue("index", inIndex, "indexChanged"); + }, + /** + Sets the active panel to the panel specified by the given index. + Regardless of the value of the _animate_ property, the transition to the + next panel will not animate and will be immediate. + */ + setIndexDirect: function(inIndex) { + this.setIndex(inIndex); + this.completed(); + }, + //* Transitions to the previous panel--i.e., the panel whose index value is + //* one less than that of the current active panel. + previous: function() { + this.setIndex(this.index-1); + }, + //* Transitions to the next panel--i.e., the panel whose index value is one + //* greater than that of the current active panel. + next: function() { + this.setIndex(this.index+1); + }, + //* @protected + clamp: function(inValue) { + var l = this.getPanels().length-1; + if (this.wrap) { + // FIXME: dragging makes assumptions about direction and from->start indexes. + //return inValue < 0 ? l : (inValue > l ? 0 : inValue); + return inValue; + } else { + return Math.max(0, Math.min(inValue, l)); + } + }, + indexChanged: function(inOld) { + this.lastIndex = inOld; + this.index = this.clamp(this.index); + if (!this.dragging) { + if (this.$.animator.isAnimating()) { + this.completed(); + } + this.$.animator.stop(); + if (this.hasNode()) { + if (this.animate) { + this.startTransition(); + this.$.animator.play({ + startValue: this.fraction + }); + } else { + this.refresh(); + } + } + } + }, + step: function(inSender) { + this.fraction = inSender.value; + this.stepTransition(); + }, + completed: function() { + if (this.$.animator.isAnimating()) { + this.$.animator.stop(); + } + this.fraction = 1; + this.stepTransition(); + this.finishTransition(); + }, + dragstart: function(inSender, inEvent) { + if (this.draggable && this.layout && this.layout.canDragEvent(inEvent)) { + inEvent.preventDefault(); + this.dragstartTransition(inEvent); + this.dragging = true; + this.$.animator.stop(); + return true; + } + }, + drag: function(inSender, inEvent) { + if (this.dragging) { + inEvent.preventDefault(); + this.dragTransition(inEvent); + } + }, + dragfinish: function(inSender, inEvent) { + if (this.dragging) { + this.dragging = false; + inEvent.preventTap(); + this.dragfinishTransition(inEvent); + } + }, + dragstartTransition: function(inEvent) { + if (!this.$.animator.isAnimating()) { + var f = this.fromIndex = this.index; + this.toIndex = f - (this.layout ? this.layout.calcDragDirection(inEvent) : 0); + } else { + this.verifyDragTransition(inEvent); + } + this.fromIndex = this.clamp(this.fromIndex); + this.toIndex = this.clamp(this.toIndex); + //this.log(this.fromIndex, this.toIndex); + this.fireTransitionStart(); + if (this.layout) { + this.layout.start(); + } + }, + dragTransition: function(inEvent) { + // note: for simplicity we choose to calculate the distance directly between + // the first and last transition point. + var d = this.layout ? this.layout.calcDrag(inEvent) : 0; + var t$ = this.transitionPoints, s = t$[0], f = t$[t$.length-1]; + var as = this.fetchArrangement(s); + var af = this.fetchArrangement(f); + var dx = this.layout ? this.layout.drag(d, s, as, f, af) : 0; + var dragFail = d && !dx; + if (dragFail) { + //this.log(dx, s, as, f, af); + } + this.fraction += dx; + var fr = this.fraction; + if (fr > 1 || fr < 0 || dragFail) { + if (fr > 0 || dragFail) { + this.dragfinishTransition(inEvent); + } + this.dragstartTransition(inEvent); + this.fraction = 0; + // FIXME: account for lost fraction + //this.dragTransition(inEvent); + } + this.stepTransition(); + }, + dragfinishTransition: function(inEvent) { + this.verifyDragTransition(inEvent); + this.setIndex(this.toIndex); + // note: if we're still dragging, then we're at a transition boundary + // and should fire the finish event + if (this.dragging) { + this.fireTransitionFinish(); + } + }, + verifyDragTransition: function(inEvent) { + var d = this.layout ? this.layout.calcDragDirection(inEvent) : 0; + var f = Math.min(this.fromIndex, this.toIndex); + var t = Math.max(this.fromIndex, this.toIndex); + if (d > 0) { + var s = f; + f = t; + t = s; + } + if (f != this.fromIndex) { + this.fraction = 1 - this.fraction; + } + //this.log("old", this.fromIndex, this.toIndex, "new", f, t); + this.fromIndex = f; + this.toIndex = t; + }, + refresh: function() { + if (this.$.animator.isAnimating()) { + this.$.animator.stop(); + } + this.startTransition(); + this.fraction = 1; + this.stepTransition(); + this.finishTransition(); + }, + startTransition: function() { + this.fromIndex = this.fromIndex != null ? this.fromIndex : this.lastIndex || 0; + this.toIndex = this.toIndex != null ? this.toIndex : this.index; + //this.log(this.id, this.fromIndex, this.toIndex); + if (this.layout) { + this.layout.start(); + } + this.fireTransitionStart(); + }, + finishTransition: function() { + if (this.layout) { + this.layout.finish(); + } + this.transitionPoints = []; + this.fraction = 0; + this.fromIndex = this.toIndex = null; + this.fireTransitionFinish(); + }, + fireTransitionStart: function() { + var t = this.startTransitionInfo; + if (this.hasNode() && (!t || (t.fromIndex != this.fromIndex || t.toIndex != this.toIndex))) { + this.startTransitionInfo = {fromIndex: this.fromIndex, toIndex: this.toIndex}; + this.doTransitionStart(enyo.clone(this.startTransitionInfo)); + } + }, + fireTransitionFinish: function() { + var t = this.finishTransitionInfo; + if (this.hasNode() && (!t || (t.fromIndex != this.lastIndex || t.toIndex != this.index))) { + this.finishTransitionInfo = {fromIndex: this.lastIndex, toIndex: this.index}; + this.doTransitionFinish(enyo.clone(this.finishTransitionInfo)); + } + this.lastIndex=this.index; + }, + // gambit: we interpolate between arrangements as needed. + stepTransition: function() { + if (this.hasNode()) { + // select correct transition points and normalize fraction. + var t$ = this.transitionPoints; + var r = (this.fraction || 0) * (t$.length-1); + var i = Math.floor(r); + r = r - i; + var s = t$[i], f = t$[i+1]; + // get arrangements and lerp between them + var s0 = this.fetchArrangement(s); + var s1 = this.fetchArrangement(f); + this.arrangement = s0 && s1 ? enyo.Panels.lerp(s0, s1, r) : (s0 || s1); + if (this.arrangement && this.layout) { + this.layout.flowArrangement(); + } + } + }, + fetchArrangement: function(inName) { + if ((inName != null) && !this.arrangements[inName] && this.layout) { + this.layout._arrange(inName); + this.arrangements[inName] = this.readArrangement(this.getPanels()); + } + return this.arrangements[inName]; + }, + readArrangement: function(inC) { + var r = []; + for (var i=0, c$=inC, c; (c=c$[i]); i++) { + r.push(enyo.clone(c._arranger)); + } + return r; + }, + statics: { + isScreenNarrow: function() { + return enyo.dom.getWindowWidth() <= 800; + }, + lerp: function(inA0, inA1, inFrac) { + var r = []; + for (var i=0, k$=enyo.keys(inA0), k; (k=k$[i]); i++) { + r.push(this.lerpObject(inA0[k], inA1[k], inFrac)); + } + return r; + }, + lerpObject: function(inNew, inOld, inFrac) { + var b = enyo.clone(inNew), n, o; + // inOld might be undefined when deleting panels + if (inOld) { + for (var i in inNew) { + n = inNew[i]; + o = inOld[i]; + if (n != o) { + b[i] = n - (n - o) * inFrac; + } + } + } + return b; + } + } +}); + diff --git a/html/lib/layout/panels/source/arrangers/Arranger.css b/html/lib/layout/panels/source/arrangers/Arranger.css new file mode 100644 index 0000000..4d23be0 --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/Arranger.css @@ -0,0 +1,29 @@ +.enyo-arranger { + position: relative; + overflow: hidden; +} + +.enyo-arranger.enyo-fit { + position: absolute; +} + +.enyo-arranger > * { + position: absolute; + left: 0; + top: 0; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; +} + +.enyo-arranger-fit > * { + /* + override any width/height set on panels + */ + width: 100% !important; + height: 100% !important; + min-width: 0 !important; + max-width: auto !important; + min-height: 0 !important; + max-height: auto !important; +} diff --git a/html/lib/layout/panels/source/arrangers/Arranger.js b/html/lib/layout/panels/source/arrangers/Arranger.js new file mode 100644 index 0000000..b10f6b8 --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/Arranger.js @@ -0,0 +1,226 @@ +/** + _enyo.Arranger_ is an <a href="#enyo.Layout">enyo.Layout</a> that considers + one of the controls it lays out as active. The other controls are placed + relative to the active control as makes sense for the layout. + + Arranger supports dynamic layouts, meaning it's possible to transition + between its layouts via animation. Typically, arrangers should lay out + controls using CSS transforms, since these are optimized for animation. To + support this, the controls in an Arranger are absolutely positioned, and + the Arranger kind has an `accelerated` property, which marks controls for + CSS compositing. The default setting of "auto" ensures that this will occur + if enabled by the platform. + + For more information, see the documentation on + [Arrangers](https://github.com/enyojs/enyo/wiki/Arrangers) + in the Enyo Developer Guide. +*/ +enyo.kind({ + name: "enyo.Arranger", + kind: "Layout", + layoutClass: "enyo-arranger", + /** + Sets controls being laid out to use CSS compositing. A setting of "auto" + will mark controls for compositing if the platform supports it. + */ + accelerated: "auto", + //* Property of the drag event used to calculate the amount a drag moves the layout + dragProp: "ddx", + //* Property of the drag event used to calculate the direction of a drag + dragDirectionProp: "xDirection", + //* Property of the drag event used to calculate whether a drag should occur + canDragProp: "horizontal", + /** + If set to true, transitions between non-adjacent arrangements will go + through the intermediate arrangements. This is useful when direct + transitions between arrangements would be visually jarring. + */ + incrementalPoints: false, + /** + Called when removing an arranger (for example, when switching a Panels + control to a different arrangerKind). Subclasses should implement this + function to reset whatever properties they've changed on child controls. + You *must* call the superclass implementation in your subclass's + _destroy_ function. + */ + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + c._arranger = null; + } + this.inherited(arguments); + }, + //* @public + /** + Arranges the given array of controls (_inC_) in the layout specified by + _inName_. When implementing this method, rather than apply styling + directly to controls, call _arrangeControl(inControl, inArrangement)_ + with an _inArrangement_ object with styling settings. These will then be + applied via the _flowControl(inControl, inArrangement)_ method. + */ + arrange: function(inC, inName) { + }, + /** + Sizes the controls in the layout. This method is called only at reflow + time. Note that sizing is separated from other layout done in the + _arrange_ method because it is expensive and not suitable for dynamic + layout. + */ + size: function() { + }, + /** + Called when a layout transition begins. Implement this method to perform + tasks that should only occur when a transition starts; for example, some + controls could be shown or hidden. In addition, the _transitionPoints_ + array may be set on the container to dictate the named arrangments + between which the transition occurs. + */ + start: function() { + var f = this.container.fromIndex, t = this.container.toIndex; + var p$ = this.container.transitionPoints = [f]; + // optionally add a transition point for each index between from and to. + if (this.incrementalPoints) { + var d = Math.abs(t - f) - 2; + var i = f; + while (d >= 0) { + i = i + (t < f ? -1 : 1); + p$.push(i); + d--; + } + } + p$.push(this.container.toIndex); + }, + /** + Called when a layout transition completes. Implement this method to + perform tasks that should only occur when a transition ends; for + example, some controls could be shown or hidden. + */ + finish: function() { + }, + //* @protected + canDragEvent: function(inEvent) { + return inEvent[this.canDragProp]; + }, + calcDragDirection: function(inEvent) { + return inEvent[this.dragDirectionProp]; + }, + calcDrag: function(inEvent) { + return inEvent[this.dragProp]; + }, + drag: function(inDp, inAn, inA, inBn, inB) { + var f = this.measureArrangementDelta(-inDp, inAn, inA, inBn, inB); + return f; + }, + measureArrangementDelta: function(inX, inI0, inA0, inI1, inA1) { + var d = this.calcArrangementDifference(inI0, inA0, inI1, inA1); + var s = d ? inX / Math.abs(d) : 0; + s = s * (this.container.fromIndex > this.container.toIndex ? -1 : 1); + //enyo.log("delta", s); + return s; + }, + //* @public + /** + Called when dragging the layout, this method returns the difference in + pixels between the arrangement _inA0_ for layout setting _inI0_ and + arrangement _inA1_ for layout setting _inI1_. This data is used to calculate + the percentage that a drag should move the layout between two active states. + */ + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + }, + //* @protected + _arrange: function(inIndex) { + var c$ = this.getOrderedControls(inIndex); + this.arrange(c$, inIndex); + }, + arrangeControl: function(inControl, inArrangement) { + inControl._arranger = enyo.mixin(inControl._arranger || {}, inArrangement); + }, + flow: function() { + this.c$ = [].concat(this.container.getPanels()); + this.controlsIndex = 0; + for (var i=0, c$=this.container.getPanels(), c; c=c$[i]; i++) { + enyo.dom.accelerate(c, this.accelerated); + if (enyo.platform.safari) { + // On Safari-desktop, sometimes having the panel's direct child set to accelerate isn't sufficient + // this is most often the case with Lists contained inside another control, inside a Panels + var grands=c.children; + for (var j=0, kid; kid=grands[j]; j++) { + enyo.dom.accelerate(kid, this.accelerated); + } + } + } + }, + reflow: function() { + var cn = this.container.hasNode(); + this.containerBounds = cn ? {width: cn.clientWidth, height: cn.clientHeight} : {}; + this.size(); + }, + flowArrangement: function() { + var a = this.container.arrangement; + if (a) { + for (var i=0, c$=this.container.getPanels(), c; c=c$[i]; i++) { + this.flowControl(c, a[i]); + } + } + }, + //* @public + /** + Lays out the control (_inControl_) according to the settings stored in + the _inArrangment_ object. By default, _flowControl_ will apply settings + of left, top, and opacity. This method should only be implemented to + apply other settings made via _arrangeControl_. + */ + flowControl: function(inControl, inArrangement) { + enyo.Arranger.positionControl(inControl, inArrangement); + var o = inArrangement.opacity; + if (o != null) { + enyo.Arranger.opacifyControl(inControl, o); + } + }, + //* @protected + // Gets an array of controls arranged in state order. + // note: optimization, dial around a single array. + getOrderedControls: function(inIndex) { + var whole = Math.floor(inIndex); + var a = whole - this.controlsIndex; + var sign = a > 0; + var c$ = this.c$ || []; + for (var i=0; i<Math.abs(a); i++) { + if (sign) { + c$.push(c$.shift()); + } else { + c$.unshift(c$.pop()); + } + } + this.controlsIndex = whole; + return c$; + }, + statics: { + // Positions a control via transform: translateX/Y if supported and falls back to left/top if not. + positionControl: function(inControl, inBounds, inUnit) { + var unit = inUnit || "px"; + if (!this.updating) { + if (enyo.dom.canTransform() && !enyo.platform.android) { + var l = inBounds.left, t = inBounds.top; + var l = enyo.isString(l) ? l : l && (l + unit); + var t = enyo.isString(t) ? t : t && (t + unit); + enyo.dom.transform(inControl, {translateX: l || null, translateY: t || null}); + } else { + inControl.setBounds(inBounds, inUnit); + } + } + }, + opacifyControl: function(inControl, inOpacity) { + var o = inOpacity; + // FIXME: very high/low settings of opacity can cause a control to + // blink so cap this here. + o = o > .99 ? 1 : (o < .01 ? 0 : o); + // note: we only care about ie8 + if (enyo.platform.ie < 9) { + inControl.applyStyle("filter", "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + (o * 100) + ")"); + } else { + inControl.applyStyle("opacity", o); + } + } + } +}); diff --git a/html/lib/layout/panels/source/arrangers/CardArranger.js b/html/lib/layout/panels/source/arrangers/CardArranger.js new file mode 100644 index 0000000..28b94b2 --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/CardArranger.js @@ -0,0 +1,49 @@ +/** + _enyo.CardArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> that + displays only one active control. The non-active controls are hidden with + _setShowing(false)_. Transitions between arrangements are handled by fading + from one control to the next. +*/ +enyo.kind({ + name: "enyo.CardArranger", + kind: "Arranger", + layoutClass: "enyo-arranger enyo-arranger-fit", + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + return this.containerBounds.width; + }, + arrange: function(inC, inName) { + for (var i=0, c, b, v; c=inC[i]; i++) { + v = (i == 0) ? 1 : 0; + this.arrangeControl(c, {opacity: v}); + } + }, + start: function() { + this.inherited(arguments); + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + var wasShowing=c.showing; + c.setShowing(i == this.container.fromIndex || i == (this.container.toIndex)); + if (c.showing && !wasShowing) { + c.resized(); + } + } + + }, + finish: function() { + this.inherited(arguments); + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + c.setShowing(i == this.container.toIndex); + } + }, + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + enyo.Arranger.opacifyControl(c, 1); + if (!c.showing) { + c.setShowing(true); + } + } + this.inherited(arguments); + } +}); diff --git a/html/lib/layout/panels/source/arrangers/CardSlideInArranger.js b/html/lib/layout/panels/source/arrangers/CardSlideInArranger.js new file mode 100644 index 0000000..7700557 --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/CardSlideInArranger.js @@ -0,0 +1,62 @@ +/** + _enyo.CardSlideInArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> + that displays only one active control. The non-active controls are hidden + with _setShowing(false)_. Transitions between arrangements are handled by + sliding the new control over the current one. + + Note that CardSlideInArranger always slides controls in from the right. If + you want an arranger that slides to the right and left, try + <a href="#enyo.LeftRightArranger">enyo.LeftRightArranger</a>. +*/ +enyo.kind({ + name: "enyo.CardSlideInArranger", + kind: "CardArranger", + start: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + var wasShowing=c.showing; + c.setShowing(i == this.container.fromIndex || i == (this.container.toIndex)); + if (c.showing && !wasShowing) { + c.resized(); + } + } + var l = this.container.fromIndex; + var i = this.container.toIndex; + this.container.transitionPoints = [ + i + "." + l + ".s", + i + "." + l + ".f" + ]; + }, + finish: function() { + this.inherited(arguments); + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + c.setShowing(i == this.container.toIndex); + } + }, + arrange: function(inC, inName) { + var p = inName.split("."); + var f = p[0], s= p[1], starting = (p[2] == "s"); + var b = this.containerBounds.width; + for (var i=0, c$=this.container.getPanels(), c, v; c=c$[i]; i++) { + v = b; + if (s == i) { + v = starting ? 0 : -b; + } + if (f == i) { + v = starting ? b : 0; + } + if (s == i && s == f) { + v = 0; + } + this.arrangeControl(c, {left: v}); + } + }, + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + enyo.Arranger.positionControl(c, {left: null}); + } + this.inherited(arguments); + } +}); diff --git a/html/lib/layout/panels/source/arrangers/CarouselArranger.js b/html/lib/layout/panels/source/arrangers/CarouselArranger.js new file mode 100644 index 0000000..3a2f26a --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/CarouselArranger.js @@ -0,0 +1,109 @@ +/** + _enyo.CarouselArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> + that displays the active control, along with some number of inactive + controls to fill the available space. The active control is positioned on + the left side of the container, and the rest of the views are laid out to + the right. + + One of the controls may have _fit: true_ set, in which case it will take up + any remaining space after all of the other controls have been sized. + + For best results with CarouselArranger, you should set a minimum width for + each control via a CSS style, e.g., _min-width: 25%_ or _min-width: 250px_. + + Transitions between arrangements are handled by sliding the new controls in + from the right and sliding the old controls off to the left. +*/ +enyo.kind({ + name: "enyo.CarouselArranger", + kind: "Arranger", + size: function() { + var c$ = this.container.getPanels(); + var padding = this.containerPadding = this.container.hasNode() ? enyo.FittableLayout.calcPaddingExtents(this.container.node) : {}; + var pb = this.containerBounds; + pb.height -= padding.top + padding.bottom; + pb.width -= padding.left + padding.right; + // used space + var fit; + for (var i=0, s=0, m, c; c=c$[i]; i++) { + m = enyo.FittableLayout.calcMarginExtents(c.hasNode()); + c.width = c.getBounds().width; + c.marginWidth = m.right + m.left; + s += (c.fit ? 0 : c.width) + c.marginWidth; + if (c.fit) { + fit = c; + } + } + if (fit) { + var w = pb.width - s; + fit.width = w >= 0 ? w : fit.width; + } + for (var i=0, e=padding.left, m, c; c=c$[i]; i++) { + c.setBounds({top: padding.top, bottom: padding.bottom, width: c.fit ? c.width : null}); + } + }, + arrange: function(inC, inName) { + if (this.container.wrap) { + this.arrangeWrap(inC, inName); + } else { + this.arrangeNoWrap(inC, inName); + } + }, + arrangeNoWrap: function(inC, inName) { + var c$ = this.container.getPanels(); + var s = this.container.clamp(inName); + var nw = this.containerBounds.width; + // do we have enough content to fill the width? + for (var i=s, cw=0, c; c=c$[i]; i++) { + cw += c.width + c.marginWidth; + if (cw > nw) { + break; + } + } + // if content width is less than needed, adjust starting point index and offset + var n = nw - cw; + var o = 0; + if (n > 0) { + var s1 = s; + for (var i=s-1, aw=0, c; c=c$[i]; i--) { + aw += c.width + c.marginWidth; + if (n - aw <= 0) { + o = (n - aw); + s = i; + break; + } + } + } + // arrange starting from needed index with detected offset so we fill space + for (var i=0, e=this.containerPadding.left + o, w, c; c=c$[i]; i++) { + w = c.width + c.marginWidth; + if (i < s) { + this.arrangeControl(c, {left: -w}); + } else { + this.arrangeControl(c, {left: Math.floor(e)}); + e += w; + } + } + }, + arrangeWrap: function(inC, inName) { + for (var i=0, e=this.containerPadding.left, m, c; c=inC[i]; i++) { + this.arrangeControl(c, {left: e}); + e += c.width + c.marginWidth; + } + }, + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + var i = Math.abs(inI0 % this.c$.length); + return inA0[i].left - inA1[i].left; + }, + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + enyo.Arranger.positionControl(c, {left: null, top: null}); + c.applyStyle("top", null); + c.applyStyle("bottom", null); + c.applyStyle("left", null); + c.applyStyle("width", null); + } + this.inherited(arguments); + } +}); diff --git a/html/lib/layout/panels/source/arrangers/CollapsingArranger.js b/html/lib/layout/panels/source/arrangers/CollapsingArranger.js new file mode 100644 index 0000000..799c94b --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/CollapsingArranger.js @@ -0,0 +1,80 @@ +/** + _enyo.CollapsingArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> + that displays the active control, along with some number of inactive + controls to fill the available space. The active control is positioned on + the left side of the container and the rest of the views are laid out to the + right. The last control, if visible, will expand to fill whatever space is + not taken up by the previous controls. + + For best results with CollapsingArranger, you should set a minimum width + for each control via a CSS style, e.g., _min-width: 25%_ or + _min-width: 250px_. + + Transitions between arrangements are handled by sliding the new control in + from the right and collapsing the old control to the left. +*/ +enyo.kind({ + name: "enyo.CollapsingArranger", + kind: "CarouselArranger", + size: function() { + this.clearLastSize(); + this.inherited(arguments); + }, + //* @protected + // clear size from last if it's not actually the last + // (required for adding another control) + clearLastSize: function() { + for (var i=0, c$=this.container.getPanels(), c; c=c$[i]; i++) { + if (c._fit && i != c$.length-1) { + c.applyStyle("width", null); + c._fit = null; + } + } + }, + //* @public + arrange: function(inC, inIndex) { + var c$ = this.container.getPanels(); + for (var i=0, e=this.containerPadding.left, m, c; c=c$[i]; i++) { + this.arrangeControl(c, {left: e}); + if (i >= inIndex) { + e += c.width + c.marginWidth; + } + // FIXME: overdragging-ish + if (i == c$.length - 1 && inIndex < 0) { + this.arrangeControl(c, {left: e - inIndex}); + } + } + }, + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + var i = this.container.getPanels().length-1; + return Math.abs(inA1[i].left - inA0[i].left); + }, + flowControl: function(inControl, inA) { + this.inherited(arguments); + if (this.container.realtimeFit) { + var c$ = this.container.getPanels(); + var l = c$.length-1; + var last = c$[l]; + if (inControl == last) { + this.fitControl(inControl, inA.left); + } + } + + }, + finish: function() { + this.inherited(arguments); + if (!this.container.realtimeFit && this.containerBounds) { + var c$ = this.container.getPanels(); + var a$ = this.container.arrangement; + var l = c$.length-1; + var c = c$[l]; + this.fitControl(c, a$[l].left); + } + }, + //* @protected + fitControl: function(inControl, inOffset) { + inControl._fit = true; + inControl.applyStyle("width", (this.containerBounds.width - inOffset) + "px"); + inControl.resized(); + } +}); diff --git a/html/lib/layout/panels/source/arrangers/OtherArrangers.js b/html/lib/layout/panels/source/arrangers/OtherArrangers.js new file mode 100644 index 0000000..533bac3 --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/OtherArrangers.js @@ -0,0 +1,202 @@ +/** + _enyo.LeftRightArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> + that displays the active control and some of the previous and next controls. + The active control is centered horizontally in the container, and the + previous and next controls are laid out to the left and right, respectively. + + Transitions between arrangements are handled by sliding the new control + in from the right and sliding the active control out to the left. +*/ +enyo.kind({ + name: "enyo.LeftRightArranger", + kind: "Arranger", + //* The margin width (i.e., how much of the previous and next controls + //* are visible) in pixels + margin: 40, + //* @protected + axisSize: "width", + offAxisSize: "height", + axisPosition: "left", + constructor: function() { + this.inherited(arguments); + this.margin = this.container.margin != null ? this.container.margin : this.margin; + }, + //* @public + size: function() { + var c$ = this.container.getPanels(); + var port = this.containerBounds[this.axisSize]; + var box = port - this.margin -this.margin; + for (var i=0, b, c; c=c$[i]; i++) { + b = {}; + b[this.axisSize] = box; + b[this.offAxisSize] = "100%"; + c.setBounds(b); + } + }, + arrange: function(inC, inIndex) { + var o = Math.floor(this.container.getPanels().length/2); + var c$ = this.getOrderedControls(Math.floor(inIndex)-o); + var box = this.containerBounds[this.axisSize] - this.margin -this.margin; + var e = this.margin - box * o; + var m = (c$.length - 1) / 2; + for (var i=0, c, b, v; c=c$[i]; i++) { + b = {}; + b[this.axisPosition] = e; + b.opacity = (i == 0 || i == c$.length-1) ? 0 : 1; + this.arrangeControl(c, b); + e += box; + } + }, + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + var i = Math.abs(inI0 % this.c$.length); + //enyo.log(inI0, inI1); + return inA0[i][this.axisPosition] - inA1[i][this.axisPosition]; + }, + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + enyo.Arranger.positionControl(c, {left: null, top: null}); + enyo.Arranger.opacifyControl(c, 1); + c.applyStyle("left", null); + c.applyStyle("top", null); + c.applyStyle("height", null); + c.applyStyle("width", null); + } + this.inherited(arguments); + } +}); + +/** + _enyo.TopBottomArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> + that displays the active control and some of the previous and next controls. + The active control is centered vertically in the container, and the previous + and next controls are laid out above and below, respectively. + + Transitions between arrangements are handled by sliding the new control + in from the bottom and sliding the active control out the top. +*/ +enyo.kind({ + name: "enyo.TopBottomArranger", + kind: "LeftRightArranger", + dragProp: "ddy", + dragDirectionProp: "yDirection", + canDragProp: "vertical", + //* @protected + axisSize: "height", + offAxisSize: "width", + axisPosition: "top" +}); + +/** + _enyo.SpiralArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> that + arranges controls in a spiral. The active control is positioned on top and + the other controls are laid out in a spiral pattern below. + + Transitions between arrangements are handled by rotating the new control + up from below and rotating the active control down to the end of the spiral. +*/ +enyo.kind({ + name: "enyo.SpiralArranger", + kind: "Arranger", + //* Always go through incremental arrangements when transitioning + incrementalPoints: true, + //* The amount of space between successive controls + inc: 20, + size: function() { + var c$ = this.container.getPanels(); + var b = this.containerBounds; + var w = this.controlWidth = b.width/3; + var h = this.controlHeight = b.height/3; + for (var i=0, c; c=c$[i]; i++) { + c.setBounds({width: w, height: h}); + } + }, + arrange: function(inC, inName) { + var s = this.inc; + for (var i=0, l=inC.length, c; c=inC[i]; i++) { + var x = Math.cos(i/l * 2*Math.PI) * i * s + this.controlWidth; + var y = Math.sin(i/l * 2*Math.PI) * i * s + this.controlHeight; + this.arrangeControl(c, {left: x, top: y}); + } + }, + start: function() { + this.inherited(arguments); + var c$ = this.getOrderedControls(this.container.toIndex); + for (var i=0, c; c=c$[i]; i++) { + c.applyStyle("z-index", c$.length - i); + } + }, + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + return this.controlWidth; + }, + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + c.applyStyle("z-index", null); + enyo.Arranger.positionControl(c, {left: null, top: null}); + c.applyStyle("left", null); + c.applyStyle("top", null); + c.applyStyle("height", null); + c.applyStyle("width", null); + } + this.inherited(arguments); + } +}); + + +/** + _enyo.GridArranger_ is an <a href="#enyo.Arranger">enyo.Arranger</a> that + arranges controls in a grid. The active control is positioned at the + top-left of the grid and the other controls are laid out from left to right + and then from top to bottom. + + Transitions between arrangements are handled by moving the active control to + the end of the grid and shifting the other controls to the left, or up to + the previous row, to fill the space. +*/ +enyo.kind({ + name: "enyo.GridArranger", + kind: "Arranger", + //* Always go through incremental arrangements when transitioning + incrementalPoints: true, + //* @public + //* Column width + colWidth: 100, + //* Column height + colHeight: 100, + size: function() { + var c$ = this.container.getPanels(); + var w=this.colWidth, h=this.colHeight; + for (var i=0, c; c=c$[i]; i++) { + c.setBounds({width: w, height: h}); + } + }, + arrange: function(inC, inIndex) { + var w=this.colWidth, h=this.colHeight; + var cols = Math.floor(this.containerBounds.width / w); + var c; + for (var y=0, i=0; i<inC.length; y++) { + for (var x=0; (x<cols) && (c=inC[i]); x++, i++) { + this.arrangeControl(c, {left: w*x, top: h*y}); + } + } + }, + flowControl: function(inControl, inA) { + this.inherited(arguments); + enyo.Arranger.opacifyControl(inControl, inA.top % this.colHeight != 0 ? 0.25 : 1); + }, + calcArrangementDifference: function(inI0, inA0, inI1, inA1) { + return this.colWidth; + }, + destroy: function() { + var c$ = this.container.getPanels(); + for (var i=0, c; c=c$[i]; i++) { + enyo.Arranger.positionControl(c, {left: null, top: null}); + c.applyStyle("left", null); + c.applyStyle("top", null); + c.applyStyle("height", null); + c.applyStyle("width", null); + } + this.inherited(arguments); + } +}); diff --git a/html/lib/layout/panels/source/arrangers/package.js b/html/lib/layout/panels/source/arrangers/package.js new file mode 100644 index 0000000..135b754 --- /dev/null +++ b/html/lib/layout/panels/source/arrangers/package.js @@ -0,0 +1,9 @@ +enyo.depends( + "Arranger.js", + "Arranger.css", + "CardArranger.js", + "CardSlideInArranger.js", + "CarouselArranger.js", + "CollapsingArranger.js", + "OtherArrangers.js" +);
\ No newline at end of file diff --git a/html/lib/layout/panels/source/package.js b/html/lib/layout/panels/source/package.js new file mode 100644 index 0000000..270146f --- /dev/null +++ b/html/lib/layout/panels/source/package.js @@ -0,0 +1,5 @@ +enyo.depends( + "arrangers", + "Panels.css", + "Panels.js" +);
\ No newline at end of file |