You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
18 KiB
594 lines
18 KiB
define(["dojo/_base/declare", "dojo/_base/sniff", "dojo/_base/lang", "dojo/aspect", "dojo/dom", "dojo/dom-attr", "dojo/dom-class", "dojo/dom-geometry", "dojo/dom-style", "dojo/dom-construct", "dojo/on", "dojo/_base/window", "dojo/mouse", "dijit/_WidgetBase", "dijit/_TemplatedMixin", "dijit/_Container", "./FisheyeListItem"], |
|
function(declare, has, lang, aspect, dom, attr, domClass, geometry, style, construct, on, winUtil, mouse, _WidgetBase, _TemplatedMixin, _Container, FisheyeListItem){ |
|
|
|
return declare("dojox.widget.FisheyeList", |
|
[_WidgetBase, _TemplatedMixin, _Container], { |
|
// summary: |
|
// Menu similar to the fish eye menu on the Mac OS |
|
// example: |
|
// | <div dojoType="dojo.widget.FisheyeList" |
|
// | itemWidth="40" itemHeight="40" |
|
// | itemMaxWidth="150" itemMaxHeight="150" |
|
// | orientation="horizontal" |
|
// | effectUnits="2" |
|
// | itemPadding="10" |
|
// | attachEdge="center" |
|
// | labelEdge="bottom"> |
|
// | |
|
// | <div dojoType="dojox.widget.FisheyeListItem" |
|
// | id="item1" |
|
// | onclick="alert('click on' + this.label + '(from widget id ' + this.widgetId + ')!');" |
|
// | label="Item 1" |
|
// | iconSrc="images/fisheye_1.png"> |
|
// | </div> |
|
// | ... |
|
// | </div> |
|
|
|
constructor: function(){ |
|
// |
|
// TODO |
|
// fix really long labels in vertical mode |
|
// |
|
|
|
this.pos = {'x': -1, 'y': -1}; // current cursor position, relative to the grid |
|
|
|
// for conservative trigger mode, when triggered, timerScale is gradually increased from 0 to 1 |
|
this.timerScale = 1.0; |
|
}, |
|
|
|
EDGE: { |
|
CENTER: 0, |
|
LEFT: 1, |
|
RIGHT: 2, |
|
TOP: 3, |
|
BOTTOM: 4 |
|
}, |
|
|
|
templateString: '<div class="dojoxFisheyeListBar" data-dojo-attach-point="containerNode"></div>', |
|
|
|
snarfChildDomOutput: true, |
|
|
|
// itemWidth: Integer |
|
// width of menu item (in pixels) in it's dormant state (when the mouse is far away) |
|
itemWidth: 40, |
|
|
|
// itemHeight: Integer |
|
// height of menu item (in pixels) in it's dormant state (when the mouse is far away) |
|
itemHeight: 40, |
|
|
|
// itemMaxWidth: Integer |
|
// width of menu item (in pixels) in it's fully enlarged state (when the mouse is directly over it) |
|
itemMaxWidth: 150, |
|
|
|
// itemMaxHeight: Integer |
|
// height of menu item (in pixels) in it's fully enlarged state (when the mouse is directly over it) |
|
itemMaxHeight: 150, |
|
|
|
imgNode: null, |
|
|
|
// orientation: String |
|
// orientation of the menu, either "horizontal" or "vertical" |
|
orientation: 'horizontal', |
|
|
|
// isFixed: Boolean |
|
// toggle to enable additional listener (window scroll) if FisheyeList is in a fixed postion |
|
isFixed: false, |
|
|
|
// conservativeTrigger: Boolean |
|
// if true, don't start enlarging menu items until mouse is over an image; |
|
// if false, start enlarging menu items as the mouse moves near them. |
|
conservativeTrigger: false, |
|
|
|
// effectUnits: Number |
|
// controls how much reaction the menu makes, relative to the distance of the mouse from the menu |
|
effectUnits: 2, |
|
|
|
// itemPadding: Integer |
|
// padding (in pixels) between each menu item |
|
itemPadding: 10, |
|
|
|
// attachEdge: String |
|
// Controls the border that the menu items don't expand past; |
|
// for example, if set to "top", then the menu items will drop downwards as they expand. |
|
// Values: "center", "left", "right", "top", "bottom". |
|
attachEdge: 'center', |
|
|
|
// labelEdge: String |
|
// Controls were the labels show up in relation to the menu item icons. |
|
// Values: "center", "left", "right", "top", "bottom". |
|
labelEdge: 'bottom', |
|
|
|
postCreate: function(){ |
|
var e = this.EDGE, |
|
isHorizontal = this.isHorizontal = (this.orientation == 'horizontal'); |
|
|
|
dom.setSelectable(this.domNode, false); |
|
|
|
this.selectedNode = -1; |
|
|
|
this.isOver = false; |
|
this.hitX1 = -1; |
|
this.hitY1 = -1; |
|
this.hitX2 = -1; |
|
this.hitY2 = -1; |
|
|
|
// |
|
// only some edges make sense... |
|
// |
|
this.anchorEdge = this._toEdge(this.attachEdge, e.CENTER); |
|
this.labelEdge = this._toEdge(this.labelEdge, e.TOP); |
|
|
|
if(this.labelEdge == e.CENTER){ this.labelEdge = e.TOP; } |
|
|
|
if(isHorizontal){ |
|
if(this.anchorEdge == e.LEFT){ this.anchorEdge = e.CENTER; } |
|
if(this.anchorEdge == e.RIGHT){ this.anchorEdge = e.CENTER; } |
|
if(this.labelEdge == e.LEFT){ this.labelEdge = e.TOP; } |
|
if(this.labelEdge == e.RIGHT){ this.labelEdge = e.TOP; } |
|
}else{ |
|
if(this.anchorEdge == e.TOP){ this.anchorEdge = e.CENTER; } |
|
if(this.anchorEdge == e.BOTTOM){ this.anchorEdge = e.CENTER; } |
|
if(this.labelEdge == e.TOP){ this.labelEdge = e.LEFT; } |
|
if(this.labelEdge == e.BOTTOM){ this.labelEdge = e.LEFT; } |
|
} |
|
|
|
// |
|
// figure out the proximity size |
|
// |
|
var effectUnits = this.effectUnits; |
|
this.proximityLeft = this.itemWidth * (effectUnits - 0.5); |
|
this.proximityRight = this.itemWidth * (effectUnits - 0.5); |
|
this.proximityTop = this.itemHeight * (effectUnits - 0.5); |
|
this.proximityBottom = this.itemHeight * (effectUnits - 0.5); |
|
|
|
if(this.anchorEdge == e.LEFT){ |
|
this.proximityLeft = 0; |
|
} |
|
if(this.anchorEdge == e.RIGHT){ |
|
this.proximityRight = 0; |
|
} |
|
if(this.anchorEdge == e.TOP){ |
|
this.proximityTop = 0; |
|
} |
|
if(this.anchorEdge == e.BOTTOM){ |
|
this.proximityBottom = 0; |
|
} |
|
if(this.anchorEdge == e.CENTER){ |
|
this.proximityLeft /= 2; |
|
this.proximityRight /= 2; |
|
this.proximityTop /= 2; |
|
this.proximityBottom /= 2; |
|
} |
|
}, |
|
|
|
startup: function(){ |
|
// summary: |
|
// create our connections and setup our FisheyeList |
|
this.children = this.getChildren(); |
|
//original postCreate() --tk |
|
this._initializePositioning(); |
|
|
|
// |
|
// in liberal trigger mode, activate menu whenever mouse is close, in conservative mode, pause until needed |
|
// |
|
this._onMouseMoveHandle = on.pausable(winUtil.doc.documentElement, "mousemove", lang.hitch(this, "_onMouseMove")); |
|
if(this.conservativeTrigger){ |
|
this._onMouseMoveHandle.pause(); |
|
} |
|
if(this.isFixed){ |
|
this.own(on(winUtil.doc,"scroll", lang.hitch(this, this._onScroll))); |
|
} |
|
|
|
// Deactivate the menu if mouse is moved off screen (doesn't work for FF?) |
|
this.own( |
|
on(winUtil.doc.documentElement, mouse.leave, lang.hitch(this, "_onBodyOut")), |
|
aspect.after(this, "addChild", lang.hitch(this, "_initializePositioning"), true), |
|
aspect.after(winUtil.global, "onresize", lang.hitch(this, "_initializePositioning"), true) |
|
); |
|
}, |
|
|
|
_initializePositioning: function(){ |
|
this.itemCount = this.children.length; |
|
|
|
this.barWidth = (this.isHorizontal ? this.itemCount : 1) * this.itemWidth; |
|
this.barHeight = (this.isHorizontal ? 1 : this.itemCount) * this.itemHeight; |
|
|
|
this.totalWidth = this.proximityLeft + this.proximityRight + this.barWidth; |
|
this.totalHeight = this.proximityTop + this.proximityBottom + this.barHeight; |
|
|
|
// |
|
// calculate effect ranges for each item |
|
// |
|
|
|
for(var i=0; i<this.children.length; i++){ |
|
|
|
this.children[i].posX = this.itemWidth * (this.isHorizontal ? i : 0); |
|
this.children[i].posY = this.itemHeight * (this.isHorizontal ? 0 : i); |
|
|
|
this.children[i].cenX = this.children[i].posX + (this.itemWidth / 2); |
|
this.children[i].cenY = this.children[i].posY + (this.itemHeight / 2); |
|
|
|
var isz = this.isHorizontal ? this.itemWidth : this.itemHeight, |
|
r = this.effectUnits * isz, |
|
c = this.isHorizontal ? this.children[i].cenX : this.children[i].cenY, |
|
lhs = this.isHorizontal ? this.proximityLeft : this.proximityTop, |
|
rhs = this.isHorizontal ? this.proximityRight : this.proximityBottom, |
|
siz = this.isHorizontal ? this.barWidth : this.barHeight, |
|
|
|
range_lhs = r, |
|
range_rhs = r; |
|
|
|
if(range_lhs > c+lhs){ range_lhs = c+lhs; } |
|
if(range_rhs > (siz-c+rhs)){ range_rhs = siz-c+rhs; } |
|
|
|
this.children[i].effectRangeLeft = range_lhs / isz; |
|
this.children[i].effectRangeRght = range_rhs / isz; |
|
|
|
//dojo.debug('effect range for '+i+' is '+range_lhs+'/'+range_rhs); |
|
} |
|
|
|
// |
|
// create the bar |
|
// |
|
style.set(this.domNode, {width: this.barWidth + 'px', height: this.barHeight + 'px'}); |
|
|
|
// |
|
// position the items |
|
// |
|
for(i=0; i<this.children.length; i++){ |
|
var itm = this.children[i]; |
|
var elm = itm.domNode; |
|
style.set(elm, {left: itm.posX + 'px', top: itm.posY + 'px', width: this.itemWidth + 'px', height: this.itemHeight + 'px'}); |
|
|
|
style.set(itm.imgNode, {left: this.itemPadding+'%', top: this.itemPadding+'%', width: (100 - 2 * this.itemPadding) + '%', height: (100 - 2 * this.itemPadding) + '%'}); |
|
} |
|
// |
|
// calc the grid |
|
// |
|
this._calcHitGrid(); |
|
}, |
|
|
|
_overElement: function(/* DomNode|String */ node, /* Event */ e){ |
|
// summary: |
|
// Returns whether the mouse is over the passed element. |
|
// node: |
|
// Must must be display:block (ie, not a `<span>`) |
|
node = dom.byId(node); |
|
var mouse = {x: e.pageX, y: e.pageY}, |
|
absolute = geometry.position(node, true), |
|
top = absolute.y, |
|
bottom = top + absolute.h, |
|
left = absolute.x, |
|
right = left + absolute.w; |
|
|
|
return (mouse.x >= left |
|
&& mouse.x <= right |
|
&& mouse.y >= top |
|
&& mouse.y <= bottom |
|
); // Boolean |
|
}, |
|
|
|
_onBodyOut: function(/*Event*/ e){ |
|
// clicking over an object inside of body causes this event to fire; ignore that case |
|
if( this._overElement(winUtil.body(), e) ){ |
|
return; |
|
} |
|
this._setDormant(e); |
|
}, |
|
|
|
_setDormant: function(/*Event*/ e){ |
|
// summary: |
|
// called when mouse moves out of menu's range |
|
|
|
if(!this.isOver){ return; } // already dormant? |
|
this.isOver = false; |
|
|
|
if(this.conservativeTrigger){ |
|
// user can't re-trigger the menu expansion |
|
// until he mouses over a icon again |
|
this._onMouseMoveHandle.pause(); |
|
} |
|
this._onGridMouseMove(-1, -1); |
|
}, |
|
|
|
_setActive: function(/*Event*/ e){ |
|
// summary: |
|
// called when mouse is moved into menu's range |
|
|
|
if(this.isOver){ return; } // already activated? |
|
this.isOver = true; |
|
|
|
if(this.conservativeTrigger){ |
|
// switch event handlers so that we handle mouse events from anywhere near |
|
// the menu |
|
this._onMouseMoveHandle.resume(); |
|
|
|
this.timerScale=0.0; |
|
|
|
// call mouse handler to do some initial necessary calculations/positioning |
|
this._onMouseMove(e); |
|
|
|
// slowly expand the icon size so it isn't jumpy |
|
this._expandSlowly(); |
|
} |
|
}, |
|
|
|
_onMouseMove: function(/*Event*/ e){ |
|
// summary: |
|
// called when mouse is moved |
|
if( (e.pageX >= this.hitX1) && (e.pageX <= this.hitX2) && |
|
(e.pageY >= this.hitY1) && (e.pageY <= this.hitY2) ){ |
|
if(!this.isOver){ |
|
this._setActive(e); |
|
} |
|
this._onGridMouseMove(e.pageX-this.hitX1, e.pageY-this.hitY1); |
|
}else{ |
|
if(this.isOver){ |
|
this._setDormant(e); |
|
} |
|
} |
|
}, |
|
|
|
_onScroll: function(){ |
|
this._calcHitGrid(); |
|
}, |
|
|
|
onResized: function(){ |
|
this._calcHitGrid(); |
|
}, |
|
|
|
_onGridMouseMove: function(x, y){ |
|
// summary: |
|
// called when mouse is moved in the vicinity of the menu |
|
this.pos = {x:x, y:y}; |
|
this._paint(); |
|
}, |
|
|
|
_paint: function(){ |
|
var x=this.pos.x; |
|
var y=this.pos.y; |
|
|
|
if(this.itemCount <= 0){ return; } |
|
|
|
// |
|
// figure out our main index |
|
// |
|
var pos = this.isHorizontal ? x : y, |
|
prx = this.isHorizontal ? this.proximityLeft : this.proximityTop, |
|
siz = this.isHorizontal ? this.itemWidth : this.itemHeight, |
|
sim = this.isHorizontal ? |
|
(1.0-this.timerScale)*this.itemWidth + this.timerScale*this.itemMaxWidth : |
|
(1.0-this.timerScale)*this.itemHeight + this.timerScale*this.itemMaxHeight, |
|
|
|
cen = ((pos - prx) / siz) - 0.5, |
|
max_off_cen = (sim / siz) - 0.5; |
|
|
|
if(max_off_cen > this.effectUnits){ max_off_cen = this.effectUnits; } |
|
|
|
// |
|
// figure out our off-axis weighting |
|
// |
|
var off_weight = 0, cen2; |
|
|
|
if(this.anchorEdge == this.EDGE.BOTTOM){ |
|
cen2 = (y - this.proximityTop) / this.itemHeight; |
|
off_weight = (cen2 > 0.5) ? 1 : y / (this.proximityTop + (this.itemHeight / 2)); |
|
} |
|
if(this.anchorEdge == this.EDGE.TOP){ |
|
cen2 = (y - this.proximityTop) / this.itemHeight; |
|
off_weight = (cen2 < 0.5) ? 1 : (this.totalHeight - y) / (this.proximityBottom + (this.itemHeight / 2)); |
|
} |
|
if(this.anchorEdge == this.EDGE.RIGHT){ |
|
cen2 = (x - this.proximityLeft) / this.itemWidth; |
|
off_weight = (cen2 > 0.5) ? 1 : x / (this.proximityLeft + (this.itemWidth / 2)); |
|
} |
|
if(this.anchorEdge == this.EDGE.LEFT){ |
|
cen2 = (x - this.proximityLeft) / this.itemWidth; |
|
off_weight = (cen2 < 0.5) ? 1 : (this.totalWidth - x) / (this.proximityRight + (this.itemWidth / 2)); |
|
} |
|
if(this.anchorEdge == this.EDGE.CENTER){ |
|
if(this.isHorizontal){ |
|
off_weight = y / (this.totalHeight); |
|
}else{ |
|
off_weight = x / (this.totalWidth); |
|
} |
|
|
|
if(off_weight > 0.5){ |
|
off_weight = 1 - off_weight; |
|
} |
|
|
|
off_weight *= 2; |
|
} |
|
|
|
// |
|
// set the sizes |
|
// |
|
for(var i=0; i<this.itemCount; i++){ |
|
var weight = this._weighAt(cen, i); |
|
if(weight < 0){weight = 0;} |
|
this._setItemSize(i, weight * off_weight); |
|
} |
|
|
|
// |
|
// set the positions |
|
// |
|
|
|
var main_p = Math.round(cen), |
|
offset = 0; |
|
|
|
if(cen < 0){ |
|
|
|
main_p = 0; |
|
|
|
}else if(cen > this.itemCount - 1){ |
|
|
|
main_p = this.itemCount -1; |
|
|
|
}else{ |
|
|
|
offset = (cen - main_p) * ((this.isHorizontal ? this.itemWidth : this.itemHeight) - this.children[main_p].sizeMain); |
|
} |
|
|
|
this._positionElementsFrom(main_p, offset); |
|
}, |
|
|
|
_weighAt: function(/*Integer*/ cen, /*Integer*/ i){ |
|
var dist = Math.abs(cen - i), |
|
limit = ((cen - i) > 0) ? this.children[i].effectRangeRght : this.children[i].effectRangeLeft; |
|
|
|
return (dist > limit) ? 0 : (1 - dist / limit); // Integer |
|
}, |
|
|
|
_setItemSize: function(p, scale){ |
|
if(this.children[p].scale == scale){ return; } |
|
this.children[p].scale = scale; |
|
|
|
scale *= this.timerScale; |
|
var w = Math.round(this.itemWidth + ((this.itemMaxWidth - this.itemWidth ) * scale)), |
|
h = Math.round(this.itemHeight + ((this.itemMaxHeight - this.itemHeight) * scale)); |
|
|
|
if(this.isHorizontal){ |
|
|
|
this.children[p].sizeW = w; |
|
this.children[p].sizeH = h; |
|
|
|
this.children[p].sizeMain = w; |
|
this.children[p].sizeOff = h; |
|
|
|
var y = 0; |
|
if(this.anchorEdge == this.EDGE.TOP){ |
|
y = (this.children[p].cenY - (this.itemHeight / 2)); |
|
}else if(this.anchorEdge == this.EDGE.BOTTOM){ |
|
y = (this.children[p].cenY - (h - (this.itemHeight / 2))); |
|
}else{ |
|
y = (this.children[p].cenY - (h / 2)); |
|
} |
|
|
|
this.children[p].usualX = Math.round(this.children[p].cenX - (w / 2)); |
|
style.set(this.children[p].domNode, {top: y + 'px', left: this.children[p].usualX + 'px'}); |
|
|
|
}else{ |
|
|
|
this.children[p].sizeW = w; |
|
this.children[p].sizeH = h; |
|
|
|
this.children[p].sizeOff = w; |
|
this.children[p].sizeMain = h; |
|
|
|
var x = 0; |
|
if(this.anchorEdge == this.EDGE.LEFT){ |
|
x = this.children[p].cenX - (this.itemWidth / 2); |
|
}else if(this.anchorEdge == this.EDGE.RIGHT){ |
|
x = this.children[p].cenX - (w - (this.itemWidth / 2)); |
|
}else{ |
|
x = this.children[p].cenX - (w / 2); |
|
} |
|
|
|
this.children[p].usualY = Math.round(this.children[p].cenY - (h / 2)); |
|
style.set(this.children[p].domNode, {left: x + 'px', top: this.children[p].usualY + 'px'}); |
|
} |
|
|
|
style.set(this.children[p].domNode, {width: w + 'px', height: h + 'px'}); |
|
|
|
if(this.children[p].svgNode){ |
|
this.children[p].svgNode.setSize(w, h); |
|
} |
|
}, |
|
|
|
_positionElementsFrom: function(p, offset){ |
|
var pos = 0; |
|
|
|
var usual, start; |
|
if(this.isHorizontal){ |
|
usual = "usualX"; |
|
start = "left"; |
|
}else{ |
|
usual = "usualY"; |
|
start = "top"; |
|
} |
|
pos = Math.round(this.children[p][usual] + offset); |
|
if(style.get(this.children[p].domNode, start) != (pos + 'px')){ |
|
style.set(this.children[p].domNode, start, pos + 'px'); |
|
this._positionLabel(this.children[p]); |
|
} |
|
|
|
// position before |
|
var bpos = pos; |
|
for(var i=p-1; i>=0; i--){ |
|
bpos -= this.children[i].sizeMain; |
|
|
|
if(style.get(this.children[p].domNode, start) != (bpos + 'px')){ |
|
style.set(this.children[i].domNode, start, bpos + 'px'); |
|
this._positionLabel(this.children[i]); |
|
} |
|
} |
|
|
|
// position after |
|
var apos = pos; |
|
for(i=p+1; i<this.itemCount; i++){ |
|
apos += this.children[i-1].sizeMain; |
|
if(style.get(this.children[p].domNode, start) != (apos + 'px')){ |
|
style.set(this.children[i].domNode, start, apos + 'px'); |
|
this._positionLabel(this.children[i]); |
|
} |
|
} |
|
|
|
}, |
|
|
|
_positionLabel: function(itm){ |
|
var x = 0; |
|
var y = 0; |
|
|
|
var mb = geometry.getMarginBox(itm.lblNode); |
|
|
|
if(this.labelEdge == this.EDGE.TOP){ |
|
x = Math.round((itm.sizeW / 2) - (mb.w / 2)); |
|
y = -mb.h; |
|
} |
|
|
|
if(this.labelEdge == this.EDGE.BOTTOM){ |
|
x = Math.round((itm.sizeW / 2) - (mb.w / 2)); |
|
y = itm.sizeH; |
|
} |
|
|
|
if(this.labelEdge == this.EDGE.LEFT){ |
|
x = -mb.w; |
|
y = Math.round((itm.sizeH / 2) - (mb.h / 2)); |
|
} |
|
|
|
if(this.labelEdge == this.EDGE.RIGHT){ |
|
x = itm.sizeW; |
|
y = Math.round((itm.sizeH / 2) - (mb.h / 2)); |
|
} |
|
|
|
style.set(itm.lblNode, {left: x + 'px', top: y + 'px'}); |
|
}, |
|
|
|
_calcHitGrid: function(){ |
|
|
|
var pos = geometry.position(this.domNode, true); |
|
|
|
this.hitX1 = pos.x - this.proximityLeft; |
|
this.hitY1 = pos.y - this.proximityTop; |
|
this.hitX2 = this.hitX1 + this.totalWidth; |
|
this.hitY2 = this.hitY1 + this.totalHeight; |
|
|
|
}, |
|
|
|
_toEdge: function(inp, def){ |
|
return this.EDGE[inp.toUpperCase()] || def; |
|
}, |
|
|
|
_expandSlowly: function(){ |
|
// summary: |
|
// slowly expand the image to user specified max size |
|
if(!this.isOver){ return; } |
|
this.timerScale += 0.2; |
|
this._paint(); |
|
if(this.timerScale<1.0){ |
|
setTimeout(lang.hitch(this, "_expandSlowly"), 10); |
|
} |
|
} |
|
}); |
|
|
|
});
|
|
|