Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(426)

Side by Side Diff: client/samples/swarm/Views.dart

Issue 9314024: Final CL to kill off client/samples . (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « client/samples/swarm/UIState.dart ('k') | client/samples/swarm/appengine/app.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 // This file contains View framework classes.
6 // As it grows, it may need to be split into multiple files.
7
8 /** A factory that creates a view from a data model. */
9 interface ViewFactory<D> {
10 View newView(D item);
11
12 /** The width of the created view or null if the width is not fixed. */
13 int get width();
14
15 /** The height of the created view or null if the height is not fixed. */
16 int get height();
17 }
18
19 interface VariableSizeViewFactory<D> {
20 View newView(D item);
21
22 /** The width of the created view for a specific data model. */
23 int getWidth(D item);
24
25 /** The height of the created view for a specific data model. */
26 int getHeight(D item);
27 }
28
29 /** A collection of event listeners. */
30 class EventListeners {
31 var listeners;
32 EventListeners() {
33 listeners = new List();
34 }
35
36 void addListener(listener) {
37 listeners.add(listener);
38 }
39
40 void fire(var event) {
41 for (final listener in listeners) {
42 listener(event);
43 }
44 }
45 }
46
47
48 /**
49 * Private view class used to store placeholder views for detatched ListView
50 * elements.
51 */
52 class _PlaceholderView extends View {
53 _PlaceholderView() : super() {}
54
55 Element render() => new Element.tag('div');
56 }
57
58 /**
59 * Class providing all metrics required to layout a data driven list view.
60 */
61 interface ListViewLayout<D> {
62 void onDataChange();
63
64 // TODO(jacobr): placing the newView member function on this class seems like
65 // the wrong design.
66 View newView(int index);
67 /** Get the height of the view. Possibly expensive to compute. */
68 int getHeight(int viewLength);
69 /** Get the width of the view. Possibly expensive to compute. */
70 int getWidth(int viewLength);
71 /** Get the length of the view. Possible expensive to compute. */
72 int getLength(int viewLength);
73 /** Estimated height of the view. Guaranteed to be fast to compute. */
74 int getEstimatedHeight(int viewLength);
75 /** Estimated with of the view. Guaranteed to be fast to compute. */
76 int getEstimatedWidth(int viewLength);
77
78 /**
79 * Returns the offset in px that the ith item in the view should be placed
80 * at.
81 */
82 int getOffset(int index);
83
84 /**
85 * The page the ith item in the view should be placed in.
86 */
87 int getPage(int index, int viewLength);
88 int getPageStartIndex(int index, int viewLength);
89
90 int getEstimatedLength(int viewLength);
91 /**
92 * Snap a specified index to the nearest visible view given the [viewLength].
93 */
94 int getSnapIndex(num offset, num viewLength);
95 /**
96 * Returns an interval specifying what views are currently visible given a
97 * particular [:offset:].
98 */
99 Interval computeVisibleInterval(num offset, num viewLength,
100 num bufferLength);
101 }
102
103 /**
104 * Base class used for the simple fixed size item [:ListView:] classes and more
105 * complex list view classes such as [:VariableSizeListView:] using a
106 * [:ListViewLayout:] class to drive the actual layout.
107 */
108 class GenericListView<D> extends View {
109 /** Minimum throw distance in pixels to trigger snapping to the next item. */
110 static final SNAP_TO_NEXT_THROW_THRESHOLD = 15;
111
112 static final INDEX_DATA_ATTRIBUTE = 'data-index';
113
114 final bool _scrollable;
115 final bool _showScrollbar;
116 final bool _snapToItems;
117 Scroller scroller;
118 Scrollbar _scrollbar;
119 List<D> _data;
120 ObservableValue<D> _selectedItem;
121 Map<int, View> _itemViews;
122 Element _containerElem;
123 bool _vertical;
124 /** Length of the scrollable dimension of the view in px. */
125 int _viewLength = 0;
126 Interval _activeInterval;
127 bool _paginate;
128 bool _removeClippedViews;
129 ListViewLayout<D> _layout;
130 D _lastSelectedItem;
131 PageState _pages;
132
133 /**
134 * Creates a new GenericListView with the given layout and data. If [:_data:]
135 * is an [:ObservableList<T>:] then it will listen to changes to the list
136 * and update the view appropriately.
137 */
138 GenericListView(
139 this._layout,
140 this._data,
141 this._scrollable,
142 this._vertical,
143 this._selectedItem,
144 this._snapToItems,
145 this._paginate,
146 this._removeClippedViews,
147 this._showScrollbar,
148 this._pages)
149 : super(),
150 _activeInterval = new Interval(0, 0),
151 _itemViews = new Map<int, View>() {
152 // TODO(rnystrom): Move this into enterDocument once we have an exitDocument
153 // that we can use to unregister it.
154 if (_scrollable) {
155 window.on.resize.add((Event event) {
156 if (isInDocument) {
157 onResize();
158 }
159 });
160 }
161 }
162
163 void onSelectedItemChange() {
164 // TODO(rnystrom): use Observable to track the last value of _selectedItem
165 // rather than tracking it ourselves.
166 _select(findIndex(_lastSelectedItem), false);
167 _select(findIndex(_selectedItem.value), true);
168 _lastSelectedItem = _selectedItem.value;
169 }
170
171 Collection<View> get childViews() {
172 return _itemViews.getValues();
173 }
174
175 void _onClick(MouseEvent e) {
176 int index = _findAssociatedIndex(e.target);
177 if (index != null) {
178 _selectedItem.value = _data[index];
179 }
180 }
181
182 int _findAssociatedIndex(Node leafNode) {
183 Node node = leafNode;
184 while (node != null && node != _containerElem) {
185 if (node.parent == _containerElem) {
186 return _nodeToIndex(node);
187 }
188 node = node.parent;
189 }
190 return null;
191 }
192
193 int _nodeToIndex(Element node) {
194 // TODO(jacobr): use data attributes when available.
195 String index = node.attributes[INDEX_DATA_ATTRIBUTE];
196 if (index != null && index.length > 0) {
197 return Math.parseInt(index);
198 }
199 return null;
200 }
201
202 Element render() {
203 final node = new Element.tag('div');
204 if (_scrollable) {
205 _containerElem = new Element.tag('div');
206 _containerElem.tabIndex = -1;
207 node.nodes.add(_containerElem);
208 } else {
209 _containerElem = node;
210 }
211
212 if (_scrollable) {
213 scroller = new Scroller(
214 _containerElem,
215 _vertical /* verticalScrollEnabled */,
216 !_vertical /* horizontalScrollEnabled */,
217 true /* momentumEnabled */,
218 () {
219 num width = _layout.getWidth(_viewLength);
220 num height = _layout.getHeight(_viewLength);
221 width = width != null ? width : 0;
222 height = height != null ? height : 0;
223 final completer = new Completer<Size>();
224 completer.complete(new Size(width, height));
225 return completer.future;
226 },
227 _paginate && _snapToItems ?
228 Scroller.FAST_SNAP_DECELERATION_FACTOR : 1);
229 scroller.onContentMoved.add((e) => renderVisibleItems(false));
230 if (_pages != null) {
231 watch(_pages.target, (s) => _onPageSelected());
232 }
233
234 if (_snapToItems) {
235 scroller.onDecelStart.add((e) => _decelStart());
236 scroller.onScrollerDragEnd.add((e) => _decelStart());
237 }
238 if (_showScrollbar) {
239 _scrollbar = new Scrollbar(scroller, true);
240 }
241 } else {
242 _reserveArea();
243 renderVisibleItems(true);
244 }
245
246 return node;
247 }
248
249 void afterRender(Element node) {
250 // If our data source is observable, observe it.
251 if (_data is ObservableList<D>) {
252 ObservableList<D> observable = _data;
253 attachWatch(observable, (EventSummary e) {
254 if (e.target == observable) {
255 onDataChange();
256 }
257 });
258 }
259
260 if (_selectedItem != null) {
261 addOnClick(function(Event e) { _onClick(e); });
262 }
263
264 if (_selectedItem != null) {
265 watch(_selectedItem, (EventSummary summary) => onSelectedItemChange());
266 }
267 }
268
269 void onDataChange() {
270 _layout.onDataChange();
271 _renderItems();
272 }
273
274 void _reserveArea() {
275 final style = _containerElem.style;
276 int width = _layout.getWidth(_viewLength);
277 int height = _layout.getHeight(_viewLength);
278 if (width != null) {
279 style.width = '${width}px';
280 }
281 if (height != null) {
282 style.height = '${height}px';
283 }
284 // TODO(jacobr): this should be specified by the default CSS for a
285 // GenericListView.
286 style.overflow = 'hidden';
287 }
288
289
290 void onResize() {
291 int lastViewLength = _viewLength;
292 node.rect.then((ElementRect rect) {
293 _viewLength = _vertical ? rect.offset.height : rect.offset.width;
294 if (_viewLength != lastViewLength) {
295 if (_scrollbar != null) {
296 _scrollbar.refresh();
297 }
298 renderVisibleItems(true);
299 }
300 });
301 }
302
303 void enterDocument() {
304 if (scroller != null) {
305 onResize();
306
307 if (_scrollbar != null) {
308 _scrollbar.initialize();
309 }
310 }
311 }
312
313 int getNextIndex(int index, bool forward) {
314 int delta = forward ? 1 : -1;
315 if (_paginate) {
316 int newPage = Math.max(0, _layout.getPage(index, _viewLength) + delta);
317 index = _layout.getPageStartIndex(newPage, _viewLength);
318 } else {
319 index += delta;
320 }
321 return GoogleMath.clamp(index, 0, _data.length - 1);
322 }
323
324 void _decelStart() {
325 num currentTarget = scroller.verticalEnabled ?
326 scroller.currentTarget.y : scroller.currentTarget.x;
327 num current = scroller.verticalEnabled ?
328 scroller.contentOffset.y : scroller.contentOffset.x;
329 num targetIndex = _layout.getSnapIndex(currentTarget, _viewLength);
330 if (current != currentTarget) {
331 // The user is throwing rather than statically releasing.
332 // For this case, we want to move them to the next snap interval
333 // as long as they made at least a minimal throw gesture.
334 num currentIndex = _layout.getSnapIndex(current, _viewLength);
335 if (currentIndex == targetIndex &&
336 (currentTarget - current).abs() > SNAP_TO_NEXT_THROW_THRESHOLD &&
337 -_layout.getOffset(targetIndex) != currentTarget) {
338 num snappedCurrentPosition = -_layout.getOffset(targetIndex);
339 targetIndex = getNextIndex(targetIndex, currentTarget < current);
340 }
341 }
342 num targetPosition = -_layout.getOffset(targetIndex);
343 if (currentTarget != targetPosition) {
344 if (scroller.verticalEnabled) {
345 scroller.throwTo(scroller.contentOffset.x, targetPosition);
346 } else {
347 scroller.throwTo(targetPosition, scroller.contentOffset.y);
348 }
349 } else {
350 // Update the target page only after we are all done animating.
351 if (_pages != null) {
352 _pages.target.value =_layout.getPage(targetIndex, _viewLength);
353 }
354 }
355 }
356
357 void _renderItems() {
358 for (int i = _activeInterval.start; i < _activeInterval.end; i++) {
359 _removeView(i);
360 }
361 _itemViews.clear();
362 _activeInterval = new Interval(0, 0);
363 if (scroller == null) {
364 _reserveArea();
365 }
366 renderVisibleItems(false);
367 }
368
369 void _onPageSelected() {
370 if (_pages.target !=
371 _layout.getPage(_activeInterval.start, _viewLength)) {
372 _throwTo(_layout.getOffset(
373 _layout.getPageStartIndex(_pages.target.value, _viewLength)));
374 }
375 }
376
377 num get _offset() {
378 return scroller.verticalEnabled ?
379 scroller.getVerticalOffset() : scroller.getHorizontalOffset();
380 }
381
382 /**
383 * Calculates visible interval, based on the scroller position.
384 */
385 Interval getVisibleInterval() {
386 return _layout.computeVisibleInterval(_offset, _viewLength, 0);
387 }
388
389 void renderVisibleItems(bool lengthChanged) {
390 Interval targetInterval;
391 if (scroller != null) {
392 targetInterval = getVisibleInterval();
393 } else {
394 // If the view is not scrollable, render all elements.
395 targetInterval = new Interval(0, _data.length);
396 }
397
398 if (_pages != null) {
399 _pages.current.value =
400 _layout.getPage(targetInterval.start, _viewLength);
401 }
402 if (_pages != null) {
403 _pages.length.value = _data.length > 0 ?
404 _layout.getPage(_data.length - 1, _viewLength) + 1 : 0;
405 }
406
407 if (!_removeClippedViews) {
408 // Avoid removing clipped views by extending the target interval to
409 // include the existing interval of rendered views.
410 targetInterval = targetInterval.union(_activeInterval);
411 }
412
413 if (lengthChanged == false && targetInterval == _activeInterval) {
414 return;
415 }
416
417 // TODO(jacobr): add unittests that this code behaves correctly.
418
419 // Remove views that are not needed anymore
420 for (int i = _activeInterval.start,
421 end = Math.min(targetInterval.start, _activeInterval.end);
422 i < end; i++) {
423 _removeView(i);
424 }
425 for (int i = Math.max(targetInterval.end, _activeInterval.start);
426 i < _activeInterval.end; i++) {
427 _removeView(i);
428 }
429
430 // Add new views
431 for (int i = targetInterval.start,
432 end = Math.min(_activeInterval.start, targetInterval.end);
433 i < end; i++) {
434 _addView(i);
435 }
436 for (int i = Math.max(_activeInterval.end, targetInterval.start);
437 i < targetInterval.end; i++) {
438 _addView(i);
439 }
440
441 _activeInterval = targetInterval;
442 }
443
444 void _removeView(int index) {
445 // Do not remove placeholder views as they need to stay present in case
446 // they scroll out of view and then back into view.
447 if (!(_itemViews[index] is _PlaceholderView)) {
448 // Remove from the active DOM but don't destroy.
449 _itemViews[index].node.remove();
450 childViewRemoved(_itemViews[index]);
451 }
452 }
453
454 View _newView(int index) {
455 final view = _layout.newView(index);
456 view.node.attributes[INDEX_DATA_ATTRIBUTE] = index.toString();
457 return view;
458 }
459
460 View _addView(int index) {
461 if (_itemViews.containsKey(index)) {
462 final view = _itemViews[index];
463 _addViewHelper(view, index);
464 childViewAdded(view);
465 return view;
466 }
467
468 final view = _newView(index);
469 _itemViews[index] = view;
470 // TODO(jacobr): its ugly to put this here... but its needed
471 // as typical even-odd css queries won't work as we only display some
472 // children at a time.
473 if (index == 0) {
474 view.addClass('first-child');
475 }
476 _selectHelper(view, _data[index] == _lastSelectedItem);
477 // The order of the child elements doesn't matter as we use absolute
478 // positioning.
479 _addViewHelper(view, index);
480 childViewAdded(view);
481 return view;
482 }
483
484 void _addViewHelper(View view, int index) {
485 _positionSubview(view.node, index);
486 // The view might already be attached.
487 if (view.node.parent != _containerElem) {
488 _containerElem.nodes.add(view.node);
489 }
490 }
491
492 /**
493 * Detach a subview from the view replacing it with an empty placeholder view.
494 * The detatched subview can be safely reparented.
495 */
496 View detachSubview(D itemData) {
497 int index = findIndex(itemData);
498 View view = _itemViews[index];
499 if (view == null) {
500 // Edge case: add the view so we can detatch as the view is currently
501 // outside but might soon be inside the visible area.
502 assert(!_activeInterval.contains(index));
503 _addView(index);
504 view = _itemViews[index];
505 }
506 final placeholder = new _PlaceholderView();
507 view.node.replaceWith(placeholder.node);
508 _itemViews[index] = placeholder;
509 return view;
510 }
511
512 /**
513 * Reattach a subview from the view that was detached from the view
514 * by calling detachSubview. [callback] is called once the subview is
515 * reattached and done animating into position.
516 */
517 void reattachSubview(D data, View view, bool animate) {
518 int index = findIndex(data);
519 // TODO(jacobr): perform some validation that the view is
520 // really detached.
521 var currentPosition;
522 if (animate) {
523 currentPosition =
524 FxUtil.computeRelativePosition(view.node, _containerElem);
525 }
526 assert (_itemViews[index] is _PlaceholderView);
527 view.enterDocument();
528 _itemViews[index].node.replaceWith(view.node);
529 _itemViews[index] = view;
530 if (animate) {
531 FxUtil.setTranslate(view.node, currentPosition.x, currentPosition.y, 0);
532 // The view's position is unchanged except now re-parented to
533 // the list view.
534 window.setTimeout(() { _positionSubview(view.node, index); }, 0);
535 } else {
536 _positionSubview(view.node, index);
537 }
538 }
539
540 int findIndex(D targetItem) {
541 // TODO(jacobr): move this to a util library or modify this class so that
542 // the data is an List not a Collection.
543 int i = 0;
544 for (D item in _data) {
545 if (item == targetItem) {
546 return i;
547 }
548 i++;
549 }
550 return null;
551 }
552
553 void _positionSubview(Element node, int index) {
554 if (_vertical) {
555 FxUtil.setTranslate(node, 0, _layout.getOffset(index), 0);
556 } else {
557 FxUtil.setTranslate(node, _layout.getOffset(index), 0, 0);
558 }
559 node.style.zIndex = index.toString();
560 }
561
562 void _select(int index, bool selected) {
563 if (index != null) {
564 final subview = getSubview(index);
565 if (subview != null) {
566 _selectHelper(subview, selected);
567 }
568 }
569 }
570
571 void _selectHelper(View view, bool selected) {
572 if (selected) {
573 view.addClass('sel');
574 } else {
575 view.removeClass('sel');
576 }
577 }
578
579 View getSubview(int index) {
580 return _itemViews[index];
581 }
582
583 void showView(D targetItem) {
584 int index = findIndex(targetItem);
585 if (index != null) {
586 if (_layout.getOffset(index) < -_offset) {
587 _throwTo(_layout.getOffset(index));
588 } else if (_layout.getOffset(index + 1) > (-_offset + _viewLength)) {
589 // TODO(jacobr): for completeness we should check whether
590 // the current view is longer than _viewLength in which case
591 // there are some nasty edge cases.
592 _throwTo(_layout.getOffset(index + 1) - _viewLength);
593 }
594 }
595 }
596
597 void _throwTo(num offset) {
598 if (_vertical) {
599 scroller.throwTo(0, -offset);
600 } else {
601 scroller.throwTo(-offset, 0);
602 }
603 }
604 }
605
606 class FixedSizeListViewLayout<D> implements ListViewLayout<D> {
607 final ViewFactory<D> itemViewFactory;
608 final bool _vertical;
609 List<D> _data;
610 bool _paginate;
611
612 FixedSizeListViewLayout(this.itemViewFactory, this._data, this._vertical,
613 this._paginate);
614
615 void onDataChange() {}
616
617 View newView(int index) {
618 return itemViewFactory.newView(_data[index]);
619 }
620
621 int get _itemLength() {
622 return _vertical ? itemViewFactory.height : itemViewFactory.width;
623 }
624
625
626 int getWidth(int viewLength) {
627 return _vertical ? itemViewFactory.width : getLength(viewLength);
628 }
629
630 int getHeight(int viewLength) {
631 return _vertical ? getLength(viewLength) : itemViewFactory.height;
632 }
633
634 int getEstimatedHeight(int viewLength) {
635 // Returns the exact height as it is trivial to compute for this layout.
636 return getHeight(viewLength);
637 }
638
639 int getEstimatedWidth(int viewLength) {
640 // Returns the exact height as it is trivial to compute for this layout.
641 return getWidth(viewLength);
642 }
643
644 int getEstimatedLength(int viewLength) {
645 // Returns the exact length as it is trivial to compute for this layout.
646 return getLength(viewLength);
647 }
648
649 int getLength(int viewLength) {
650 int itemLength =
651 _vertical ? itemViewFactory.height : itemViewFactory.width;
652 if (viewLength == null || viewLength == 0) {
653 return itemLength * _data.length;
654 } else if (_paginate) {
655 if (_data.length > 0) {
656 final pageLength = getPageLength(viewLength);
657 return getPage(_data.length - 1, viewLength)
658 * pageLength + Math.max(viewLength, pageLength);
659 } else {
660 return 0;
661 }
662 } else {
663 return itemLength * (_data.length - 1) + Math.max(viewLength, itemLength);
664 }
665 }
666
667 int getOffset(int index) {
668 return index * _itemLength;
669 }
670
671 int getPageLength(int viewLength) {
672 final itemsPerPage = (viewLength / _itemLength).floor();
673 return (Math.max(1, itemsPerPage) * _itemLength).toInt();
674 }
675
676 int getPage(int index, int viewLength) {
677 return (getOffset(index) / getPageLength(viewLength)).floor().toInt();
678 }
679
680 int getPageStartIndex(int page, int viewLength) {
681 return (getPageLength(viewLength) / _itemLength).toInt() * page;
682 }
683
684 int getSnapIndex(num offset, int viewLength) {
685 int index = (-offset / _itemLength).round().toInt();
686 if (_paginate) {
687 index = getPageStartIndex(getPage(index, viewLength), viewLength);
688 }
689 return GoogleMath.clamp(index, 0, _data.length - 1);
690 }
691
692 Interval computeVisibleInterval(
693 num offset, num viewLength, num bufferLength) {
694 num targetIntervalStart =
695 Math.max(0,((-offset - bufferLength) / _itemLength).floor());
696 num targetIntervalEnd = GoogleMath.clamp(
697 ((-offset + viewLength + bufferLength) / _itemLength).ceil(),
698 targetIntervalStart,
699 _data.length);
700 return new Interval(targetIntervalStart.toInt(),
701 targetIntervalEnd.toInt());
702 }
703 }
704
705 /**
706 * Simple list view class where each item has fixed width and height.
707 */
708 class ListView<D> extends GenericListView<D> {
709
710 /**
711 * Creates a new ListView for the given data. If [:_data:] is an
712 * [:ObservableList<T>:] then it will listen to changes to the list and
713 * update the view appropriately.
714 */
715 ListView(List<D> data, ViewFactory<D> itemViewFactory, bool scrollable,
716 bool vertical, ObservableValue<D> selectedItem,
717 [bool snapToItems = false,
718 bool paginate = false,
719 bool removeClippedViews = false,
720 bool showScrollbar = false,
721 PageState pages = null])
722 : super(new FixedSizeListViewLayout<D>(itemViewFactory, data, vertical,
723 paginate),
724 data, scrollable, vertical, selectedItem, snapToItems, paginate,
725 removeClippedViews, showScrollbar, pages);
726 }
727
728 /**
729 * Layout where each item may have variable size along the axis the list view
730 * extends.
731 */
732 class VariableSizeListViewLayout<D> implements ListViewLayout<D> {
733 List<D> _data;
734 List<int> _itemOffsets;
735 List<int> _lengths;
736 int _lastOffset = 0;
737 bool _vertical;
738 bool _paginate;
739 VariableSizeViewFactory<D> itemViewFactory;
740 Interval _lastVisibleInterval;
741
742 VariableSizeListViewLayout(this.itemViewFactory, data, this._vertical,
743 this._paginate) :
744 _data = data,
745 _lastVisibleInterval = new Interval(0, 0) {
746 _itemOffsets = <int>[];
747 _lengths = <int>[];
748 _itemOffsets.add(0);
749 }
750
751 void onDataChange() {
752 _itemOffsets.clear();
753 _itemOffsets.add(0);
754 _lengths.clear();
755 }
756
757 View newView(int index) => itemViewFactory.newView(_data[index]);
758
759 int getWidth(int viewLength) {
760 if (_vertical) {
761 return itemViewFactory.getWidth(null);
762 } else {
763 return getLength(viewLength);
764 }
765 }
766
767 int getHeight(int viewLength) {
768 if (_vertical) {
769 return getLength(viewLength);
770 } else {
771 return itemViewFactory.getHeight(null);
772 }
773 }
774
775 int getEstimatedHeight(int viewLength) {
776 if (_vertical) {
777 return getEstimatedLength(viewLength);
778 } else {
779 return itemViewFactory.getHeight(null);
780 }
781 }
782
783 int getEstimatedWidth(int viewLength) {
784 if (_vertical) {
785 return itemViewFactory.getWidth(null);
786 } else {
787 return getEstimatedLength(viewLength);
788 }
789 }
790
791 // TODO(jacobr): this logic is overly complicated. Replace with something
792 // simpler.
793 int getEstimatedLength(int viewLength) {
794 if (_lengths.length == _data.length) {
795 // No need to estimate... we have all the data already.
796 return getLength(viewLength);
797 }
798 if (_itemOffsets.length > 1 && _lengths.length > 0) {
799 // Estimate length by taking the average of the lengths
800 // of the known views.
801 num lengthFromAllButLastElement = 0;
802 if (_itemOffsets.length > 2) {
803 lengthFromAllButLastElement =
804 (getOffset(_itemOffsets.length - 2) -
805 getOffset(0)) *
806 (_data.length / (_itemOffsets.length - 2));
807 }
808 return (lengthFromAllButLastElement +
809 Math.max(viewLength, _lengths[_lengths.length - 1])).toInt();
810 } else {
811 if (_lengths.length == 1) {
812 return Math.max(viewLength, _lengths[0]);
813 } else {
814 return viewLength;
815 }
816 }
817 }
818
819 int getLength(int viewLength) {
820 if (_data.length == 0) {
821 return viewLength;
822 } else {
823 // Hack so that _lengths[length - 1] is available.
824 getOffset(_data.length);
825 return (getOffset(_data.length - 1) - getOffset(0)) +
826 Math.max(_lengths[_lengths.length - 1], viewLength);
827 }
828 }
829
830 int getOffset(int index) {
831 if (index >= _itemOffsets.length) {
832 int offset = _itemOffsets[_itemOffsets.length - 1];
833 for (int i = _itemOffsets.length; i <= index; i++) {
834 int length = _vertical ? itemViewFactory.getHeight(_data[i - 1])
835 : itemViewFactory.getWidth(_data[i - 1]);
836 offset += length;
837 _itemOffsets.add(offset);
838 _lengths.add(length);
839 }
840 }
841 return _itemOffsets[index];
842 }
843
844 int getPage(int index, int viewLength) {
845 // TODO(jacobr): implement.
846 throw 'Not implemented';
847 }
848
849 int getPageStartIndex(int page, int viewLength) {
850 // TODO(jacobr): implement.
851 throw 'Not implemented';
852 }
853
854 int getSnapIndex(num offset, int viewLength) {
855 for (int i = 1; i < _data.length; i++) {
856 if (getOffset(i) + getOffset(i - 1) > -offset * 2) {
857 return i - 1;
858 }
859 }
860 return _data.length - 1;
861 }
862
863 Interval computeVisibleInterval(
864 num offset, num viewLength, num bufferLength) {
865 offset = offset.toInt();
866 int start = _findFirstItemBefore(
867 -offset - bufferLength,
868 _lastVisibleInterval != null ? _lastVisibleInterval.start : 0);
869 int end = _findFirstItemAfter(
870 -offset + viewLength + bufferLength,
871 _lastVisibleInterval != null ? _lastVisibleInterval.end : 0);
872 _lastVisibleInterval = new Interval(start, Math.max(start, end));
873 _lastOffset = offset;
874 return _lastVisibleInterval;
875 }
876
877 int _findFirstItemAfter(num target, int hint) {
878 for (int i = 0; i < _data.length; i++) {
879 if (getOffset(i) > target) {
880 return i;
881 }
882 }
883 return _data.length;
884 }
885
886 // TODO(jacobr): use hint.
887 int _findFirstItemBefore(num target, int hint) {
888 // We go search this direction delaying computing the actual view size
889 // as long as possible.
890 for (int i = 1; i < _data.length; i++) {
891 if (getOffset(i) >= target) {
892 return i - 1;
893 }
894 }
895 return Math.max(_data.length - 1, 0);
896 }
897 }
898
899 class VariableSizeListView<D> extends GenericListView<D> {
900
901 VariableSizeListView(List<D> data,
902 VariableSizeViewFactory<D> itemViewFactory,
903 bool scrollable,
904 bool vertical,
905 ObservableValue<D> selectedItem,
906 [bool snapToItems = false,
907 bool paginate = false,
908 bool removeClippedViews = false,
909 bool showScrollbar = false,
910 PageState pages = null])
911 : super(new VariableSizeListViewLayout(itemViewFactory, data, vertical,
912 paginate),
913 data, scrollable, vertical, selectedItem, snapToItems,
914 paginate, removeClippedViews, showScrollbar, pages);
915 }
916
917 /** A back button that is equivalent to clicking "back" in the browser. */
918 class BackButton extends View {
919 BackButton() : super();
920
921 Element render() => new Element.html('<div class="back-arrow button"></div>');
922
923 void afterRender(Element node) {
924 addOnClick((e) => window.history.back());
925 }
926 }
927
928
929 // TODO(terry): Maybe should be part of ButtonView class in appstack/view?
930 /** OS button. */
931 class PushButtonView extends View {
932 final String _text;
933 final String _cssClass;
934 final _clickHandler;
935
936 PushButtonView(this._text, this._cssClass, this._clickHandler) : super();
937
938 Element render() {
939 return new Element.html('<button class="${_cssClass}">${_text}</button>');
940 }
941
942 void afterRender(Element node) {
943 addOnClick(_clickHandler);
944 }
945 }
946
947
948 // TODO(terry): Add a drop shadow around edge and corners need to be rounded.
949 // Need to support conveyor for contents of dialog so it's not
950 // larger than the parent window.
951 /** A generic dialog view supports title, done button and dialog content. */
952 class DialogView extends View {
953 final String _title;
954 final String _cssName;
955 final View _content;
956 Element container;
957 PushButtonView _done;
958
959 DialogView(this._title, this._cssName, this._content) : super() {}
960
961 Element render() {
962 final node = new Element.html('''
963 <div class="dialog-modal">
964 <div class="dialog $_cssName">
965 <div class="dialog-title-area">
966 <span class="dialog-title">$_title</span>
967 </div>
968 <div class="dialog-body"></div>
969 </div>
970 </div>''');
971
972 _done = new PushButtonView('Done', 'done-button',
973 EventBatch.wrap((e) => onDone()));
974 final titleArea = node.query('.dialog-title-area');
975 titleArea.nodes.add(_done.node);
976
977 container = node.query('.dialog-body');
978 container.nodes.add(_content.node);
979
980 return node;
981 }
982
983 /** Override to handle dialog done. */
984 void onDone() { }
985 }
OLDNEW
« no previous file with comments | « client/samples/swarm/UIState.dart ('k') | client/samples/swarm/appengine/app.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698