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

Side by Side Diff: client/touch/Scrollbar.dart

Issue 9382027: Move client/{base, observable, layout, touch, util, view} to samples/ui_lib . (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/touch/ScrollWatcher.dart ('k') | client/touch/Scroller.dart » ('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 /**
6 * Implementation of a scrollbar for the custom scrolling behavior
7 * defined in [:Scroller:].
8 */
9 class Scrollbar implements ScrollListener {
10 /**
11 * The minimum size of scrollbars when not compressed.
12 */
13 static final _MIN_SIZE = 30;
14
15 /**
16 * The minimum compressed size of scrollbars. Scrollbars are compressed when
17 * the content is stretching past its boundaries.
18 */
19 static final _MIN_COMPRESSED_SIZE = 8;
20 /** Padding in pixels to add above and bellow the scrollbar. */
21 static final _PADDING_LENGTH = 10;
22 /**
23 * The amount of time to wait before hiding scrollbars after showing them.
24 * Measured in ms.
25 */
26 static final _DISPLAY_TIME = 300;
27 static final DRAG_CLASS_NAME = 'drag';
28
29 Scroller _scroller;
30 Element _frame;
31 bool _scrollInProgress = false;
32 bool _scrollBarDragInProgressValue = false;
33
34 /**
35 * Cached values of height and width. Keys will be 'height' and 'width'
36 * depending on if they are applied to vertical or horizontal scrollbar.
37 */
38 Map<String, num> _cachedSize;
39
40 /**
41 * This bound function will be used as the input to window.setTimeout when
42 * scheduling the hiding of the scrollbars.
43 */
44 Function _boundHideFn;
45
46 Element _verticalElement;
47 Element _horizontalElement;
48
49 int _currentScrollStartMouse;
50 num _currentScrollStartOffset;
51 bool _currentScrollVertical;
52 num _currentScrollRatio;
53 num _timerId;
54
55 bool _displayOnHover;
56 bool _hovering = false;
57
58 Scrollbar(Scroller scroller, [displayOnHover = true]) :
59 _displayOnHover = displayOnHover,
60 _scroller = scroller,
61 _frame = scroller.getFrame(),
62 _cachedSize = new Map<String, num>() {
63 _boundHideFn = () { _showScrollbars(false); };
64 }
65
66 bool get _scrollBarDragInProgress() => _scrollBarDragInProgressValue;
67
68 void set _scrollBarDragInProgress(bool value) {
69 _scrollBarDragInProgressValue = value;
70 _toggleClass(_verticalElement, DRAG_CLASS_NAME,
71 value && _currentScrollVertical);
72 _toggleClass(_horizontalElement, DRAG_CLASS_NAME,
73 value && !_currentScrollVertical);
74 }
75
76 // TODO(jacobr): move this helper method into the DOM.
77 void _toggleClass(Element e, String className, bool enabled) {
78 if (enabled) {
79 if (!e.classes.contains(className)) {
80 e.classes.add(className);
81 }
82 } else {
83 e.classes.remove(className);
84 }
85 }
86
87 /**
88 * Initializes elements and event handlers. Must be called after
89 * construction and before usage.
90 */
91 void initialize() {
92 // Don't initialize if we have already been initialized.
93 // TODO(jacobr): remove this once bugs are fixed and enterDocument is only
94 // called once by each view.
95 if (_verticalElement != null) {
96 return;
97 }
98 _verticalElement = new Element.html(
99 '<div class="touch-scrollbar touch-scrollbar-vertical"></div>');
100 _horizontalElement = new Element.html(
101 '<div class="touch-scrollbar touch-scrollbar-horizontal"></div>');
102 _scroller.addScrollListener(this);
103
104 Element scrollerEl = _scroller.getElement();
105
106 if (!Device.supportsTouch) {
107 _addEventListeners(
108 _verticalElement, _onStart, _onMove, _onEnd, _onEnd, true);
109 _addEventListeners(
110 _horizontalElement, _onStart, _onMove, _onEnd, _onEnd, true);
111 }
112
113 _scroller.addScrollListener(this);
114 _showScrollbars(false);
115 _scroller.onScrollerStart.add(_onScrollerStart);
116 _scroller.onScrollerEnd.add(_onScrollerEnd);
117 if (_displayOnHover) {
118 // TODO(jacobr): rather than adding all these event listeners we could
119 // instead attach a single global event listener and let data in the
120 // DOM drive.
121 _frame.on.click.add((Event e) {
122 // Always focus on click as one of our children isn't all focused.
123 if (!_frame.contains(document.activeElement)) {
124 scrollerEl.focus();
125 }
126 }, false);
127 _frame.on.mouseOver.add((Event e) {
128 final activeElement = document.activeElement;
129 // TODO(jacobr): don't steal focus from a child element or a truly
130 // focusable element. Only support stealing focus ffrom another
131 // element that was given fake focus.
132 if (activeElement is BodyElement ||
133 (!_frame.contains(activeElement) &&
134 activeElement is DivElement)) {
135 scrollerEl.focus();
136 }
137 if (_hovering == false) {
138 _hovering = true;
139 _cancelTimeout();
140 _showScrollbars(true);
141 refresh();
142 }
143 }, false);
144 _frame.on.mouseOut.add((e) {
145 _hovering = false;
146 // Start hiding immediately if we aren't
147 // scrolling or already in the process of
148 // hidng the scrollbar
149 if (!_scrollInProgress && _timerId == null) {
150 _boundHideFn();
151 }
152 }, false);
153 }
154 }
155
156 void _onStart(UIEvent e) {
157 Element elementOver = e.target;
158 if (elementOver == _verticalElement ||
159 elementOver == _horizontalElement) {
160 _currentScrollVertical = elementOver == _verticalElement;
161 if (_currentScrollVertical) {
162 _currentScrollStartMouse = e.pageY;
163 _currentScrollStartOffset = _scroller.getVerticalOffset();
164 } else {
165 _currentScrollStartMouse = e.pageX;
166 _currentScrollStartOffset = _scroller.getHorizontalOffset();
167 }
168 _refreshScrollRatio();
169 _scrollBarDragInProgress = true;
170 _scroller._momentum.abort();
171 e.stopPropagation();
172 }
173 }
174
175 void _refreshScrollRatio() {
176 Size contentSize = _scroller._getAdjustedContentSize();
177 if (_currentScrollVertical) {
178 _refreshScrollRatioHelper(
179 _scroller._scrollSize.height, contentSize.height);
180 } else {
181 _refreshScrollRatioHelper(_scroller._scrollSize.width,
182 contentSize.width);
183 }
184 }
185
186
187 void _refreshScrollRatioHelper(num frameSize, num contentSize) {
188 num frameTravelDistance = frameSize - _defaultScrollSize(
189 frameSize, contentSize) -_PADDING_LENGTH * 2;
190 if (frameTravelDistance < 0.001) {
191 _currentScrollRatio = 0;
192 } else {
193 _currentScrollRatio = (contentSize - frameSize) / frameTravelDistance;
194 }
195 }
196
197 void _onMove(UIEvent e) {
198 if (!_scrollBarDragInProgress) {
199 return;
200 }
201 _refreshScrollRatio();
202 int coordinate = _currentScrollVertical ? e.pageY : e.pageX;
203 num delta = (coordinate - _currentScrollStartMouse) * _currentScrollRatio;
204 if (delta != 0) {
205 num x;
206 num y;
207 _currentScrollStartOffset -= delta;
208 if (_currentScrollVertical) {
209 x = _scroller.getHorizontalOffset();
210 y = _currentScrollStartOffset.toInt();
211 } else {
212 x = _currentScrollStartOffset.toInt();
213 y = _scroller.getVerticalOffset();
214 }
215 _scroller.setPosition(x, y);
216 }
217 _currentScrollStartMouse = coordinate;
218 }
219
220 void _onEnd(UIEvent e) {
221 _scrollBarDragInProgress = false;
222 // TODO(jacobr): make scrollbar less tightly coupled to the scroller.
223 _scroller.onScrollerDragEnd.dispatch(
224 new Event(ScrollerEventType.DRAG_END));
225 }
226
227
228 /**
229 * When scrolling ends, schedule a timeout to hide the scrollbars.
230 */
231 void _onScrollerEnd(Event e) {
232 _cancelTimeout();
233 _timerId = window.setTimeout(_boundHideFn, _DISPLAY_TIME);
234 _scrollInProgress = false;
235 }
236 void onScrollerMoved(num scrollX, num scrollY, bool decelerating) {
237 if (_scrollInProgress == false) {
238 // Display the scrollbar and then immediately prepare to hide it...
239 _onScrollerStart(null);
240 _onScrollerEnd(null);
241 }
242 updateScrollbars(scrollX, scrollY);
243 }
244
245 void refresh() {
246 if (_scrollInProgress == false && _hovering == false) {
247 // No need to refresh if not visible.
248 return;
249 }
250 _scroller._resize(() {
251 updateScrollbars(_scroller.getHorizontalOffset(),
252 _scroller.getVerticalOffset());
253 });
254 }
255
256 void updateScrollbars(num scrollX, num scrollY) {
257 Size contentSize = _scroller._getAdjustedContentSize();
258 if (_scroller._shouldScrollHorizontally()) {
259 num scrollPercentX = _scroller.getHorizontalScrollPercent(scrollX);
260 _updateScrollbar(_horizontalElement, scrollX, scrollPercentX,
261 _scroller._scrollSize.width,
262 contentSize.width, 'right', 'width');
263 }
264 if (_scroller._shouldScrollVertically()) {
265 num scrollPercentY = _scroller.getVerticalScrollPercent(scrollY);
266 _updateScrollbar(_verticalElement, scrollY, scrollPercentY,
267 _scroller._scrollSize.height,
268 contentSize.height, 'bottom', 'height');
269 }
270 }
271
272 /**
273 * When scrolling starts, show scrollbars and clear hide intervals.
274 */
275 void _onScrollerStart(Event e) {
276 _scrollInProgress = true;
277 _cancelTimeout();
278 _showScrollbars(true);
279 }
280
281 void _cancelTimeout() {
282 if (_timerId != null) {
283 window.clearTimeout(_timerId);
284 _timerId = null;
285 }
286 }
287
288 /**
289 * Show or hide the scrollbars by changing the opacity.
290 */
291 void _showScrollbars(bool show) {
292 if (_hovering == true && _displayOnHover) {
293 show = true;
294 }
295 _toggleOpacity(_verticalElement, show);
296 _toggleOpacity(_horizontalElement, show);
297 }
298
299 _toggleOpacity(Element element, bool show) {
300 if (show) {
301 element.style.removeProperty("opacity");
302 } else {
303 element.style.opacity = '0';
304 }
305 }
306
307 num _defaultScrollSize(num frameSize, num contentSize) {
308 return GoogleMath.clamp(
309 (frameSize -_PADDING_LENGTH * 2) * frameSize / contentSize,
310 _MIN_SIZE, frameSize -_PADDING_LENGTH * 2);
311 }
312
313 /**
314 * Update the vertical or horizontal scrollbar based on the new scroll
315 * properties. The CSS property to adjust for position (bottom|right) is
316 * specified by [cssPos]. The CSS property to adjust for size (height|width)
317 * is specified by [cssSize].
318 */
319 void _updateScrollbar(Element element, num offset,
320 num scrollPercent, num frameSize,
321 num contentSize, String cssPos, String cssSize) {
322 if (!_cachedSize.containsKey(cssSize)) {
323 if (offset == null || contentSize < frameSize) {
324 return;
325 }
326 _frame.nodes.add(element);
327 }
328 num stretchPercent;
329 if (scrollPercent > 1) {
330 stretchPercent = scrollPercent - 1;
331 } else {
332 stretchPercent = scrollPercent < 0 ? -scrollPercent : 0;
333 }
334 num scrollPx = stretchPercent * (contentSize - frameSize);
335 num maxSize = _defaultScrollSize(frameSize, contentSize);
336 num size = Math.max(_MIN_COMPRESSED_SIZE, maxSize - scrollPx);
337 num maxOffset = frameSize - size -_PADDING_LENGTH * 2;
338 num pos = GoogleMath.clamp(scrollPercent * maxOffset,
339 0, maxOffset) + _PADDING_LENGTH;
340 pos = pos.round();
341 size = size.round();
342 final style = element.style;
343 style.setProperty(cssPos, '${pos}px', '');
344 if (_cachedSize[cssSize] != size) {
345 _cachedSize[cssSize] = size;
346 style.setProperty(cssSize, '${size}px', '');
347 }
348 if (element.parent == null) {
349 _frame.nodes.add(element);
350 }
351 }
352 }
OLDNEW
« no previous file with comments | « client/touch/ScrollWatcher.dart ('k') | client/touch/Scroller.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698