1 | /* |
2 | * Copyright (c) 2010, Google Inc. All rights reserved. |
3 | * Copyright (C) 2008, 2011, 2014-2016 Apple Inc. All Rights Reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions are |
7 | * met: |
8 | * |
9 | * * Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * * Redistributions in binary form must reproduce the above |
12 | * copyright notice, this list of conditions and the following disclaimer |
13 | * in the documentation and/or other materials provided with the |
14 | * distribution. |
15 | * * Neither the name of Google Inc. nor the names of its |
16 | * contributors may be used to endorse or promote products derived from |
17 | * this software without specific prior written permission. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include "config.h" |
33 | #include "ScrollableArea.h" |
34 | |
35 | #include "FloatPoint.h" |
36 | #include "GraphicsContext.h" |
37 | #include "GraphicsLayer.h" |
38 | #include "LayoutRect.h" |
39 | #include "Logging.h" |
40 | #include "PlatformWheelEvent.h" |
41 | #include "ScrollAnimator.h" |
42 | #include "ScrollAnimatorMock.h" |
43 | #include "ScrollbarTheme.h" |
44 | #include <wtf/text/TextStream.h> |
45 | |
46 | namespace WebCore { |
47 | |
48 | struct SameSizeAsScrollableArea { |
49 | virtual ~SameSizeAsScrollableArea(); |
50 | #if ENABLE(CSS_SCROLL_SNAP) |
51 | void* pointers[3]; |
52 | unsigned currentIndices[2]; |
53 | #else |
54 | void* pointer[2]; |
55 | #endif |
56 | IntPoint origin; |
57 | unsigned bitfields : 16; |
58 | }; |
59 | |
60 | COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small); |
61 | |
62 | ScrollableArea::ScrollableArea() |
63 | : m_constrainsScrollingToContentEdge(true) |
64 | , m_inLiveResize(false) |
65 | , m_verticalScrollElasticity(ScrollElasticityNone) |
66 | , m_horizontalScrollElasticity(ScrollElasticityNone) |
67 | , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault) |
68 | , m_scrollOriginChanged(false) |
69 | , m_currentScrollType(static_cast<unsigned>(ScrollType::User)) |
70 | , m_scrollShouldClearLatchedState(false) |
71 | { |
72 | } |
73 | |
74 | ScrollableArea::~ScrollableArea() = default; |
75 | |
76 | ScrollAnimator& ScrollableArea::scrollAnimator() const |
77 | { |
78 | if (!m_scrollAnimator) { |
79 | if (usesMockScrollAnimator()) { |
80 | m_scrollAnimator = std::make_unique<ScrollAnimatorMock>(const_cast<ScrollableArea&>(*this), [this](const String& message) { |
81 | logMockScrollAnimatorMessage(message); |
82 | }); |
83 | } else |
84 | m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea&>(*this)); |
85 | } |
86 | |
87 | ASSERT(m_scrollAnimator); |
88 | return *m_scrollAnimator.get(); |
89 | } |
90 | |
91 | void ScrollableArea::setScrollOrigin(const IntPoint& origin) |
92 | { |
93 | if (m_scrollOrigin != origin) { |
94 | m_scrollOrigin = origin; |
95 | m_scrollOriginChanged = true; |
96 | } |
97 | } |
98 | |
99 | float ScrollableArea::adjustScrollStepForFixedContent(float step, ScrollbarOrientation, ScrollGranularity) |
100 | { |
101 | return step; |
102 | } |
103 | |
104 | bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier) |
105 | { |
106 | ScrollbarOrientation orientation; |
107 | Scrollbar* scrollbar; |
108 | if (direction == ScrollUp || direction == ScrollDown) { |
109 | orientation = VerticalScrollbar; |
110 | scrollbar = verticalScrollbar(); |
111 | } else { |
112 | orientation = HorizontalScrollbar; |
113 | scrollbar = horizontalScrollbar(); |
114 | } |
115 | |
116 | if (!scrollbar) |
117 | return false; |
118 | |
119 | float step = 0; |
120 | switch (granularity) { |
121 | case ScrollByLine: |
122 | step = scrollbar->lineStep(); |
123 | break; |
124 | case ScrollByPage: |
125 | step = scrollbar->pageStep(); |
126 | break; |
127 | case ScrollByDocument: |
128 | step = scrollbar->totalSize(); |
129 | break; |
130 | case ScrollByPixel: |
131 | step = scrollbar->pixelStep(); |
132 | break; |
133 | } |
134 | |
135 | if (direction == ScrollUp || direction == ScrollLeft) |
136 | multiplier = -multiplier; |
137 | |
138 | step = adjustScrollStepForFixedContent(step, orientation, granularity); |
139 | return scrollAnimator().scroll(orientation, granularity, step, multiplier); |
140 | } |
141 | |
142 | void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset, ScrollClamping clamping) |
143 | { |
144 | LOG_WITH_STREAM(Scrolling, stream << "ScrollableArea " << this << " scrollToOffsetWithoutAnimation " << offset); |
145 | scrollAnimator().scrollToOffsetWithoutAnimation(offset, clamping); |
146 | } |
147 | |
148 | void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset) |
149 | { |
150 | auto currentOffset = scrollOffsetFromPosition(IntPoint(scrollAnimator().currentPosition())); |
151 | if (orientation == HorizontalScrollbar) |
152 | scrollToOffsetWithoutAnimation(FloatPoint(offset, currentOffset.y())); |
153 | else |
154 | scrollToOffsetWithoutAnimation(FloatPoint(currentOffset.x(), offset)); |
155 | } |
156 | |
157 | void ScrollableArea::notifyScrollPositionChanged(const ScrollPosition& position) |
158 | { |
159 | scrollPositionChanged(position); |
160 | scrollAnimator().setCurrentPosition(position); |
161 | } |
162 | |
163 | void ScrollableArea::scrollPositionChanged(const ScrollPosition& position) |
164 | { |
165 | IntPoint oldPosition = scrollPosition(); |
166 | // Tell the derived class to scroll its contents. |
167 | setScrollOffset(scrollOffsetFromPosition(position)); |
168 | |
169 | Scrollbar* verticalScrollbar = this->verticalScrollbar(); |
170 | |
171 | // Tell the scrollbars to update their thumb postions. |
172 | if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { |
173 | horizontalScrollbar->offsetDidChange(); |
174 | if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) { |
175 | if (!verticalScrollbar) |
176 | horizontalScrollbar->invalidate(); |
177 | else { |
178 | // If there is both a horizontalScrollbar and a verticalScrollbar, |
179 | // then we must also invalidate the corner between them. |
180 | IntRect boundsAndCorner = horizontalScrollbar->boundsRect(); |
181 | boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width()); |
182 | horizontalScrollbar->invalidateRect(boundsAndCorner); |
183 | } |
184 | } |
185 | } |
186 | if (verticalScrollbar) { |
187 | verticalScrollbar->offsetDidChange(); |
188 | if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar()) |
189 | verticalScrollbar->invalidate(); |
190 | } |
191 | |
192 | if (scrollPosition() != oldPosition) |
193 | scrollAnimator().notifyContentAreaScrolled(scrollPosition() - oldPosition); |
194 | } |
195 | |
196 | bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent) |
197 | { |
198 | if (!isScrollableOrRubberbandable()) |
199 | return false; |
200 | |
201 | bool handledEvent = scrollAnimator().handleWheelEvent(wheelEvent); |
202 | #if ENABLE(CSS_SCROLL_SNAP) |
203 | if (scrollAnimator().activeScrollSnapIndexDidChange()) { |
204 | setCurrentHorizontalSnapPointIndex(scrollAnimator().activeScrollSnapIndexForAxis(ScrollEventAxis::Horizontal)); |
205 | setCurrentVerticalSnapPointIndex(scrollAnimator().activeScrollSnapIndexForAxis(ScrollEventAxis::Vertical)); |
206 | } |
207 | #endif |
208 | return handledEvent; |
209 | } |
210 | |
211 | #if ENABLE(TOUCH_EVENTS) |
212 | bool ScrollableArea::handleTouchEvent(const PlatformTouchEvent& touchEvent) |
213 | { |
214 | return scrollAnimator().handleTouchEvent(touchEvent); |
215 | } |
216 | #endif |
217 | |
218 | // NOTE: Only called from Internals for testing. |
219 | void ScrollableArea::setScrollOffsetFromInternals(const ScrollOffset& offset) |
220 | { |
221 | setScrollOffsetFromAnimation(offset); |
222 | } |
223 | |
224 | void ScrollableArea::setScrollOffsetFromAnimation(const ScrollOffset& offset) |
225 | { |
226 | ScrollPosition position = scrollPositionFromOffset(offset); |
227 | if (requestScrollPositionUpdate(position)) |
228 | return; |
229 | |
230 | scrollPositionChanged(position); |
231 | } |
232 | |
233 | void ScrollableArea::willStartLiveResize() |
234 | { |
235 | if (m_inLiveResize) |
236 | return; |
237 | m_inLiveResize = true; |
238 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
239 | scrollAnimator->willStartLiveResize(); |
240 | } |
241 | |
242 | void ScrollableArea::willEndLiveResize() |
243 | { |
244 | if (!m_inLiveResize) |
245 | return; |
246 | m_inLiveResize = false; |
247 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
248 | scrollAnimator->willEndLiveResize(); |
249 | } |
250 | |
251 | void ScrollableArea::contentAreaWillPaint() const |
252 | { |
253 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
254 | scrollAnimator->contentAreaWillPaint(); |
255 | } |
256 | |
257 | void ScrollableArea::mouseEnteredContentArea() const |
258 | { |
259 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
260 | scrollAnimator->mouseEnteredContentArea(); |
261 | } |
262 | |
263 | void ScrollableArea::mouseExitedContentArea() const |
264 | { |
265 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
266 | scrollAnimator->mouseExitedContentArea(); |
267 | } |
268 | |
269 | void ScrollableArea::mouseMovedInContentArea() const |
270 | { |
271 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
272 | scrollAnimator->mouseMovedInContentArea(); |
273 | } |
274 | |
275 | void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const |
276 | { |
277 | scrollAnimator().mouseEnteredScrollbar(scrollbar); |
278 | } |
279 | |
280 | void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const |
281 | { |
282 | scrollAnimator().mouseExitedScrollbar(scrollbar); |
283 | } |
284 | |
285 | void ScrollableArea::mouseIsDownInScrollbar(Scrollbar* scrollbar, bool mouseIsDown) const |
286 | { |
287 | scrollAnimator().mouseIsDownInScrollbar(scrollbar, mouseIsDown); |
288 | } |
289 | |
290 | void ScrollableArea::contentAreaDidShow() const |
291 | { |
292 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
293 | scrollAnimator->contentAreaDidShow(); |
294 | } |
295 | |
296 | void ScrollableArea::contentAreaDidHide() const |
297 | { |
298 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
299 | scrollAnimator->contentAreaDidHide(); |
300 | } |
301 | |
302 | void ScrollableArea::lockOverlayScrollbarStateToHidden(bool shouldLockState) const |
303 | { |
304 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
305 | scrollAnimator->lockOverlayScrollbarStateToHidden(shouldLockState); |
306 | } |
307 | |
308 | bool ScrollableArea::scrollbarsCanBeActive() const |
309 | { |
310 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
311 | return scrollAnimator->scrollbarsCanBeActive(); |
312 | return true; |
313 | } |
314 | |
315 | void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) |
316 | { |
317 | if (orientation == VerticalScrollbar) |
318 | scrollAnimator().didAddVerticalScrollbar(scrollbar); |
319 | else |
320 | scrollAnimator().didAddHorizontalScrollbar(scrollbar); |
321 | |
322 | // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar |
323 | setScrollbarOverlayStyle(scrollbarOverlayStyle()); |
324 | } |
325 | |
326 | void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) |
327 | { |
328 | if (orientation == VerticalScrollbar) |
329 | scrollAnimator().willRemoveVerticalScrollbar(scrollbar); |
330 | else |
331 | scrollAnimator().willRemoveHorizontalScrollbar(scrollbar); |
332 | } |
333 | |
334 | void ScrollableArea::contentsResized() |
335 | { |
336 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
337 | scrollAnimator->contentsResized(); |
338 | } |
339 | |
340 | void ScrollableArea::availableContentSizeChanged(AvailableSizeChangeReason) |
341 | { |
342 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
343 | scrollAnimator->contentsResized(); // This flashes overlay scrollbars. |
344 | } |
345 | |
346 | bool ScrollableArea::hasOverlayScrollbars() const |
347 | { |
348 | return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar()) |
349 | || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar()); |
350 | } |
351 | |
352 | void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle) |
353 | { |
354 | m_scrollbarOverlayStyle = overlayStyle; |
355 | |
356 | if (horizontalScrollbar()) { |
357 | ScrollbarTheme::theme().updateScrollbarOverlayStyle(*horizontalScrollbar()); |
358 | horizontalScrollbar()->invalidate(); |
359 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
360 | scrollAnimator->invalidateScrollbarPartLayers(horizontalScrollbar()); |
361 | } |
362 | |
363 | if (verticalScrollbar()) { |
364 | ScrollbarTheme::theme().updateScrollbarOverlayStyle(*verticalScrollbar()); |
365 | verticalScrollbar()->invalidate(); |
366 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
367 | scrollAnimator->invalidateScrollbarPartLayers(verticalScrollbar()); |
368 | } |
369 | } |
370 | |
371 | bool ScrollableArea::useDarkAppearanceForScrollbars() const |
372 | { |
373 | // If dark appearance is used or the overlay style is light (because of a dark page background), set the dark appearance. |
374 | return useDarkAppearance() || scrollbarOverlayStyle() == WebCore::ScrollbarOverlayStyleLight; |
375 | } |
376 | |
377 | void ScrollableArea::invalidateScrollbar(Scrollbar& scrollbar, const IntRect& rect) |
378 | { |
379 | if (&scrollbar == horizontalScrollbar()) { |
380 | if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) { |
381 | graphicsLayer->setNeedsDisplay(); |
382 | graphicsLayer->setContentsNeedsDisplay(); |
383 | return; |
384 | } |
385 | } else if (&scrollbar == verticalScrollbar()) { |
386 | if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) { |
387 | graphicsLayer->setNeedsDisplay(); |
388 | graphicsLayer->setContentsNeedsDisplay(); |
389 | return; |
390 | } |
391 | } |
392 | |
393 | invalidateScrollbarRect(scrollbar, rect); |
394 | } |
395 | |
396 | void ScrollableArea::invalidateScrollCorner(const IntRect& rect) |
397 | { |
398 | if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) { |
399 | graphicsLayer->setNeedsDisplay(); |
400 | return; |
401 | } |
402 | |
403 | invalidateScrollCornerRect(rect); |
404 | } |
405 | |
406 | void ScrollableArea::verticalScrollbarLayerDidChange() |
407 | { |
408 | scrollAnimator().verticalScrollbarLayerDidChange(); |
409 | } |
410 | |
411 | void ScrollableArea::horizontalScrollbarLayerDidChange() |
412 | { |
413 | scrollAnimator().horizontalScrollbarLayerDidChange(); |
414 | } |
415 | |
416 | bool ScrollableArea::hasLayerForHorizontalScrollbar() const |
417 | { |
418 | return layerForHorizontalScrollbar(); |
419 | } |
420 | |
421 | bool ScrollableArea::hasLayerForVerticalScrollbar() const |
422 | { |
423 | return layerForVerticalScrollbar(); |
424 | } |
425 | |
426 | bool ScrollableArea::hasLayerForScrollCorner() const |
427 | { |
428 | return layerForScrollCorner(); |
429 | } |
430 | |
431 | #if ENABLE(CSS_SCROLL_SNAP) |
432 | ScrollSnapOffsetsInfo<LayoutUnit>& ScrollableArea::ensureSnapOffsetsInfo() |
433 | { |
434 | if (!m_snapOffsetsInfo) |
435 | m_snapOffsetsInfo = std::make_unique<ScrollSnapOffsetsInfo<LayoutUnit>>(); |
436 | return *m_snapOffsetsInfo; |
437 | } |
438 | |
439 | const Vector<LayoutUnit>* ScrollableArea::horizontalSnapOffsets() const |
440 | { |
441 | if (!m_snapOffsetsInfo) |
442 | return nullptr; |
443 | |
444 | return &m_snapOffsetsInfo->horizontalSnapOffsets; |
445 | } |
446 | |
447 | const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::horizontalSnapOffsetRanges() const |
448 | { |
449 | if (!m_snapOffsetsInfo) |
450 | return nullptr; |
451 | |
452 | return &m_snapOffsetsInfo->horizontalSnapOffsetRanges; |
453 | } |
454 | |
455 | const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::verticalSnapOffsetRanges() const |
456 | { |
457 | if (!m_snapOffsetsInfo) |
458 | return nullptr; |
459 | |
460 | return &m_snapOffsetsInfo->verticalSnapOffsetRanges; |
461 | } |
462 | |
463 | const Vector<LayoutUnit>* ScrollableArea::verticalSnapOffsets() const |
464 | { |
465 | if (!m_snapOffsetsInfo) |
466 | return nullptr; |
467 | |
468 | return &m_snapOffsetsInfo->verticalSnapOffsets; |
469 | } |
470 | |
471 | void ScrollableArea::setHorizontalSnapOffsets(const Vector<LayoutUnit>& horizontalSnapOffsets) |
472 | { |
473 | // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator. |
474 | if (horizontalSnapOffsets.size()) |
475 | scrollAnimator(); |
476 | |
477 | ensureSnapOffsetsInfo().horizontalSnapOffsets = horizontalSnapOffsets; |
478 | } |
479 | |
480 | void ScrollableArea::setVerticalSnapOffsets(const Vector<LayoutUnit>& verticalSnapOffsets) |
481 | { |
482 | // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator. |
483 | if (verticalSnapOffsets.size()) |
484 | scrollAnimator(); |
485 | |
486 | ensureSnapOffsetsInfo().verticalSnapOffsets = verticalSnapOffsets; |
487 | } |
488 | |
489 | void ScrollableArea::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& horizontalRanges) |
490 | { |
491 | ensureSnapOffsetsInfo().horizontalSnapOffsetRanges = horizontalRanges; |
492 | } |
493 | |
494 | void ScrollableArea::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& verticalRanges) |
495 | { |
496 | ensureSnapOffsetsInfo().verticalSnapOffsetRanges = verticalRanges; |
497 | } |
498 | |
499 | void ScrollableArea::clearHorizontalSnapOffsets() |
500 | { |
501 | if (!m_snapOffsetsInfo) |
502 | return; |
503 | |
504 | m_snapOffsetsInfo->horizontalSnapOffsets = { }; |
505 | m_snapOffsetsInfo->horizontalSnapOffsetRanges = { }; |
506 | m_currentHorizontalSnapPointIndex = 0; |
507 | } |
508 | |
509 | void ScrollableArea::clearVerticalSnapOffsets() |
510 | { |
511 | if (!m_snapOffsetsInfo) |
512 | return; |
513 | |
514 | m_snapOffsetsInfo->verticalSnapOffsets = { }; |
515 | m_snapOffsetsInfo->verticalSnapOffsetRanges = { }; |
516 | m_currentVerticalSnapPointIndex = 0; |
517 | } |
518 | |
519 | IntPoint ScrollableArea::nearestActiveSnapPoint(const IntPoint& currentPosition) |
520 | { |
521 | if (!horizontalSnapOffsets() && !verticalSnapOffsets()) |
522 | return currentPosition; |
523 | |
524 | if (!existingScrollAnimator()) |
525 | return currentPosition; |
526 | |
527 | IntPoint correctedPosition = currentPosition; |
528 | |
529 | if (horizontalSnapOffsets()) { |
530 | const auto& horizontal = *horizontalSnapOffsets(); |
531 | |
532 | size_t activeIndex = currentHorizontalSnapPointIndex(); |
533 | if (activeIndex < horizontal.size()) |
534 | correctedPosition.setX(horizontal[activeIndex].toInt()); |
535 | } |
536 | |
537 | if (verticalSnapOffsets()) { |
538 | const auto& vertical = *verticalSnapOffsets(); |
539 | |
540 | size_t activeIndex = currentVerticalSnapPointIndex(); |
541 | if (activeIndex < vertical.size()) |
542 | correctedPosition.setY(vertical[activeIndex].toInt()); |
543 | } |
544 | |
545 | return correctedPosition; |
546 | } |
547 | |
548 | void ScrollableArea::updateScrollSnapState() |
549 | { |
550 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
551 | scrollAnimator->updateScrollSnapState(); |
552 | |
553 | if (isScrollSnapInProgress()) |
554 | return; |
555 | |
556 | IntPoint currentPosition = scrollPosition(); |
557 | IntPoint correctedPosition = nearestActiveSnapPoint(currentPosition); |
558 | |
559 | if (correctedPosition != currentPosition) |
560 | scrollToOffsetWithoutAnimation(correctedPosition); |
561 | } |
562 | #else |
563 | void ScrollableArea::updateScrollSnapState() |
564 | { |
565 | } |
566 | #endif |
567 | |
568 | |
569 | void ScrollableArea::serviceScrollAnimations() |
570 | { |
571 | if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) |
572 | scrollAnimator->serviceScrollAnimations(); |
573 | } |
574 | |
575 | #if PLATFORM(IOS_FAMILY) |
576 | bool ScrollableArea::isPinnedInBothDirections(const IntSize& scrollDelta) const |
577 | { |
578 | return isPinnedHorizontallyInDirection(scrollDelta.width()) && isPinnedVerticallyInDirection(scrollDelta.height()); |
579 | } |
580 | |
581 | bool ScrollableArea::isPinnedHorizontallyInDirection(int horizontalScrollDelta) const |
582 | { |
583 | if (horizontalScrollDelta < 0 && isHorizontalScrollerPinnedToMinimumPosition()) |
584 | return true; |
585 | if (horizontalScrollDelta > 0 && isHorizontalScrollerPinnedToMaximumPosition()) |
586 | return true; |
587 | return false; |
588 | } |
589 | |
590 | bool ScrollableArea::isPinnedVerticallyInDirection(int verticalScrollDelta) const |
591 | { |
592 | if (verticalScrollDelta < 0 && isVerticalScrollerPinnedToMinimumPosition()) |
593 | return true; |
594 | if (verticalScrollDelta > 0 && isVerticalScrollerPinnedToMaximumPosition()) |
595 | return true; |
596 | return false; |
597 | } |
598 | #endif // PLATFORM(IOS_FAMILY) |
599 | |
600 | int ScrollableArea::horizontalScrollbarIntrusion() const |
601 | { |
602 | return verticalScrollbar() ? verticalScrollbar()->occupiedWidth() : 0; |
603 | } |
604 | |
605 | int ScrollableArea::verticalScrollbarIntrusion() const |
606 | { |
607 | return horizontalScrollbar() ? horizontalScrollbar()->occupiedHeight() : 0; |
608 | } |
609 | |
610 | IntSize ScrollableArea::scrollbarIntrusion() const |
611 | { |
612 | return { horizontalScrollbarIntrusion(), verticalScrollbarIntrusion() }; |
613 | } |
614 | |
615 | ScrollPosition ScrollableArea::scrollPosition() const |
616 | { |
617 | // FIXME: This relationship seems to be inverted. Scrollbars should be 'view', not 'model', and should get their values from us. |
618 | int x = horizontalScrollbar() ? horizontalScrollbar()->value() : 0; |
619 | int y = verticalScrollbar() ? verticalScrollbar()->value() : 0; |
620 | return IntPoint(x, y); |
621 | } |
622 | |
623 | ScrollPosition ScrollableArea::minimumScrollPosition() const |
624 | { |
625 | return scrollPositionFromOffset(ScrollPosition()); |
626 | } |
627 | |
628 | ScrollPosition ScrollableArea::maximumScrollPosition() const |
629 | { |
630 | return scrollPositionFromOffset(ScrollPosition(totalContentsSize() - visibleSize())); |
631 | } |
632 | |
633 | ScrollOffset ScrollableArea::maximumScrollOffset() const |
634 | { |
635 | return ScrollOffset(totalContentsSize() - visibleSize()); |
636 | } |
637 | |
638 | ScrollPosition ScrollableArea::scrollPositionFromOffset(ScrollOffset offset) const |
639 | { |
640 | return scrollPositionFromOffset(offset, toIntSize(m_scrollOrigin)); |
641 | } |
642 | |
643 | ScrollOffset ScrollableArea::scrollOffsetFromPosition(ScrollPosition position) const |
644 | { |
645 | return scrollOffsetFromPosition(position, toIntSize(m_scrollOrigin)); |
646 | } |
647 | |
648 | bool ScrollableArea::scrolledToTop() const |
649 | { |
650 | return scrollPosition().y() <= minimumScrollPosition().y(); |
651 | } |
652 | |
653 | bool ScrollableArea::scrolledToBottom() const |
654 | { |
655 | return scrollPosition().y() >= maximumScrollPosition().y(); |
656 | } |
657 | |
658 | bool ScrollableArea::scrolledToLeft() const |
659 | { |
660 | return scrollPosition().x() <= minimumScrollPosition().x(); |
661 | } |
662 | |
663 | bool ScrollableArea::scrolledToRight() const |
664 | { |
665 | return scrollPosition().x() >= maximumScrollPosition().x(); |
666 | } |
667 | |
668 | void ScrollableArea::scrollbarStyleChanged(ScrollbarStyle, bool) |
669 | { |
670 | availableContentSizeChanged(AvailableSizeChangeReason::ScrollbarsChanged); |
671 | } |
672 | |
673 | IntSize ScrollableArea::reachableTotalContentsSize() const |
674 | { |
675 | return totalContentsSize(); |
676 | } |
677 | |
678 | IntSize ScrollableArea::totalContentsSize() const |
679 | { |
680 | IntSize totalContentsSize = contentsSize(); |
681 | totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight()); |
682 | return totalContentsSize; |
683 | } |
684 | |
685 | IntRect ScrollableArea::visibleContentRect(VisibleContentRectBehavior visibleContentRectBehavior) const |
686 | { |
687 | return visibleContentRectInternal(ExcludeScrollbars, visibleContentRectBehavior); |
688 | } |
689 | |
690 | IntRect ScrollableArea::visibleContentRectIncludingScrollbars(VisibleContentRectBehavior visibleContentRectBehavior) const |
691 | { |
692 | return visibleContentRectInternal(IncludeScrollbars, visibleContentRectBehavior); |
693 | } |
694 | |
695 | IntRect ScrollableArea::visibleContentRectInternal(VisibleContentRectIncludesScrollbars scrollbarInclusion, VisibleContentRectBehavior) const |
696 | { |
697 | int verticalScrollbarWidth = 0; |
698 | int horizontalScrollbarHeight = 0; |
699 | |
700 | if (scrollbarInclusion == IncludeScrollbars) { |
701 | if (Scrollbar* verticalBar = verticalScrollbar()) |
702 | verticalScrollbarWidth = verticalBar->occupiedWidth(); |
703 | if (Scrollbar* horizontalBar = horizontalScrollbar()) |
704 | horizontalScrollbarHeight = horizontalBar->occupiedHeight(); |
705 | } |
706 | |
707 | return IntRect(scrollPosition().x(), |
708 | scrollPosition().y(), |
709 | std::max(0, visibleWidth() + verticalScrollbarWidth), |
710 | std::max(0, visibleHeight() + horizontalScrollbarHeight)); |
711 | } |
712 | |
713 | LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, const LayoutPoint& scrollPosition, const LayoutPoint& scrollOrigin, int , int ) |
714 | { |
715 | // The viewport rect that we're scrolling shouldn't be larger than our document. |
716 | LayoutSize idealScrollRectSize(std::min(visibleContentRect.width(), totalContentsSize.width()), std::min(visibleContentRect.height(), totalContentsSize.height())); |
717 | |
718 | LayoutRect scrollRect(scrollPosition + scrollOrigin - LayoutSize(0, headerHeight), idealScrollRectSize); |
719 | LayoutRect documentRect(LayoutPoint(), LayoutSize(totalContentsSize.width(), totalContentsSize.height() - headerHeight - footerHeight)); |
720 | |
721 | // Use intersection to constrain our ideal scroll rect by the document rect. |
722 | scrollRect.intersect(documentRect); |
723 | |
724 | if (scrollRect.size() != idealScrollRectSize) { |
725 | // If the rect was clipped, restore its size, effectively pushing it "down" from the top left. |
726 | scrollRect.setSize(idealScrollRectSize); |
727 | |
728 | // If we still clip, push our rect "up" from the bottom right. |
729 | scrollRect.intersect(documentRect); |
730 | if (scrollRect.width() < idealScrollRectSize.width()) |
731 | scrollRect.move(-(idealScrollRectSize.width() - scrollRect.width()), 0_lu); |
732 | if (scrollRect.height() < idealScrollRectSize.height()) |
733 | scrollRect.move(0_lu, -(idealScrollRectSize.height() - scrollRect.height())); |
734 | } |
735 | |
736 | return scrollRect.location() - toLayoutSize(scrollOrigin); |
737 | } |
738 | |
739 | LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutPoint& scrollPosition) |
740 | { |
741 | return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight()); |
742 | } |
743 | |
744 | void ScrollableArea::computeScrollbarValueAndOverhang(float currentPosition, float totalSize, float visibleSize, float& doubleValue, float& overhangAmount) |
745 | { |
746 | doubleValue = 0; |
747 | overhangAmount = 0; |
748 | float maximum = totalSize - visibleSize; |
749 | |
750 | if (currentPosition < 0) { |
751 | // Scrolled past the top. |
752 | doubleValue = 0; |
753 | overhangAmount = -currentPosition; |
754 | } else if (visibleSize + currentPosition > totalSize) { |
755 | // Scrolled past the bottom. |
756 | doubleValue = 1; |
757 | overhangAmount = currentPosition + visibleSize - totalSize; |
758 | } else { |
759 | // Within the bounds of the scrollable area. |
760 | if (maximum > 0) |
761 | doubleValue = currentPosition / maximum; |
762 | else |
763 | doubleValue = 0; |
764 | } |
765 | } |
766 | |
767 | } // namespace WebCore |
768 | |