UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

430 lines (361 loc) 11 kB
/* ************************************************************************ 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; } });