| /* |
| * Copyright (C) 2011 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" |
| |
| #include "base/optional.h" |
| #include "build/build_config.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/web_mouse_event.h" |
| #include "third_party/blink/public/platform/web_point.h" |
| #include "third_party/blink/public/platform/web_rect.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h" |
| #include "third_party/blink/renderer/platform/graphics/color.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| |
| #if !defined(OS_MACOSX) |
| #include "third_party/blink/public/platform/web_theme_engine.h" |
| #endif |
| |
| namespace blink { |
| |
| bool ScrollbarTheme::g_mock_scrollbars_enabled_ = false; |
| |
| static inline bool ShouldPaintScrollbarPart(const IntRect& part_rect, |
| const CullRect& cull_rect) { |
| return (!part_rect.IsEmpty()) || cull_rect.IntersectsCullRect(part_rect); |
| } |
| |
| bool ScrollbarTheme::Paint(const Scrollbar& scrollbar, |
| GraphicsContext& graphics_context, |
| const CullRect& cull_rect) { |
| // Create the ScrollbarControlPartMask based on the cullRect |
| ScrollbarControlPartMask scroll_mask = kNoPart; |
| |
| IntRect back_button_start_paint_rect; |
| IntRect back_button_end_paint_rect; |
| IntRect forward_button_start_paint_rect; |
| IntRect forward_button_end_paint_rect; |
| if (HasButtons(scrollbar)) { |
| back_button_start_paint_rect = |
| BackButtonRect(scrollbar, kBackButtonStartPart, true); |
| if (ShouldPaintScrollbarPart(back_button_start_paint_rect, cull_rect)) |
| scroll_mask |= kBackButtonStartPart; |
| back_button_end_paint_rect = |
| BackButtonRect(scrollbar, kBackButtonEndPart, true); |
| if (ShouldPaintScrollbarPart(back_button_end_paint_rect, cull_rect)) |
| scroll_mask |= kBackButtonEndPart; |
| forward_button_start_paint_rect = |
| ForwardButtonRect(scrollbar, kForwardButtonStartPart, true); |
| if (ShouldPaintScrollbarPart(forward_button_start_paint_rect, cull_rect)) |
| scroll_mask |= kForwardButtonStartPart; |
| forward_button_end_paint_rect = |
| ForwardButtonRect(scrollbar, kForwardButtonEndPart, true); |
| if (ShouldPaintScrollbarPart(forward_button_end_paint_rect, cull_rect)) |
| scroll_mask |= kForwardButtonEndPart; |
| } |
| |
| IntRect start_track_rect; |
| IntRect thumb_rect; |
| IntRect end_track_rect; |
| IntRect track_paint_rect = TrackRect(scrollbar, true); |
| scroll_mask |= kTrackBGPart; |
| bool thumb_present = HasThumb(scrollbar); |
| if (thumb_present) { |
| IntRect track = TrackRect(scrollbar); |
| SplitTrack(scrollbar, track, start_track_rect, thumb_rect, end_track_rect); |
| if (ShouldPaintScrollbarPart(thumb_rect, cull_rect)) |
| scroll_mask |= kThumbPart; |
| if (ShouldPaintScrollbarPart(start_track_rect, cull_rect)) |
| scroll_mask |= kBackTrackPart; |
| if (ShouldPaintScrollbarPart(end_track_rect, cull_rect)) |
| scroll_mask |= kForwardTrackPart; |
| } |
| |
| // Paint the scrollbar background (only used by custom CSS scrollbars). |
| PaintScrollbarBackground(graphics_context, scrollbar); |
| |
| // Paint the back and forward buttons. |
| if (scroll_mask & kBackButtonStartPart) |
| PaintButton(graphics_context, scrollbar, back_button_start_paint_rect, |
| kBackButtonStartPart); |
| if (scroll_mask & kBackButtonEndPart) |
| PaintButton(graphics_context, scrollbar, back_button_end_paint_rect, |
| kBackButtonEndPart); |
| if (scroll_mask & kForwardButtonStartPart) |
| PaintButton(graphics_context, scrollbar, forward_button_start_paint_rect, |
| kForwardButtonStartPart); |
| if (scroll_mask & kForwardButtonEndPart) |
| PaintButton(graphics_context, scrollbar, forward_button_end_paint_rect, |
| kForwardButtonEndPart); |
| |
| if (scroll_mask & kTrackBGPart) |
| PaintTrackBackground(graphics_context, scrollbar, track_paint_rect); |
| |
| if ((scroll_mask & kForwardTrackPart) || (scroll_mask & kBackTrackPart)) { |
| // Paint the track pieces above and below the thumb. |
| if (scroll_mask & kBackTrackPart) |
| PaintTrackPiece(graphics_context, scrollbar, start_track_rect, |
| kBackTrackPart); |
| if (scroll_mask & kForwardTrackPart) |
| PaintTrackPiece(graphics_context, scrollbar, end_track_rect, |
| kForwardTrackPart); |
| |
| PaintTickmarks(graphics_context, scrollbar, track_paint_rect); |
| } |
| |
| if (scroll_mask & kThumbPart) |
| PaintThumbWithOpacity(graphics_context, scrollbar, thumb_rect); |
| |
| return true; |
| } |
| |
| ScrollbarPart ScrollbarTheme::HitTest(const Scrollbar& scrollbar, |
| const IntPoint& position_in_root_frame) { |
| ScrollbarPart result = kNoPart; |
| if (!scrollbar.Enabled()) |
| return result; |
| |
| IntPoint test_position = |
| scrollbar.ConvertFromRootFrame(position_in_root_frame); |
| test_position.Move(scrollbar.X(), scrollbar.Y()); |
| |
| if (!scrollbar.FrameRect().Contains(test_position)) |
| return kNoPart; |
| |
| result = kScrollbarBGPart; |
| |
| IntRect track = TrackRect(scrollbar); |
| if (track.Contains(test_position)) { |
| IntRect before_thumb_rect; |
| IntRect thumb_rect; |
| IntRect after_thumb_rect; |
| SplitTrack(scrollbar, track, before_thumb_rect, thumb_rect, |
| after_thumb_rect); |
| if (thumb_rect.Contains(test_position)) |
| result = kThumbPart; |
| else if (before_thumb_rect.Contains(test_position)) |
| result = kBackTrackPart; |
| else if (after_thumb_rect.Contains(test_position)) |
| result = kForwardTrackPart; |
| else |
| result = kTrackBGPart; |
| } else if (BackButtonRect(scrollbar, kBackButtonStartPart) |
| .Contains(test_position)) { |
| result = kBackButtonStartPart; |
| } else if (BackButtonRect(scrollbar, kBackButtonEndPart) |
| .Contains(test_position)) { |
| result = kBackButtonEndPart; |
| } else if (ForwardButtonRect(scrollbar, kForwardButtonStartPart) |
| .Contains(test_position)) { |
| result = kForwardButtonStartPart; |
| } else if (ForwardButtonRect(scrollbar, kForwardButtonEndPart) |
| .Contains(test_position)) { |
| result = kForwardButtonEndPart; |
| } |
| return result; |
| } |
| |
| void ScrollbarTheme::PaintScrollCorner( |
| GraphicsContext& context, |
| const DisplayItemClient& display_item_client, |
| const IntRect& corner_rect) { |
| if (corner_rect.IsEmpty()) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| context, display_item_client, DisplayItem::kScrollbarCorner)) |
| return; |
| |
| DrawingRecorder recorder(context, display_item_client, |
| DisplayItem::kScrollbarCorner); |
| #if defined(OS_MACOSX) |
| context.FillRect(corner_rect, Color::kWhite); |
| #else |
| Platform::Current()->ThemeEngine()->Paint( |
| context.Canvas(), WebThemeEngine::kPartScrollbarCorner, |
| WebThemeEngine::kStateNormal, WebRect(corner_rect), nullptr); |
| #endif |
| } |
| |
| void ScrollbarTheme::PaintTickmarks(GraphicsContext& context, |
| const Scrollbar& scrollbar, |
| const IntRect& rect) { |
| // Android paints tickmarks in the browser at FindResultBar.java. |
| #if !defined(OS_ANDROID) |
| if (scrollbar.Orientation() != kVerticalScrollbar) |
| return; |
| |
| if (rect.Height() <= 0 || rect.Width() <= 0) |
| return; |
| |
| // Get the tickmarks for the frameview. |
| Vector<IntRect> tickmarks; |
| scrollbar.GetTickmarks(tickmarks); |
| if (!tickmarks.size()) |
| return; |
| |
| if (DrawingRecorder::UseCachedDrawingIfPossible( |
| context, scrollbar, DisplayItem::kScrollbarTickmarks)) |
| return; |
| |
| DrawingRecorder recorder(context, scrollbar, |
| DisplayItem::kScrollbarTickmarks); |
| GraphicsContextStateSaver state_saver(context); |
| context.SetShouldAntialias(false); |
| |
| for (Vector<IntRect>::const_iterator i = tickmarks.begin(); |
| i != tickmarks.end(); ++i) { |
| // Calculate how far down (in %) the tick-mark should appear. |
| const float percent = static_cast<float>(i->Y()) / scrollbar.TotalSize(); |
| |
| // Calculate how far down (in pixels) the tick-mark should appear. |
| const int y_pos = rect.Y() + (rect.Height() * percent); |
| |
| FloatRect tick_rect(rect.X(), y_pos, rect.Width(), 3); |
| context.FillRect(tick_rect, Color(0xCC, 0xAA, 0x00, 0xFF)); |
| |
| FloatRect tick_stroke(rect.X() + TickmarkBorderWidth(), y_pos + 1, |
| rect.Width() - 2 * TickmarkBorderWidth(), 1); |
| context.FillRect(tick_stroke, Color(0xFF, 0xDD, 0x00, 0xFF)); |
| } |
| #endif |
| } |
| |
| TimeDelta ScrollbarTheme::OverlayScrollbarFadeOutDelay() const { |
| // On Mac, fading is controlled by the painting code in ScrollAnimatorMac. |
| return TimeDelta(); |
| } |
| |
| TimeDelta ScrollbarTheme::OverlayScrollbarFadeOutDuration() const { |
| // On Mac, fading is controlled by the painting code in ScrollAnimatorMac. |
| return TimeDelta(); |
| } |
| |
| int ScrollbarTheme::ThumbPosition(const Scrollbar& scrollbar, |
| float scroll_position) { |
| if (scrollbar.Enabled()) { |
| float size = scrollbar.TotalSize() - scrollbar.VisibleSize(); |
| // Avoid doing a floating point divide by zero and return 1 when |
| // usedTotalSize == visibleSize. |
| if (!size) |
| return 0; |
| float pos = std::max(0.0f, scroll_position) * |
| (TrackLength(scrollbar) - ThumbLength(scrollbar)) / size; |
| return (pos < 1 && pos > 0) ? 1 : pos; |
| } |
| return 0; |
| } |
| |
| int ScrollbarTheme::ThumbLength(const Scrollbar& scrollbar) { |
| if (!scrollbar.Enabled()) |
| return 0; |
| |
| float overhang = fabsf(scrollbar.ElasticOverscroll()); |
| float proportion = 0.0f; |
| float total_size = scrollbar.TotalSize(); |
| if (total_size > 0.0f) { |
| proportion = (scrollbar.VisibleSize() - overhang) / total_size; |
| } |
| int track_len = TrackLength(scrollbar); |
| int length = round(proportion * track_len); |
| length = std::max(length, MinimumThumbLength(scrollbar)); |
| if (length > track_len) |
| length = 0; // Once the thumb is below the track length, it just goes away |
| // (to make more room for the track). |
| return length; |
| } |
| |
| int ScrollbarTheme::TrackPosition(const Scrollbar& scrollbar) { |
| IntRect constrained_track_rect = |
| ConstrainTrackRectToTrackPieces(scrollbar, TrackRect(scrollbar)); |
| return (scrollbar.Orientation() == kHorizontalScrollbar) |
| ? constrained_track_rect.X() - scrollbar.X() |
| : constrained_track_rect.Y() - scrollbar.Y(); |
| } |
| |
| int ScrollbarTheme::TrackLength(const Scrollbar& scrollbar) { |
| IntRect constrained_track_rect = |
| ConstrainTrackRectToTrackPieces(scrollbar, TrackRect(scrollbar)); |
| return (scrollbar.Orientation() == kHorizontalScrollbar) |
| ? constrained_track_rect.Width() |
| : constrained_track_rect.Height(); |
| } |
| |
| IntRect ScrollbarTheme::ThumbRect(const Scrollbar& scrollbar) { |
| if (!HasThumb(scrollbar)) |
| return IntRect(); |
| |
| IntRect track = TrackRect(scrollbar); |
| IntRect start_track_rect; |
| IntRect thumb_rect; |
| IntRect end_track_rect; |
| SplitTrack(scrollbar, track, start_track_rect, thumb_rect, end_track_rect); |
| |
| return thumb_rect; |
| } |
| |
| int ScrollbarTheme::ThumbThickness(const Scrollbar& scrollbar) { |
| IntRect track = TrackRect(scrollbar); |
| return scrollbar.Orientation() == kHorizontalScrollbar ? track.Height() |
| : track.Width(); |
| } |
| |
| void ScrollbarTheme::SplitTrack(const Scrollbar& scrollbar, |
| const IntRect& unconstrained_track_rect, |
| IntRect& before_thumb_rect, |
| IntRect& thumb_rect, |
| IntRect& after_thumb_rect) { |
| // This function won't even get called unless we're big enough to have some |
| // combination of these three rects where at least one of them is non-empty. |
| IntRect track_rect = |
| ConstrainTrackRectToTrackPieces(scrollbar, unconstrained_track_rect); |
| int thumb_pos = ThumbPosition(scrollbar); |
| if (scrollbar.Orientation() == kHorizontalScrollbar) { |
| thumb_rect = IntRect(track_rect.X() + thumb_pos, track_rect.Y(), |
| ThumbLength(scrollbar), scrollbar.Height()); |
| before_thumb_rect = |
| IntRect(track_rect.X(), track_rect.Y(), |
| thumb_pos + thumb_rect.Width() / 2, track_rect.Height()); |
| after_thumb_rect = IntRect( |
| track_rect.X() + before_thumb_rect.Width(), track_rect.Y(), |
| track_rect.MaxX() - before_thumb_rect.MaxX(), track_rect.Height()); |
| } else { |
| thumb_rect = IntRect(track_rect.X(), track_rect.Y() + thumb_pos, |
| scrollbar.Width(), ThumbLength(scrollbar)); |
| before_thumb_rect = |
| IntRect(track_rect.X(), track_rect.Y(), track_rect.Width(), |
| thumb_pos + thumb_rect.Height() / 2); |
| after_thumb_rect = IntRect( |
| track_rect.X(), track_rect.Y() + before_thumb_rect.Height(), |
| track_rect.Width(), track_rect.MaxY() - before_thumb_rect.MaxY()); |
| } |
| } |
| |
| TimeDelta ScrollbarTheme::InitialAutoscrollTimerDelay() { |
| return TimeDelta::FromMilliseconds(250); |
| } |
| |
| TimeDelta ScrollbarTheme::AutoscrollTimerDelay() { |
| return TimeDelta::FromMilliseconds(50); |
| } |
| |
| ScrollbarTheme& ScrollbarTheme::DeprecatedStaticGetTheme() { |
| if (ScrollbarTheme::MockScrollbarsEnabled()) { |
| if (RuntimeEnabledFeatures::OverlayScrollbarsEnabled()) { |
| DEFINE_STATIC_LOCAL(ScrollbarThemeOverlayMock, overlay_mock_theme, ()); |
| return overlay_mock_theme; |
| } |
| |
| DEFINE_STATIC_LOCAL(ScrollbarThemeMock, mock_theme, ()); |
| return mock_theme; |
| } |
| return NativeTheme(); |
| } |
| |
| void ScrollbarTheme::SetMockScrollbarsEnabled(bool flag) { |
| g_mock_scrollbars_enabled_ = flag; |
| } |
| |
| bool ScrollbarTheme::MockScrollbarsEnabled() { |
| return g_mock_scrollbars_enabled_; |
| } |
| |
| DisplayItem::Type ScrollbarTheme::ButtonPartToDisplayItemType( |
| ScrollbarPart part) { |
| switch (part) { |
| case kBackButtonStartPart: |
| return DisplayItem::kScrollbarBackButtonStart; |
| case kBackButtonEndPart: |
| return DisplayItem::kScrollbarBackButtonEnd; |
| case kForwardButtonStartPart: |
| return DisplayItem::kScrollbarForwardButtonStart; |
| case kForwardButtonEndPart: |
| return DisplayItem::kScrollbarForwardButtonEnd; |
| default: |
| NOTREACHED(); |
| return DisplayItem::kScrollbarBackButtonStart; |
| } |
| } |
| |
| DisplayItem::Type ScrollbarTheme::TrackPiecePartToDisplayItemType( |
| ScrollbarPart part) { |
| switch (part) { |
| case kBackTrackPart: |
| return DisplayItem::kScrollbarBackTrack; |
| case kForwardTrackPart: |
| return DisplayItem::kScrollbarForwardTrack; |
| default: |
| NOTREACHED(); |
| return DisplayItem::kScrollbarBackTrack; |
| } |
| } |
| |
| } // namespace blink |