| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:async'; | 5 import 'dart:async'; |
| 6 import 'dart:html'; | 6 import 'dart:html'; |
| 7 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; | 7 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 8 import 'package:observatory/src/elements/helpers/tag.dart'; | 8 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 9 | 9 |
| 10 typedef HtmlElement VirtualCollectionCreateCallback(); | 10 typedef HtmlElement VirtualCollectionCreateCallback(); |
| 11 typedef List<HtmlElement> VirtualCollectionHeaderCallback(); |
| 11 typedef void VirtualCollectionUpdateCallback( | 12 typedef void VirtualCollectionUpdateCallback( |
| 12 HtmlElement el, dynamic item, int index); | 13 HtmlElement el, dynamic item, int index); |
| 13 | 14 |
| 14 class VirtualCollectionElement extends HtmlElement implements Renderable { | 15 class VirtualCollectionElement extends HtmlElement implements Renderable { |
| 15 static const tag = const Tag<VirtualCollectionElement>('virtual-collection'); | 16 static const tag = const Tag<VirtualCollectionElement>('virtual-collection'); |
| 16 | 17 |
| 17 RenderingScheduler<VirtualCollectionElement> _r; | 18 RenderingScheduler<VirtualCollectionElement> _r; |
| 18 | 19 |
| 19 Stream<RenderedEvent<VirtualCollectionElement>> get onRendered => | 20 Stream<RenderedEvent<VirtualCollectionElement>> get onRendered => |
| 20 _r.onRendered; | 21 _r.onRendered; |
| 21 | 22 |
| 22 VirtualCollectionCreateCallback _create; | 23 VirtualCollectionCreateCallback _create; |
| 23 VirtualCollectionCreateCallback _createHeader; | 24 VirtualCollectionHeaderCallback _createHeader; |
| 24 VirtualCollectionUpdateCallback _update; | 25 VirtualCollectionUpdateCallback _update; |
| 25 double _itemHeight; | 26 double _itemHeight; |
| 26 double _headerHeight = 0.0; | 27 double _headerHeight = 0.0; |
| 27 int _top; | 28 int _top; |
| 28 double _height; | 29 double _height; |
| 29 List _items; | 30 List _items; |
| 30 StreamSubscription _onScrollSubscription; | 31 StreamSubscription _onScrollSubscription; |
| 31 StreamSubscription _onResizeSubscription; | 32 StreamSubscription _onResizeSubscription; |
| 32 | 33 |
| 33 List get items => _items; | 34 List get items => _items; |
| 34 | 35 |
| 35 set items(Iterable value) { | 36 set items(Iterable value) { |
| 36 _items = new List.unmodifiable(value); | 37 _items = new List.unmodifiable(value); |
| 37 _top = null; | 38 _top = null; |
| 38 _r.dirty(); | 39 _r.dirty(); |
| 39 } | 40 } |
| 40 | 41 |
| 41 factory VirtualCollectionElement(VirtualCollectionCreateCallback create, | 42 factory VirtualCollectionElement(VirtualCollectionCreateCallback create, |
| 42 VirtualCollectionUpdateCallback update, | 43 VirtualCollectionUpdateCallback update, |
| 43 {Iterable items: const [], | 44 {Iterable items: const [], |
| 44 VirtualCollectionCreateCallback createHeader, | 45 VirtualCollectionHeaderCallback createHeader, |
| 45 RenderingQueue queue}) { | 46 RenderingQueue queue}) { |
| 46 assert(create != null); | 47 assert(create != null); |
| 47 assert(update != null); | 48 assert(update != null); |
| 48 assert(items != null); | 49 assert(items != null); |
| 49 VirtualCollectionElement e = document.createElement(tag.name); | 50 VirtualCollectionElement e = document.createElement(tag.name); |
| 50 e._r = new RenderingScheduler(e, queue: queue); | 51 e._r = new RenderingScheduler(e, queue: queue); |
| 51 e._create = create; | 52 e._create = create; |
| 52 e._createHeader = createHeader; | 53 e._createHeader = createHeader; |
| 53 e._update = update; | 54 e._update = update; |
| 54 e._items = new List.unmodifiable(items); | 55 e._items = new List.unmodifiable(items); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 72 super.detached(); | 73 super.detached(); |
| 73 _r.disable(notify: true); | 74 _r.disable(notify: true); |
| 74 children = const []; | 75 children = const []; |
| 75 _onScrollSubscription.cancel(); | 76 _onScrollSubscription.cancel(); |
| 76 _onResizeSubscription.cancel(); | 77 _onResizeSubscription.cancel(); |
| 77 } | 78 } |
| 78 | 79 |
| 79 final DivElement _header = new DivElement()..classes = ['header']; | 80 final DivElement _header = new DivElement()..classes = ['header']; |
| 80 final DivElement _scroller = new DivElement()..classes = ['scroller']; | 81 final DivElement _scroller = new DivElement()..classes = ['scroller']; |
| 81 final DivElement _shifter = new DivElement()..classes = ['shifter']; | 82 final DivElement _shifter = new DivElement()..classes = ['shifter']; |
| 83 final DivElement _container = new DivElement()..classes = ['container']; |
| 82 | 84 |
| 83 dynamic getItemFromElement(HtmlElement element) { | 85 dynamic getItemFromElement(HtmlElement element) { |
| 84 final el_index = _shifter.children.indexOf(element); | 86 final el_index = _container.children.indexOf(element); |
| 85 if (el_index < 0) { | 87 if (el_index < 0) { |
| 86 return null; | 88 return null; |
| 87 } | 89 } |
| 88 final item_index = | 90 final item_index = _top + |
| 89 _top + el_index - (_shifter.children.length * _inverse_preload).floor(); | 91 el_index - |
| 92 (_container.children.length * _inverse_preload).floor(); |
| 90 if (0 <= item_index && item_index < items.length) { | 93 if (0 <= item_index && item_index < items.length) { |
| 91 return _items[item_index]; | 94 return _items[item_index]; |
| 92 } | 95 } |
| 93 return null; | 96 return null; |
| 94 } | 97 } |
| 95 | 98 |
| 96 /// The preloaded element before and after the visible area are: | 99 /// The preloaded element before and after the visible area are: |
| 97 /// 1/preload_size of the number of items in the visble area. | 100 /// 1/preload_size of the number of items in the visble area. |
| 98 /// See shared.css for the "top:-25%;". | 101 /// See shared.css for the "top:-25%;". |
| 99 static const int _preload = 2; | 102 static const int _preload = 2; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 112 void takeIntoView(item) { | 115 void takeIntoView(item) { |
| 113 _takeIntoView = item; | 116 _takeIntoView = item; |
| 114 _r.dirty(); | 117 _r.dirty(); |
| 115 } | 118 } |
| 116 | 119 |
| 117 void render() { | 120 void render() { |
| 118 if (children.isEmpty) { | 121 if (children.isEmpty) { |
| 119 children = [ | 122 children = [ |
| 120 _scroller | 123 _scroller |
| 121 ..children = [ | 124 ..children = [ |
| 122 _shifter..children = [_create()] | 125 _shifter |
| 126 ..children = [ |
| 127 _container..children = [_create()] |
| 128 ] |
| 123 ], | 129 ], |
| 124 ]; | 130 ]; |
| 125 if (_createHeader != null) { | 131 if (_createHeader != null) { |
| 126 _header.children = [_createHeader()]; | 132 _header.children = [ |
| 133 new DivElement() |
| 134 ..classes = ['container'] |
| 135 ..children = _createHeader() |
| 136 ]; |
| 127 _scroller.children.insert(0, _header); | 137 _scroller.children.insert(0, _header); |
| 128 _headerHeight = _header.children[0].getBoundingClientRect().height; | 138 _headerHeight = _header.children[0].getBoundingClientRect().height; |
| 129 } | 139 } |
| 130 _itemHeight = _shifter.children[0].getBoundingClientRect().height; | 140 _itemHeight = _container.children[0].getBoundingClientRect().height; |
| 131 _height = getBoundingClientRect().height; | 141 _height = getBoundingClientRect().height; |
| 132 } | 142 } |
| 133 | 143 |
| 134 if (_takeIntoView != null) { | 144 if (_takeIntoView != null) { |
| 135 final index = items.indexOf(_takeIntoView); | 145 final index = items.indexOf(_takeIntoView); |
| 136 if (index >= 0) { | 146 if (index >= 0) { |
| 137 final minScrollTop = _itemHeight * (index + 1) - _height; | 147 final minScrollTop = _itemHeight * (index + 1) - _height; |
| 138 final maxScrollTop = _itemHeight * index; | 148 final maxScrollTop = _itemHeight * index; |
| 139 scrollTop = ((maxScrollTop - minScrollTop) / 2 + minScrollTop).floor(); | 149 scrollTop = ((maxScrollTop - minScrollTop) / 2 + minScrollTop).floor(); |
| 140 } | 150 } |
| 141 _takeIntoView = null; | 151 _takeIntoView = null; |
| 142 } | 152 } |
| 143 | 153 |
| 144 final top = (scrollTop / _itemHeight).floor(); | 154 final top = (scrollTop / _itemHeight).floor(); |
| 145 | 155 |
| 146 _header.style.top = '${scrollTop}px'; | 156 _header.style.top = '${scrollTop}px'; |
| 147 _scroller.style.height = '${_itemHeight*(_items.length)+_headerHeight}px'; | 157 _scroller.style.height = '${_itemHeight*(_items.length)+_headerHeight}px'; |
| 148 final tail_length = (_height / _itemHeight / _preload).ceil(); | 158 final tail_length = (_height / _itemHeight / _preload).ceil(); |
| 149 final length = tail_length * 2 + tail_length * _preload; | 159 final length = tail_length * 2 + tail_length * _preload; |
| 150 | 160 |
| 151 if (_shifter.children.length < length) { | 161 if (_container.children.length < length) { |
| 152 while (_shifter.children.length != length) { | 162 while (_container.children.length != length) { |
| 153 var e = _create(); | 163 var e = _create(); |
| 154 e..style.display = 'hidden'; | 164 e..style.display = 'hidden'; |
| 155 _shifter.children.add(e); | 165 _container.children.add(e); |
| 156 } | 166 } |
| 157 _top = null; // force update; | 167 _top = null; // force update; |
| 158 } | 168 } |
| 159 | 169 |
| 160 if ((_top == null) || ((top - _top).abs() >= tail_length)) { | 170 if ((_top == null) || ((top - _top).abs() >= tail_length)) { |
| 161 _shifter.style.top = '${_itemHeight*(top-tail_length)}px'; | 171 _shifter.style.top = '${_itemHeight*(top-tail_length)}px'; |
| 162 int i = top - tail_length; | 172 int i = top - tail_length; |
| 163 for (final HtmlElement e in _shifter.children) { | 173 for (final HtmlElement e in _container.children) { |
| 164 if (0 <= i && i < _items.length) { | 174 if (0 <= i && i < _items.length) { |
| 165 e..style.display = null; | 175 e..style.display = null; |
| 166 _update(e, _items[i], i); | 176 _update(e, _items[i], i); |
| 167 } else { | 177 } else { |
| 168 e.style.display = 'hidden'; | 178 e.style.display = 'hidden'; |
| 169 } | 179 } |
| 170 i++; | 180 i++; |
| 171 } | 181 } |
| 172 _top = top; | 182 _top = top; |
| 173 } | 183 } |
| 174 } | 184 } |
| 175 | 185 |
| 176 void _onScroll(_) { | 186 void _onScroll(_) { |
| 177 _r.dirty(); | 187 _r.dirty(); |
| 178 } | 188 } |
| 179 | 189 |
| 180 void _onResize(_) { | 190 void _onResize(_) { |
| 181 final newHeight = getBoundingClientRect().height; | 191 final newHeight = getBoundingClientRect().height; |
| 182 if (newHeight > _height) { | 192 if (newHeight > _height) { |
| 183 _height = newHeight; | 193 _height = newHeight; |
| 184 _r.dirty(); | 194 _r.dirty(); |
| 185 } | 195 } |
| 186 } | 196 } |
| 187 } | 197 } |
| OLD | NEW |