OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 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 | 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 part of view; | 5 part of view; |
6 | 6 |
7 class PageState { | 7 class PageState { |
8 final ObservableValue<int> current; | 8 final ObservableValue<int> current; |
9 final ObservableValue<int> target; | 9 final ObservableValue<int> target; |
10 final ObservableValue<int> length; | 10 final ObservableValue<int> length; |
11 PageState() : | 11 PageState() |
12 current = new ObservableValue<int>(0), | 12 : current = new ObservableValue<int>(0), |
13 target = new ObservableValue<int>(0), | 13 target = new ObservableValue<int>(0), |
14 length = new ObservableValue<int>(1); | 14 length = new ObservableValue<int>(1); |
15 } | 15 } |
16 | 16 |
17 /** Simplifies using a PageNumberView and PagedColumnView together. */ | 17 /** Simplifies using a PageNumberView and PagedColumnView together. */ |
18 class PagedContentView extends CompositeView { | 18 class PagedContentView extends CompositeView { |
19 final View content; | 19 final View content; |
20 final PageState pages; | 20 final PageState pages; |
21 | 21 |
22 PagedContentView(this.content) | 22 PagedContentView(this.content) |
23 : super('paged-content'), | 23 : super('paged-content'), |
24 pages = new PageState() { | 24 pages = new PageState() { |
25 addChild(new PagedColumnView(pages, content)); | 25 addChild(new PagedColumnView(pages, content)); |
26 addChild(new PageNumberView(pages)); | 26 addChild(new PageNumberView(pages)); |
27 } | 27 } |
28 } | 28 } |
29 | 29 |
30 /** Displays current page and a left/right arrow. Used with [PagedColumnView] */ | 30 /** Displays current page and a left/right arrow. Used with [PagedColumnView] */ |
31 class PageNumberView extends View { | 31 class PageNumberView extends View { |
32 final PageState pages; | 32 final PageState pages; |
33 Element _label; | 33 Element _label; |
34 Element _left, _right; | 34 Element _left, _right; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
78 * A horizontal scrolling view that snaps to items like [ConveyorView], but only | 78 * A horizontal scrolling view that snaps to items like [ConveyorView], but only |
79 * has one child. Instead of scrolling between views, it scrolls between content | 79 * has one child. Instead of scrolling between views, it scrolls between content |
80 * that flows horizontally in columns. Supports left/right swipe to switch | 80 * that flows horizontally in columns. Supports left/right swipe to switch |
81 * between pages. Can also be used with [PageNumberView]. | 81 * between pages. Can also be used with [PageNumberView]. |
82 * | 82 * |
83 * This control assumes that it is styled with fixed or percent width and | 83 * This control assumes that it is styled with fixed or percent width and |
84 * height, so the content will flow out horizontally. This allows it to compute | 84 * height, so the content will flow out horizontally. This allows it to compute |
85 * the number of pages using [:scrollWidth:] and [:offsetWidth:]. | 85 * the number of pages using [:scrollWidth:] and [:offsetWidth:]. |
86 */ | 86 */ |
87 class PagedColumnView extends View { | 87 class PagedColumnView extends View { |
88 | |
89 static const MIN_THROW_PAGE_FRACTION = 0.01; | 88 static const MIN_THROW_PAGE_FRACTION = 0.01; |
90 final View contentView; | 89 final View contentView; |
91 | 90 |
92 final PageState pages; | 91 final PageState pages; |
93 | 92 |
94 Element _container; | 93 Element _container; |
95 int _columnGap, _columnWidth; | 94 int _columnGap, _columnWidth; |
96 int _viewportSize; | 95 int _viewportSize; |
97 Scroller scroller; | 96 Scroller scroller; |
98 | 97 |
99 PagedColumnView(this.pages, this.contentView) : super(); | 98 PagedColumnView(this.pages, this.contentView) : super(); |
100 | 99 |
101 Element render() { | 100 Element render() { |
102 final node = new Element.html(''' | 101 final node = new Element.html(''' |
103 <div class="paged-column"> | 102 <div class="paged-column"> |
104 <div class="paged-column-container"></div> | 103 <div class="paged-column-container"></div> |
105 </div>'''); | 104 </div>'''); |
106 _container = node.querySelector('.paged-column-container'); | 105 _container = node.querySelector('.paged-column-container'); |
107 _container.nodes.add(contentView.node); | 106 _container.nodes.add(contentView.node); |
108 | 107 |
109 // TODO(jmesserly): if we end up with empty columns on the last page, | 108 // TODO(jmesserly): if we end up with empty columns on the last page, |
110 // this causes the last page to end up right justified. But it seems to | 109 // this causes the last page to end up right justified. But it seems to |
111 // work reasonably well for both clicking and throwing. So for now, leave | 110 // work reasonably well for both clicking and throwing. So for now, leave |
112 // the scroller configured the default way. | 111 // the scroller configured the default way. |
113 | 112 |
114 // TODO(jacobr): use named arguments when available. | 113 // TODO(jacobr): use named arguments when available. |
115 scroller = new Scroller( | 114 scroller = new Scroller(_container, false /* verticalScrollEnabled */, |
116 _container, | 115 true /* horizontalScrollEnabled */, true /* momementumEnabled */, () { |
117 false /* verticalScrollEnabled */, | 116 return new Size(_getViewLength(_container), 1); |
118 true /* horizontalScrollEnabled */, | 117 }, Scroller.FAST_SNAP_DECELERATION_FACTOR); |
119 true /* momementumEnabled */, | |
120 () { | |
121 return new Size(_getViewLength(_container), 1); | |
122 }, | |
123 Scroller.FAST_SNAP_DECELERATION_FACTOR); | |
124 | 118 |
125 scroller.onDecelStart.listen(_snapToPage); | 119 scroller.onDecelStart.listen(_snapToPage); |
126 scroller.onScrollerDragEnd.listen(_snapToPage); | 120 scroller.onScrollerDragEnd.listen(_snapToPage); |
127 scroller.onContentMoved.listen(_onContentMoved); | 121 scroller.onContentMoved.listen(_onContentMoved); |
128 return node; | 122 return node; |
129 } | 123 } |
130 | 124 |
131 int _getViewLength(Element element) { | 125 int _getViewLength(Element element) { |
132 return _computePageSize(element) * pages.length.value; | 126 return _computePageSize(element) * pages.length.value; |
133 } | 127 } |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
186 scheduleMicrotask(() { | 180 scheduleMicrotask(() { |
187 contentView.node.style.height = '${node.offset.height}px'; | 181 contentView.node.style.height = '${node.offset.height}px'; |
188 }); | 182 }); |
189 _updatePageCount(null); | 183 _updatePageCount(null); |
190 } | 184 } |
191 | 185 |
192 bool _updatePageCount(Callback callback) { | 186 bool _updatePageCount(Callback callback) { |
193 int pageLength = 1; | 187 int pageLength = 1; |
194 scheduleMicrotask(() { | 188 scheduleMicrotask(() { |
195 if (_container.scrollWidth > _container.offset.width) { | 189 if (_container.scrollWidth > _container.offset.width) { |
196 pageLength = (_container.scrollWidth / _computePageSize(_container)) | 190 pageLength = |
197 .ceil(); | 191 (_container.scrollWidth / _computePageSize(_container)).ceil(); |
198 } | 192 } |
199 pageLength = Math.max(pageLength, 1); | 193 pageLength = Math.max(pageLength, 1); |
200 | 194 |
201 int oldPage = pages.target.value; | 195 int oldPage = pages.target.value; |
202 int newPage = Math.min(oldPage, pageLength - 1); | 196 int newPage = Math.min(oldPage, pageLength - 1); |
203 | 197 |
204 // Hacky: make sure a change event always fires. | 198 // Hacky: make sure a change event always fires. |
205 // This is so we adjust the 3d transform after resize. | 199 // This is so we adjust the 3d transform after resize. |
206 if (oldPage == newPage) { | 200 if (oldPage == newPage) { |
207 pages.target.value = 0; | 201 pages.target.value = 0; |
(...skipping 21 matching lines...) Expand all Loading... |
229 scheduleMicrotask(() { | 223 scheduleMicrotask(() { |
230 int pageSize = _computePageSize(_container); | 224 int pageSize = _computePageSize(_container); |
231 int destination; | 225 int destination; |
232 num currentPageNumber = -(current / pageSize).round(); | 226 num currentPageNumber = -(current / pageSize).round(); |
233 num pageNumber = -currentTarget / pageSize; | 227 num pageNumber = -currentTarget / pageSize; |
234 if (current == currentTarget) { | 228 if (current == currentTarget) { |
235 // User was just static dragging so round to the nearest page. | 229 // User was just static dragging so round to the nearest page. |
236 pageNumber = pageNumber.round(); | 230 pageNumber = pageNumber.round(); |
237 } else { | 231 } else { |
238 if (currentPageNumber == pageNumber.round() && | 232 if (currentPageNumber == pageNumber.round() && |
239 (pageNumber - currentPageNumber).abs() > MIN_THROW_PAGE_FRACTION && | 233 (pageNumber - currentPageNumber).abs() > MIN_THROW_PAGE_FRACTION && |
240 -current + _viewportSize < _getViewLength(_container) && | 234 -current + _viewportSize < _getViewLength(_container) && |
241 current < 0) { | 235 current < 0) { |
242 // The user is trying to throw so we want to round up to the | 236 // The user is trying to throw so we want to round up to the |
243 // nearest page in the direction they are throwing. | 237 // nearest page in the direction they are throwing. |
244 pageNumber = currentTarget < current | 238 pageNumber = currentTarget < current |
245 ? currentPageNumber + 1 : currentPageNumber - 1; | 239 ? currentPageNumber + 1 |
| 240 : currentPageNumber - 1; |
246 } else { | 241 } else { |
247 pageNumber = pageNumber.round(); | 242 pageNumber = pageNumber.round(); |
248 } | 243 } |
249 } | 244 } |
250 num translate = -pageNumber * pageSize; | 245 num translate = -pageNumber * pageSize; |
251 pages.current.value = pageNumber; | 246 pages.current.value = pageNumber; |
252 if (currentTarget != translate) { | 247 if (currentTarget != translate) { |
253 scroller.throwTo(translate, 0); | 248 scroller.throwTo(translate, 0); |
254 } else { | 249 } else { |
255 // Update the target page number when we are done animating. | 250 // Update the target page number when we are done animating. |
256 pages.target.value = pageNumber; | 251 pages.target.value = pageNumber; |
257 } | 252 } |
258 }); | 253 }); |
259 } | 254 } |
260 | 255 |
261 int _computePageSize(Element element) { | 256 int _computePageSize(Element element) { |
262 // Hacky: we need to duplicate the way the columns are being computed, | 257 // Hacky: we need to duplicate the way the columns are being computed, |
263 // including rounding, to figure out how far to translate the div. | 258 // including rounding, to figure out how far to translate the div. |
264 // See http://www.w3.org/TR/css3-multicol/#column-width | 259 // See http://www.w3.org/TR/css3-multicol/#column-width |
265 _viewportSize = element.offset.width; | 260 _viewportSize = element.offset.width; |
266 | 261 |
267 // Figure out how many columns we're rendering. | 262 // Figure out how many columns we're rendering. |
268 // The algorithm ensures we're bigger than the specified min size. | 263 // The algorithm ensures we're bigger than the specified min size. |
269 int perPage = Math.max(1, | 264 int perPage = Math.max( |
270 (_viewportSize + _columnGap) ~/ (_columnWidth + _columnGap)); | 265 1, (_viewportSize + _columnGap) ~/ (_columnWidth + _columnGap)); |
271 | 266 |
272 // Divide up the viewport between the columns. | 267 // Divide up the viewport between the columns. |
273 int columnSize = (_viewportSize - (perPage - 1) * _columnGap) ~/ perPage; | 268 int columnSize = (_viewportSize - (perPage - 1) * _columnGap) ~/ perPage; |
274 | 269 |
275 // Finally, compute how big each page is, and how far to translate. | 270 // Finally, compute how big each page is, and how far to translate. |
276 return perPage * (columnSize + _columnGap); | 271 return perPage * (columnSize + _columnGap); |
277 } | 272 } |
278 | 273 |
279 void _onPageSelected() { | 274 void _onPageSelected() { |
280 scheduleMicrotask(() { | 275 scheduleMicrotask(() { |
281 int translate = -pages.target.value * _computePageSize(_container); | 276 int translate = -pages.target.value * _computePageSize(_container); |
282 scroller.throwTo(translate, 0); | 277 scroller.throwTo(translate, 0); |
283 }); | 278 }); |
284 } | 279 } |
285 } | 280 } |
OLD | NEW |