| 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 |