@qooxdoo/framework
Version:
The JS Framework for Coders
430 lines (361 loc) • 11 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2009 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Fabian Jakobs (fjakobs)
************************************************************************ */
/**
* EXPERIMENTAL!
*
* The axis maps virtual screen coordinates to item indexes. By default all
* items have the same size but it is also possible to give specific items
* a different size.
*/
qx.Class.define("qx.ui.virtual.core.Axis",
{
extend : qx.core.Object,
/**
* @param defaultItemSize {Integer} The default size of the items.
* @param itemCount {Integer} The number of item on the axis.
*/
construct : function(defaultItemSize, itemCount)
{
this.base(arguments);
this.itemCount = itemCount;
this.defaultItemSize = defaultItemSize;
// sparse array
this.customSizes = {};
},
events :
{
/** Every change to the axis configuration triggers this event. */
"change" : "qx.event.type.Event"
},
members :
{
__ranges : null,
/**
* Get the default size of the items.
*
* @return {Integer} The default item size.
*/
getDefaultItemSize : function() {
return this.defaultItemSize;
},
/**
* Set the default size the items.
*
* @param defaultItemSize {Integer} The default size of the items.
*/
setDefaultItemSize : function(defaultItemSize)
{
if (this.defaultItemSize !== defaultItemSize)
{
this.defaultItemSize = defaultItemSize;
this.__ranges = null;
this.fireNonBubblingEvent("change");
}
},
/**
* Get the number of items in the axis.
*
* @return {Integer} The number of items.
*/
getItemCount : function() {
return this.itemCount;
},
/**
* Set the number of items in the axis.
*
* @param itemCount {Integer} The new item count.
*/
setItemCount : function(itemCount)
{
if (this.itemCount !== itemCount)
{
this.itemCount = itemCount;
this.__ranges = null;
this.fireNonBubblingEvent("change");
}
},
/**
* Sets the size of a specific item. This allow item, which have a size
* different from the default size.
*
* @param index {Integer} Index of the item to change.
* @param size {Integer} New size of the item.
*/
setItemSize : function(index, size)
{
if (qx.core.Environment.get("qx.debug"))
{
this.assertArgumentsCount(arguments, 2, 2);
this.assert(
size >= 0 || size === null,
"'size' must be 'null' or an integer larger than 0."
);
}
if (this.customSizes[index] == size) {
return;
}
if (size === null) {
delete this.customSizes[index];
} else {
this.customSizes[index] = size;
}
this.__ranges = null;
this.fireNonBubblingEvent("change");
},
/**
* Get the size of the item at the given index.
*
* @param index {Integer} Index of the item to get the size for.
* @return {Integer} Size of the item.
*/
getItemSize : function(index)
{
// custom size of 0 is not allowed
return this.customSizes[index] || this.defaultItemSize;
},
/**
* Reset all custom sizes set with {@link #setItemSize}.
*/
resetItemSizes : function()
{
this.customSizes = {};
this.__ranges = null;
this.fireNonBubblingEvent("change");
},
/**
* Split the position range into disjunct intervals. Each interval starts
* with a custom sized cell. Each position is contained in exactly one range.
* The ranges are sorted according to their start position.
*
* Complexity: O(n log n) (n = number of custom sized cells)
*
* @return {Map[]} The sorted list of ranges.
*/
__getRanges : function()
{
if (this.__ranges) {
return this.__ranges;
}
var defaultSize = this.defaultItemSize;
var itemCount = this.itemCount;
var indexes = [];
for (var key in this.customSizes)
{
var index = parseInt(key, 10);
if (index < itemCount) {
indexes.push(index);
}
}
if (indexes.length == 0)
{
var ranges = [{
startIndex: 0,
endIndex: itemCount - 1,
firstItemSize: defaultSize,
rangeStart: 0,
rangeEnd: itemCount * defaultSize - 1
}];
this.__ranges = ranges;
return ranges;
}
indexes.sort(function(a,b) { return a > b ? 1 : -1;});
var ranges = [];
var correctionSum = 0;
for (var i=0; i<indexes.length; i++)
{
var index = indexes[i];
if (index >= itemCount) {
break;
}
var cellSize = this.customSizes[index];
var rangeStart = index * defaultSize + correctionSum;
correctionSum += cellSize - defaultSize;
ranges[i] = {
startIndex: index,
firstItemSize: cellSize,
rangeStart: rangeStart
};
if (i > 0) {
ranges[i-1].rangeEnd = rangeStart-1;
ranges[i-1].endIndex = index-1;
}
}
// fix first range
if (ranges[0].rangeStart > 0)
{
ranges.unshift({
startIndex: 0,
endIndex: ranges[0].startIndex-1,
firstItemSize: defaultSize,
rangeStart: 0,
rangeEnd: ranges[0].rangeStart-1
});
}
// fix last range
var lastRange = ranges[ranges.length-1];
var remainingItemsSize = (itemCount - lastRange.startIndex - 1) * defaultSize;
lastRange.rangeEnd = lastRange.rangeStart + lastRange.firstItemSize + remainingItemsSize - 1;
lastRange.endIndex = itemCount - 1;
this.__ranges = ranges;
return ranges;
},
/**
* Returns the range, which contains the position
*
* Complexity: O(log n) (n = number of custom sized cells)
*
* @param position {Integer} The position.
* @return {Map} The range, which contains the given position.
*/
__findRangeByPosition : function(position)
{
var ranges = this.__ranges || this.__getRanges();
var start = 0;
var end = ranges.length-1;
// binary search in the sorted ranges list
while (true)
{
var pivot = start + ((end - start) >> 1);
var range = ranges[pivot];
if (range.rangeEnd < position) {
start = pivot + 1;
} else if (range.rangeStart > position) {
end = pivot - 1;
} else {
return range;
}
}
},
/**
* Get the item and the offset into the item at the given position.
*
* @param position {Integer|null} The position to get the item for.
* @return {Map} A map with the keys <code>index</code> and
* <code>offset</code>. The index is the index of the item containing the
* position and offsets specifies offset into this item. If the position
* is outside of the range, <code>null</code> is returned.
*/
getItemAtPosition : function(position)
{
if (position < 0 || position >= this.getTotalSize()) {
return null;
}
var range = this.__findRangeByPosition(position);
var startPos = range.rangeStart;
var index = range.startIndex;
var firstItemSize = range.firstItemSize;
if (startPos + firstItemSize > position)
{
return {
index: index,
offset: position - startPos
};
}
else
{
var defaultSize = this.defaultItemSize;
return {
index: index + 1 + Math.floor((position - startPos - firstItemSize) / defaultSize),
offset: (position - startPos - firstItemSize) % defaultSize
};
}
},
/**
* Returns the range, which contains the position.
*
* Complexity: O(log n) (n = number of custom sized cells)
*
* @param index {Integer} The index of the item to get the range for.
* @return {Map} The range for the index.
*/
__findRangeByIndex : function(index)
{
var ranges = this.__ranges || this.__getRanges();
var start = 0;
var end = ranges.length-1;
// binary search in the sorted ranges list
while (true)
{
var pivot = start + ((end - start) >> 1);
var range = ranges[pivot];
if (range.endIndex < index) {
start = pivot + 1;
} else if (range.startIndex > index) {
end = pivot - 1;
} else {
return range;
}
}
},
/**
* Get the start position of the item with the given index.
*
* @param index {Integer} The item's index.
* @return {Integer|null} The start position of the item. If the index is outside
* of the axis range <code>null</code> is returned.
*/
getItemPosition : function(index)
{
if (index < 0 || index >= this.itemCount) {
return null;
}
var range = this.__findRangeByIndex(index);
if (range.startIndex == index) {
return range.rangeStart;
} else {
return range.rangeStart + range.firstItemSize + (index-range.startIndex-1) * this.defaultItemSize;
}
},
/**
* Returns the sum of all cell sizes.
*
* @return {Integer} The sum of all item sizes.
*/
getTotalSize : function()
{
var ranges = this.__ranges || this.__getRanges();
return ranges[ranges.length-1].rangeEnd + 1;
},
/**
* Get an array of item sizes starting with the item at "startIndex". The
* sum of all sizes in the returned array is at least "minSizeSum".
*
* @param startIndex {Integer} The index of the first item.
* @param minSizeSum {Integer} The minimum sum of the item sizes.
* @return {Integer[]} List of item sizes starting with the size of the item
* at index <code>startIndex</code>. The sum of the item sizes is at least
* <code>minSizeSum</code>.
*/
getItemSizes : function(startIndex, minSizeSum)
{
var customSizes = this.customSizes;
var defaultSize = this.defaultItemSize;
var sum = 0;
var sizes = [];
var i=0;
while (sum < minSizeSum)
{
var itemSize = customSizes[startIndex] != null ? customSizes[startIndex] : defaultSize;
startIndex++;
sum += itemSize;
sizes[i++] = itemSize;
if (startIndex >= this.itemCount) {
break;
}
}
return sizes;
}
},
destruct : function() {
this.customSizes = this.__ranges = null;
}
});