Index: client/samples/swarm/Views.dart |
=================================================================== |
--- client/samples/swarm/Views.dart (revision 3770) |
+++ client/samples/swarm/Views.dart (working copy) |
@@ -1,985 +0,0 @@ |
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-// This file contains View framework classes. |
-// As it grows, it may need to be split into multiple files. |
- |
-/** A factory that creates a view from a data model. */ |
-interface ViewFactory<D> { |
- View newView(D item); |
- |
- /** The width of the created view or null if the width is not fixed. */ |
- int get width(); |
- |
- /** The height of the created view or null if the height is not fixed. */ |
- int get height(); |
-} |
- |
-interface VariableSizeViewFactory<D> { |
- View newView(D item); |
- |
- /** The width of the created view for a specific data model. */ |
- int getWidth(D item); |
- |
- /** The height of the created view for a specific data model. */ |
- int getHeight(D item); |
-} |
- |
-/** A collection of event listeners. */ |
-class EventListeners { |
- var listeners; |
- EventListeners() { |
- listeners = new List(); |
- } |
- |
- void addListener(listener) { |
- listeners.add(listener); |
- } |
- |
- void fire(var event) { |
- for (final listener in listeners) { |
- listener(event); |
- } |
- } |
-} |
- |
- |
-/** |
- * Private view class used to store placeholder views for detatched ListView |
- * elements. |
- */ |
-class _PlaceholderView extends View { |
- _PlaceholderView() : super() {} |
- |
- Element render() => new Element.tag('div'); |
-} |
- |
-/** |
- * Class providing all metrics required to layout a data driven list view. |
- */ |
-interface ListViewLayout<D> { |
- void onDataChange(); |
- |
- // TODO(jacobr): placing the newView member function on this class seems like |
- // the wrong design. |
- View newView(int index); |
- /** Get the height of the view. Possibly expensive to compute. */ |
- int getHeight(int viewLength); |
- /** Get the width of the view. Possibly expensive to compute. */ |
- int getWidth(int viewLength); |
- /** Get the length of the view. Possible expensive to compute. */ |
- int getLength(int viewLength); |
- /** Estimated height of the view. Guaranteed to be fast to compute. */ |
- int getEstimatedHeight(int viewLength); |
- /** Estimated with of the view. Guaranteed to be fast to compute. */ |
- int getEstimatedWidth(int viewLength); |
- |
- /** |
- * Returns the offset in px that the ith item in the view should be placed |
- * at. |
- */ |
- int getOffset(int index); |
- |
- /** |
- * The page the ith item in the view should be placed in. |
- */ |
- int getPage(int index, int viewLength); |
- int getPageStartIndex(int index, int viewLength); |
- |
- int getEstimatedLength(int viewLength); |
- /** |
- * Snap a specified index to the nearest visible view given the [viewLength]. |
- */ |
- int getSnapIndex(num offset, num viewLength); |
- /** |
- * Returns an interval specifying what views are currently visible given a |
- * particular [:offset:]. |
- */ |
- Interval computeVisibleInterval(num offset, num viewLength, |
- num bufferLength); |
-} |
- |
-/** |
- * Base class used for the simple fixed size item [:ListView:] classes and more |
- * complex list view classes such as [:VariableSizeListView:] using a |
- * [:ListViewLayout:] class to drive the actual layout. |
- */ |
-class GenericListView<D> extends View { |
- /** Minimum throw distance in pixels to trigger snapping to the next item. */ |
- static final SNAP_TO_NEXT_THROW_THRESHOLD = 15; |
- |
- static final INDEX_DATA_ATTRIBUTE = 'data-index'; |
- |
- final bool _scrollable; |
- final bool _showScrollbar; |
- final bool _snapToItems; |
- Scroller scroller; |
- Scrollbar _scrollbar; |
- List<D> _data; |
- ObservableValue<D> _selectedItem; |
- Map<int, View> _itemViews; |
- Element _containerElem; |
- bool _vertical; |
- /** Length of the scrollable dimension of the view in px. */ |
- int _viewLength = 0; |
- Interval _activeInterval; |
- bool _paginate; |
- bool _removeClippedViews; |
- ListViewLayout<D> _layout; |
- D _lastSelectedItem; |
- PageState _pages; |
- |
- /** |
- * Creates a new GenericListView with the given layout and data. If [:_data:] |
- * is an [:ObservableList<T>:] then it will listen to changes to the list |
- * and update the view appropriately. |
- */ |
- GenericListView( |
- this._layout, |
- this._data, |
- this._scrollable, |
- this._vertical, |
- this._selectedItem, |
- this._snapToItems, |
- this._paginate, |
- this._removeClippedViews, |
- this._showScrollbar, |
- this._pages) |
- : super(), |
- _activeInterval = new Interval(0, 0), |
- _itemViews = new Map<int, View>() { |
- // TODO(rnystrom): Move this into enterDocument once we have an exitDocument |
- // that we can use to unregister it. |
- if (_scrollable) { |
- window.on.resize.add((Event event) { |
- if (isInDocument) { |
- onResize(); |
- } |
- }); |
- } |
- } |
- |
- void onSelectedItemChange() { |
- // TODO(rnystrom): use Observable to track the last value of _selectedItem |
- // rather than tracking it ourselves. |
- _select(findIndex(_lastSelectedItem), false); |
- _select(findIndex(_selectedItem.value), true); |
- _lastSelectedItem = _selectedItem.value; |
- } |
- |
- Collection<View> get childViews() { |
- return _itemViews.getValues(); |
- } |
- |
- void _onClick(MouseEvent e) { |
- int index = _findAssociatedIndex(e.target); |
- if (index != null) { |
- _selectedItem.value = _data[index]; |
- } |
- } |
- |
- int _findAssociatedIndex(Node leafNode) { |
- Node node = leafNode; |
- while (node != null && node != _containerElem) { |
- if (node.parent == _containerElem) { |
- return _nodeToIndex(node); |
- } |
- node = node.parent; |
- } |
- return null; |
- } |
- |
- int _nodeToIndex(Element node) { |
- // TODO(jacobr): use data attributes when available. |
- String index = node.attributes[INDEX_DATA_ATTRIBUTE]; |
- if (index != null && index.length > 0) { |
- return Math.parseInt(index); |
- } |
- return null; |
- } |
- |
- Element render() { |
- final node = new Element.tag('div'); |
- if (_scrollable) { |
- _containerElem = new Element.tag('div'); |
- _containerElem.tabIndex = -1; |
- node.nodes.add(_containerElem); |
- } else { |
- _containerElem = node; |
- } |
- |
- if (_scrollable) { |
- scroller = new Scroller( |
- _containerElem, |
- _vertical /* verticalScrollEnabled */, |
- !_vertical /* horizontalScrollEnabled */, |
- true /* momentumEnabled */, |
- () { |
- num width = _layout.getWidth(_viewLength); |
- num height = _layout.getHeight(_viewLength); |
- width = width != null ? width : 0; |
- height = height != null ? height : 0; |
- final completer = new Completer<Size>(); |
- completer.complete(new Size(width, height)); |
- return completer.future; |
- }, |
- _paginate && _snapToItems ? |
- Scroller.FAST_SNAP_DECELERATION_FACTOR : 1); |
- scroller.onContentMoved.add((e) => renderVisibleItems(false)); |
- if (_pages != null) { |
- watch(_pages.target, (s) => _onPageSelected()); |
- } |
- |
- if (_snapToItems) { |
- scroller.onDecelStart.add((e) => _decelStart()); |
- scroller.onScrollerDragEnd.add((e) => _decelStart()); |
- } |
- if (_showScrollbar) { |
- _scrollbar = new Scrollbar(scroller, true); |
- } |
- } else { |
- _reserveArea(); |
- renderVisibleItems(true); |
- } |
- |
- return node; |
- } |
- |
- void afterRender(Element node) { |
- // If our data source is observable, observe it. |
- if (_data is ObservableList<D>) { |
- ObservableList<D> observable = _data; |
- attachWatch(observable, (EventSummary e) { |
- if (e.target == observable) { |
- onDataChange(); |
- } |
- }); |
- } |
- |
- if (_selectedItem != null) { |
- addOnClick(function(Event e) { _onClick(e); }); |
- } |
- |
- if (_selectedItem != null) { |
- watch(_selectedItem, (EventSummary summary) => onSelectedItemChange()); |
- } |
- } |
- |
- void onDataChange() { |
- _layout.onDataChange(); |
- _renderItems(); |
- } |
- |
- void _reserveArea() { |
- final style = _containerElem.style; |
- int width = _layout.getWidth(_viewLength); |
- int height = _layout.getHeight(_viewLength); |
- if (width != null) { |
- style.width = '${width}px'; |
- } |
- if (height != null) { |
- style.height = '${height}px'; |
- } |
- // TODO(jacobr): this should be specified by the default CSS for a |
- // GenericListView. |
- style.overflow = 'hidden'; |
- } |
- |
- |
- void onResize() { |
- int lastViewLength = _viewLength; |
- node.rect.then((ElementRect rect) { |
- _viewLength = _vertical ? rect.offset.height : rect.offset.width; |
- if (_viewLength != lastViewLength) { |
- if (_scrollbar != null) { |
- _scrollbar.refresh(); |
- } |
- renderVisibleItems(true); |
- } |
- }); |
- } |
- |
- void enterDocument() { |
- if (scroller != null) { |
- onResize(); |
- |
- if (_scrollbar != null) { |
- _scrollbar.initialize(); |
- } |
- } |
- } |
- |
- int getNextIndex(int index, bool forward) { |
- int delta = forward ? 1 : -1; |
- if (_paginate) { |
- int newPage = Math.max(0, _layout.getPage(index, _viewLength) + delta); |
- index = _layout.getPageStartIndex(newPage, _viewLength); |
- } else { |
- index += delta; |
- } |
- return GoogleMath.clamp(index, 0, _data.length - 1); |
- } |
- |
- void _decelStart() { |
- num currentTarget = scroller.verticalEnabled ? |
- scroller.currentTarget.y : scroller.currentTarget.x; |
- num current = scroller.verticalEnabled ? |
- scroller.contentOffset.y : scroller.contentOffset.x; |
- num targetIndex = _layout.getSnapIndex(currentTarget, _viewLength); |
- if (current != currentTarget) { |
- // The user is throwing rather than statically releasing. |
- // For this case, we want to move them to the next snap interval |
- // as long as they made at least a minimal throw gesture. |
- num currentIndex = _layout.getSnapIndex(current, _viewLength); |
- if (currentIndex == targetIndex && |
- (currentTarget - current).abs() > SNAP_TO_NEXT_THROW_THRESHOLD && |
- -_layout.getOffset(targetIndex) != currentTarget) { |
- num snappedCurrentPosition = -_layout.getOffset(targetIndex); |
- targetIndex = getNextIndex(targetIndex, currentTarget < current); |
- } |
- } |
- num targetPosition = -_layout.getOffset(targetIndex); |
- if (currentTarget != targetPosition) { |
- if (scroller.verticalEnabled) { |
- scroller.throwTo(scroller.contentOffset.x, targetPosition); |
- } else { |
- scroller.throwTo(targetPosition, scroller.contentOffset.y); |
- } |
- } else { |
- // Update the target page only after we are all done animating. |
- if (_pages != null) { |
- _pages.target.value =_layout.getPage(targetIndex, _viewLength); |
- } |
- } |
- } |
- |
- void _renderItems() { |
- for (int i = _activeInterval.start; i < _activeInterval.end; i++) { |
- _removeView(i); |
- } |
- _itemViews.clear(); |
- _activeInterval = new Interval(0, 0); |
- if (scroller == null) { |
- _reserveArea(); |
- } |
- renderVisibleItems(false); |
- } |
- |
- void _onPageSelected() { |
- if (_pages.target != |
- _layout.getPage(_activeInterval.start, _viewLength)) { |
- _throwTo(_layout.getOffset( |
- _layout.getPageStartIndex(_pages.target.value, _viewLength))); |
- } |
- } |
- |
- num get _offset() { |
- return scroller.verticalEnabled ? |
- scroller.getVerticalOffset() : scroller.getHorizontalOffset(); |
- } |
- |
- /** |
- * Calculates visible interval, based on the scroller position. |
- */ |
- Interval getVisibleInterval() { |
- return _layout.computeVisibleInterval(_offset, _viewLength, 0); |
- } |
- |
- void renderVisibleItems(bool lengthChanged) { |
- Interval targetInterval; |
- if (scroller != null) { |
- targetInterval = getVisibleInterval(); |
- } else { |
- // If the view is not scrollable, render all elements. |
- targetInterval = new Interval(0, _data.length); |
- } |
- |
- if (_pages != null) { |
- _pages.current.value = |
- _layout.getPage(targetInterval.start, _viewLength); |
- } |
- if (_pages != null) { |
- _pages.length.value = _data.length > 0 ? |
- _layout.getPage(_data.length - 1, _viewLength) + 1 : 0; |
- } |
- |
- if (!_removeClippedViews) { |
- // Avoid removing clipped views by extending the target interval to |
- // include the existing interval of rendered views. |
- targetInterval = targetInterval.union(_activeInterval); |
- } |
- |
- if (lengthChanged == false && targetInterval == _activeInterval) { |
- return; |
- } |
- |
- // TODO(jacobr): add unittests that this code behaves correctly. |
- |
- // Remove views that are not needed anymore |
- for (int i = _activeInterval.start, |
- end = Math.min(targetInterval.start, _activeInterval.end); |
- i < end; i++) { |
- _removeView(i); |
- } |
- for (int i = Math.max(targetInterval.end, _activeInterval.start); |
- i < _activeInterval.end; i++) { |
- _removeView(i); |
- } |
- |
- // Add new views |
- for (int i = targetInterval.start, |
- end = Math.min(_activeInterval.start, targetInterval.end); |
- i < end; i++) { |
- _addView(i); |
- } |
- for (int i = Math.max(_activeInterval.end, targetInterval.start); |
- i < targetInterval.end; i++) { |
- _addView(i); |
- } |
- |
- _activeInterval = targetInterval; |
- } |
- |
- void _removeView(int index) { |
- // Do not remove placeholder views as they need to stay present in case |
- // they scroll out of view and then back into view. |
- if (!(_itemViews[index] is _PlaceholderView)) { |
- // Remove from the active DOM but don't destroy. |
- _itemViews[index].node.remove(); |
- childViewRemoved(_itemViews[index]); |
- } |
- } |
- |
- View _newView(int index) { |
- final view = _layout.newView(index); |
- view.node.attributes[INDEX_DATA_ATTRIBUTE] = index.toString(); |
- return view; |
- } |
- |
- View _addView(int index) { |
- if (_itemViews.containsKey(index)) { |
- final view = _itemViews[index]; |
- _addViewHelper(view, index); |
- childViewAdded(view); |
- return view; |
- } |
- |
- final view = _newView(index); |
- _itemViews[index] = view; |
- // TODO(jacobr): its ugly to put this here... but its needed |
- // as typical even-odd css queries won't work as we only display some |
- // children at a time. |
- if (index == 0) { |
- view.addClass('first-child'); |
- } |
- _selectHelper(view, _data[index] == _lastSelectedItem); |
- // The order of the child elements doesn't matter as we use absolute |
- // positioning. |
- _addViewHelper(view, index); |
- childViewAdded(view); |
- return view; |
- } |
- |
- void _addViewHelper(View view, int index) { |
- _positionSubview(view.node, index); |
- // The view might already be attached. |
- if (view.node.parent != _containerElem) { |
- _containerElem.nodes.add(view.node); |
- } |
- } |
- |
- /** |
- * Detach a subview from the view replacing it with an empty placeholder view. |
- * The detatched subview can be safely reparented. |
- */ |
- View detachSubview(D itemData) { |
- int index = findIndex(itemData); |
- View view = _itemViews[index]; |
- if (view == null) { |
- // Edge case: add the view so we can detatch as the view is currently |
- // outside but might soon be inside the visible area. |
- assert(!_activeInterval.contains(index)); |
- _addView(index); |
- view = _itemViews[index]; |
- } |
- final placeholder = new _PlaceholderView(); |
- view.node.replaceWith(placeholder.node); |
- _itemViews[index] = placeholder; |
- return view; |
- } |
- |
- /** |
- * Reattach a subview from the view that was detached from the view |
- * by calling detachSubview. [callback] is called once the subview is |
- * reattached and done animating into position. |
- */ |
- void reattachSubview(D data, View view, bool animate) { |
- int index = findIndex(data); |
- // TODO(jacobr): perform some validation that the view is |
- // really detached. |
- var currentPosition; |
- if (animate) { |
- currentPosition = |
- FxUtil.computeRelativePosition(view.node, _containerElem); |
- } |
- assert (_itemViews[index] is _PlaceholderView); |
- view.enterDocument(); |
- _itemViews[index].node.replaceWith(view.node); |
- _itemViews[index] = view; |
- if (animate) { |
- FxUtil.setTranslate(view.node, currentPosition.x, currentPosition.y, 0); |
- // The view's position is unchanged except now re-parented to |
- // the list view. |
- window.setTimeout(() { _positionSubview(view.node, index); }, 0); |
- } else { |
- _positionSubview(view.node, index); |
- } |
- } |
- |
- int findIndex(D targetItem) { |
- // TODO(jacobr): move this to a util library or modify this class so that |
- // the data is an List not a Collection. |
- int i = 0; |
- for (D item in _data) { |
- if (item == targetItem) { |
- return i; |
- } |
- i++; |
- } |
- return null; |
- } |
- |
- void _positionSubview(Element node, int index) { |
- if (_vertical) { |
- FxUtil.setTranslate(node, 0, _layout.getOffset(index), 0); |
- } else { |
- FxUtil.setTranslate(node, _layout.getOffset(index), 0, 0); |
- } |
- node.style.zIndex = index.toString(); |
- } |
- |
- void _select(int index, bool selected) { |
- if (index != null) { |
- final subview = getSubview(index); |
- if (subview != null) { |
- _selectHelper(subview, selected); |
- } |
- } |
- } |
- |
- void _selectHelper(View view, bool selected) { |
- if (selected) { |
- view.addClass('sel'); |
- } else { |
- view.removeClass('sel'); |
- } |
- } |
- |
- View getSubview(int index) { |
- return _itemViews[index]; |
- } |
- |
- void showView(D targetItem) { |
- int index = findIndex(targetItem); |
- if (index != null) { |
- if (_layout.getOffset(index) < -_offset) { |
- _throwTo(_layout.getOffset(index)); |
- } else if (_layout.getOffset(index + 1) > (-_offset + _viewLength)) { |
- // TODO(jacobr): for completeness we should check whether |
- // the current view is longer than _viewLength in which case |
- // there are some nasty edge cases. |
- _throwTo(_layout.getOffset(index + 1) - _viewLength); |
- } |
- } |
- } |
- |
- void _throwTo(num offset) { |
- if (_vertical) { |
- scroller.throwTo(0, -offset); |
- } else { |
- scroller.throwTo(-offset, 0); |
- } |
- } |
-} |
- |
-class FixedSizeListViewLayout<D> implements ListViewLayout<D> { |
- final ViewFactory<D> itemViewFactory; |
- final bool _vertical; |
- List<D> _data; |
- bool _paginate; |
- |
- FixedSizeListViewLayout(this.itemViewFactory, this._data, this._vertical, |
- this._paginate); |
- |
- void onDataChange() {} |
- |
- View newView(int index) { |
- return itemViewFactory.newView(_data[index]); |
- } |
- |
- int get _itemLength() { |
- return _vertical ? itemViewFactory.height : itemViewFactory.width; |
- } |
- |
- |
- int getWidth(int viewLength) { |
- return _vertical ? itemViewFactory.width : getLength(viewLength); |
- } |
- |
- int getHeight(int viewLength) { |
- return _vertical ? getLength(viewLength) : itemViewFactory.height; |
- } |
- |
- int getEstimatedHeight(int viewLength) { |
- // Returns the exact height as it is trivial to compute for this layout. |
- return getHeight(viewLength); |
- } |
- |
- int getEstimatedWidth(int viewLength) { |
- // Returns the exact height as it is trivial to compute for this layout. |
- return getWidth(viewLength); |
- } |
- |
- int getEstimatedLength(int viewLength) { |
- // Returns the exact length as it is trivial to compute for this layout. |
- return getLength(viewLength); |
- } |
- |
- int getLength(int viewLength) { |
- int itemLength = |
- _vertical ? itemViewFactory.height : itemViewFactory.width; |
- if (viewLength == null || viewLength == 0) { |
- return itemLength * _data.length; |
- } else if (_paginate) { |
- if (_data.length > 0) { |
- final pageLength = getPageLength(viewLength); |
- return getPage(_data.length - 1, viewLength) |
- * pageLength + Math.max(viewLength, pageLength); |
- } else { |
- return 0; |
- } |
- } else { |
- return itemLength * (_data.length - 1) + Math.max(viewLength, itemLength); |
- } |
- } |
- |
- int getOffset(int index) { |
- return index * _itemLength; |
- } |
- |
- int getPageLength(int viewLength) { |
- final itemsPerPage = (viewLength / _itemLength).floor(); |
- return (Math.max(1, itemsPerPage) * _itemLength).toInt(); |
- } |
- |
- int getPage(int index, int viewLength) { |
- return (getOffset(index) / getPageLength(viewLength)).floor().toInt(); |
- } |
- |
- int getPageStartIndex(int page, int viewLength) { |
- return (getPageLength(viewLength) / _itemLength).toInt() * page; |
- } |
- |
- int getSnapIndex(num offset, int viewLength) { |
- int index = (-offset / _itemLength).round().toInt(); |
- if (_paginate) { |
- index = getPageStartIndex(getPage(index, viewLength), viewLength); |
- } |
- return GoogleMath.clamp(index, 0, _data.length - 1); |
- } |
- |
- Interval computeVisibleInterval( |
- num offset, num viewLength, num bufferLength) { |
- num targetIntervalStart = |
- Math.max(0,((-offset - bufferLength) / _itemLength).floor()); |
- num targetIntervalEnd = GoogleMath.clamp( |
- ((-offset + viewLength + bufferLength) / _itemLength).ceil(), |
- targetIntervalStart, |
- _data.length); |
- return new Interval(targetIntervalStart.toInt(), |
- targetIntervalEnd.toInt()); |
- } |
-} |
- |
-/** |
- * Simple list view class where each item has fixed width and height. |
- */ |
-class ListView<D> extends GenericListView<D> { |
- |
- /** |
- * Creates a new ListView for the given data. If [:_data:] is an |
- * [:ObservableList<T>:] then it will listen to changes to the list and |
- * update the view appropriately. |
- */ |
- ListView(List<D> data, ViewFactory<D> itemViewFactory, bool scrollable, |
- bool vertical, ObservableValue<D> selectedItem, |
- [bool snapToItems = false, |
- bool paginate = false, |
- bool removeClippedViews = false, |
- bool showScrollbar = false, |
- PageState pages = null]) |
- : super(new FixedSizeListViewLayout<D>(itemViewFactory, data, vertical, |
- paginate), |
- data, scrollable, vertical, selectedItem, snapToItems, paginate, |
- removeClippedViews, showScrollbar, pages); |
-} |
- |
-/** |
- * Layout where each item may have variable size along the axis the list view |
- * extends. |
- */ |
-class VariableSizeListViewLayout<D> implements ListViewLayout<D> { |
- List<D> _data; |
- List<int> _itemOffsets; |
- List<int> _lengths; |
- int _lastOffset = 0; |
- bool _vertical; |
- bool _paginate; |
- VariableSizeViewFactory<D> itemViewFactory; |
- Interval _lastVisibleInterval; |
- |
- VariableSizeListViewLayout(this.itemViewFactory, data, this._vertical, |
- this._paginate) : |
- _data = data, |
- _lastVisibleInterval = new Interval(0, 0) { |
- _itemOffsets = <int>[]; |
- _lengths = <int>[]; |
- _itemOffsets.add(0); |
- } |
- |
- void onDataChange() { |
- _itemOffsets.clear(); |
- _itemOffsets.add(0); |
- _lengths.clear(); |
- } |
- |
- View newView(int index) => itemViewFactory.newView(_data[index]); |
- |
- int getWidth(int viewLength) { |
- if (_vertical) { |
- return itemViewFactory.getWidth(null); |
- } else { |
- return getLength(viewLength); |
- } |
- } |
- |
- int getHeight(int viewLength) { |
- if (_vertical) { |
- return getLength(viewLength); |
- } else { |
- return itemViewFactory.getHeight(null); |
- } |
- } |
- |
- int getEstimatedHeight(int viewLength) { |
- if (_vertical) { |
- return getEstimatedLength(viewLength); |
- } else { |
- return itemViewFactory.getHeight(null); |
- } |
- } |
- |
- int getEstimatedWidth(int viewLength) { |
- if (_vertical) { |
- return itemViewFactory.getWidth(null); |
- } else { |
- return getEstimatedLength(viewLength); |
- } |
- } |
- |
- // TODO(jacobr): this logic is overly complicated. Replace with something |
- // simpler. |
- int getEstimatedLength(int viewLength) { |
- if (_lengths.length == _data.length) { |
- // No need to estimate... we have all the data already. |
- return getLength(viewLength); |
- } |
- if (_itemOffsets.length > 1 && _lengths.length > 0) { |
- // Estimate length by taking the average of the lengths |
- // of the known views. |
- num lengthFromAllButLastElement = 0; |
- if (_itemOffsets.length > 2) { |
- lengthFromAllButLastElement = |
- (getOffset(_itemOffsets.length - 2) - |
- getOffset(0)) * |
- (_data.length / (_itemOffsets.length - 2)); |
- } |
- return (lengthFromAllButLastElement + |
- Math.max(viewLength, _lengths[_lengths.length - 1])).toInt(); |
- } else { |
- if (_lengths.length == 1) { |
- return Math.max(viewLength, _lengths[0]); |
- } else { |
- return viewLength; |
- } |
- } |
- } |
- |
- int getLength(int viewLength) { |
- if (_data.length == 0) { |
- return viewLength; |
- } else { |
- // Hack so that _lengths[length - 1] is available. |
- getOffset(_data.length); |
- return (getOffset(_data.length - 1) - getOffset(0)) + |
- Math.max(_lengths[_lengths.length - 1], viewLength); |
- } |
- } |
- |
- int getOffset(int index) { |
- if (index >= _itemOffsets.length) { |
- int offset = _itemOffsets[_itemOffsets.length - 1]; |
- for (int i = _itemOffsets.length; i <= index; i++) { |
- int length = _vertical ? itemViewFactory.getHeight(_data[i - 1]) |
- : itemViewFactory.getWidth(_data[i - 1]); |
- offset += length; |
- _itemOffsets.add(offset); |
- _lengths.add(length); |
- } |
- } |
- return _itemOffsets[index]; |
- } |
- |
- int getPage(int index, int viewLength) { |
- // TODO(jacobr): implement. |
- throw 'Not implemented'; |
- } |
- |
- int getPageStartIndex(int page, int viewLength) { |
- // TODO(jacobr): implement. |
- throw 'Not implemented'; |
- } |
- |
- int getSnapIndex(num offset, int viewLength) { |
- for (int i = 1; i < _data.length; i++) { |
- if (getOffset(i) + getOffset(i - 1) > -offset * 2) { |
- return i - 1; |
- } |
- } |
- return _data.length - 1; |
- } |
- |
- Interval computeVisibleInterval( |
- num offset, num viewLength, num bufferLength) { |
- offset = offset.toInt(); |
- int start = _findFirstItemBefore( |
- -offset - bufferLength, |
- _lastVisibleInterval != null ? _lastVisibleInterval.start : 0); |
- int end = _findFirstItemAfter( |
- -offset + viewLength + bufferLength, |
- _lastVisibleInterval != null ? _lastVisibleInterval.end : 0); |
- _lastVisibleInterval = new Interval(start, Math.max(start, end)); |
- _lastOffset = offset; |
- return _lastVisibleInterval; |
- } |
- |
- int _findFirstItemAfter(num target, int hint) { |
- for (int i = 0; i < _data.length; i++) { |
- if (getOffset(i) > target) { |
- return i; |
- } |
- } |
- return _data.length; |
- } |
- |
- // TODO(jacobr): use hint. |
- int _findFirstItemBefore(num target, int hint) { |
- // We go search this direction delaying computing the actual view size |
- // as long as possible. |
- for (int i = 1; i < _data.length; i++) { |
- if (getOffset(i) >= target) { |
- return i - 1; |
- } |
- } |
- return Math.max(_data.length - 1, 0); |
- } |
-} |
- |
-class VariableSizeListView<D> extends GenericListView<D> { |
- |
- VariableSizeListView(List<D> data, |
- VariableSizeViewFactory<D> itemViewFactory, |
- bool scrollable, |
- bool vertical, |
- ObservableValue<D> selectedItem, |
- [bool snapToItems = false, |
- bool paginate = false, |
- bool removeClippedViews = false, |
- bool showScrollbar = false, |
- PageState pages = null]) |
- : super(new VariableSizeListViewLayout(itemViewFactory, data, vertical, |
- paginate), |
- data, scrollable, vertical, selectedItem, snapToItems, |
- paginate, removeClippedViews, showScrollbar, pages); |
-} |
- |
-/** A back button that is equivalent to clicking "back" in the browser. */ |
-class BackButton extends View { |
- BackButton() : super(); |
- |
- Element render() => new Element.html('<div class="back-arrow button"></div>'); |
- |
- void afterRender(Element node) { |
- addOnClick((e) => window.history.back()); |
- } |
-} |
- |
- |
-// TODO(terry): Maybe should be part of ButtonView class in appstack/view? |
-/** OS button. */ |
-class PushButtonView extends View { |
- final String _text; |
- final String _cssClass; |
- final _clickHandler; |
- |
- PushButtonView(this._text, this._cssClass, this._clickHandler) : super(); |
- |
- Element render() { |
- return new Element.html('<button class="${_cssClass}">${_text}</button>'); |
- } |
- |
- void afterRender(Element node) { |
- addOnClick(_clickHandler); |
- } |
-} |
- |
- |
-// TODO(terry): Add a drop shadow around edge and corners need to be rounded. |
-// Need to support conveyor for contents of dialog so it's not |
-// larger than the parent window. |
-/** A generic dialog view supports title, done button and dialog content. */ |
-class DialogView extends View { |
- final String _title; |
- final String _cssName; |
- final View _content; |
- Element container; |
- PushButtonView _done; |
- |
- DialogView(this._title, this._cssName, this._content) : super() {} |
- |
- Element render() { |
- final node = new Element.html(''' |
- <div class="dialog-modal"> |
- <div class="dialog $_cssName"> |
- <div class="dialog-title-area"> |
- <span class="dialog-title">$_title</span> |
- </div> |
- <div class="dialog-body"></div> |
- </div> |
- </div>'''); |
- |
- _done = new PushButtonView('Done', 'done-button', |
- EventBatch.wrap((e) => onDone())); |
- final titleArea = node.query('.dialog-title-area'); |
- titleArea.nodes.add(_done.node); |
- |
- container = node.query('.dialog-body'); |
- container.nodes.add(_content.node); |
- |
- return node; |
- } |
- |
- /** Override to handle dialog done. */ |
- void onDone() { } |
-} |