/* * Copyright (C) 2008, 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_mac.h" #include "base/memory/scoped_policy.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/blink/public/common/input/web_mouse_event.h" #include "third_party/blink/public/platform/mac/web_scrollbar_theme.h" #include "third_party/blink/public/platform/web_theme_engine.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/scroll/mac_scrollbar_animator.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/drawing_recorder.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/theme/web_theme_engine_helper.h" #include "third_party/blink/renderer/platform/wtf/hash_set.h" #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" namespace blink { static float s_initial_button_delay = 0.5f; static float s_autoscroll_button_delay = 0.05f; static bool s_prefer_overlay_scroller_style = false; static bool s_jump_on_track_click = false; typedef HeapHashSet> ScrollbarSet; static ScrollbarSet& GetScrollbarSet() { DEFINE_STATIC_LOCAL(Persistent, set, (MakeGarbageCollected())); return *set; } // Values returned by NSScrollerImp's methods for querying sizes of various // elements. struct NSScrollerImpValues { float track_width; float track_box_width; float knob_min_length; float track_overlap_end_inset; float knob_overlap_end_inset; float track_end_inset; float knob_end_inset; }; const NSScrollerImpValues& GetScrollbarPainterValues(bool overlay, EScrollbarWidth width) { static NSScrollerImpValues overlay_small_values = { 14.0, 14.0, 26.0, 0.0, 0.0, 0.0, 1.0, }; static NSScrollerImpValues overlay_regular_values = { 16.0, 16.0, 26.0, 0.0, 0.0, 0.0, 1.0, }; static NSScrollerImpValues legacy_small_values = { 11.0, 11.0, 16.0, 0.0, 0.0, 0.0, 2.0, }; static NSScrollerImpValues legacy_regular_values = { 15.0, 15.0, 20.0, 0.0, 0.0, 0.0, 2.0, }; if (overlay) { return (width == EScrollbarWidth::kThin) ? overlay_small_values : overlay_regular_values; } else { return (width == EScrollbarWidth::kThin) ? legacy_small_values : legacy_regular_values; } } const NSScrollerImpValues& GetScrollbarPainterValues( const Scrollbar& scrollbar) { return GetScrollbarPainterValues( ScrollbarThemeMac::PreferOverlayScrollerStyle(), scrollbar.CSSScrollbarWidth()); } ScrollbarThemeMac::ScrollbarThemeMac() {} ScrollbarTheme& ScrollbarTheme::NativeTheme() { DEFINE_STATIC_LOCAL(ScrollbarThemeMac, overlay_theme, ()); return overlay_theme; } void ScrollbarThemeMac::PaintTickmarks(GraphicsContext& context, const Scrollbar& scrollbar, const gfx::Rect& rect) { gfx::Rect tickmark_track_rect = rect; tickmark_track_rect.set_x(tickmark_track_rect.x() + 1); tickmark_track_rect.set_width(tickmark_track_rect.width() - 1); ScrollbarTheme::PaintTickmarks(context, scrollbar, tickmark_track_rect); } bool ScrollbarThemeMac::ShouldCenterOnThumb(const Scrollbar& scrollbar, const WebMouseEvent& event) { bool alt_key_pressed = event.GetModifiers() & WebInputEvent::kAltKey; return (event.button == WebPointerProperties::Button::kLeft) && (s_jump_on_track_click != alt_key_pressed); } ScrollbarThemeMac::~ScrollbarThemeMac() {} base::TimeDelta ScrollbarThemeMac::InitialAutoscrollTimerDelay() { return base::Seconds(s_initial_button_delay); } base::TimeDelta ScrollbarThemeMac::AutoscrollTimerDelay() { return base::Seconds(s_autoscroll_button_delay); } bool ScrollbarThemeMac::ShouldDragDocumentInsteadOfThumb( const Scrollbar&, const WebMouseEvent& event) { return (event.GetModifiers() & WebInputEvent::Modifiers::kAltKey) != 0; } ScrollbarPart ScrollbarThemeMac::PartsToInvalidateOnThumbPositionChange( const Scrollbar& scrollbar, float old_position, float new_position) const { // MacScrollbarAnimatorImpl will invalidate scrollbar parts if necessary. return kNoPart; } void ScrollbarThemeMac::RegisterScrollbar(Scrollbar& scrollbar) { GetScrollbarSet().insert(&scrollbar); } bool ScrollbarThemeMac::IsScrollbarRegistered(Scrollbar& scrollbar) const { return GetScrollbarSet().Contains(&scrollbar); } void ScrollbarThemeMac::SetNewPainterForScrollbar(Scrollbar& scrollbar) { UpdateEnabledState(scrollbar); UpdateScrollbarOverlayColorTheme(scrollbar); } WebThemeEngine::ExtraParams GetPaintParams(const Scrollbar& scrollbar, bool overlay) { WebThemeEngine::ExtraParams params; params.scrollbar_extra.orientation = WebThemeEngine::ScrollbarOrientation::kVerticalOnRight; if (scrollbar.Orientation() == kHorizontalScrollbar) { params.scrollbar_extra.orientation = WebThemeEngine::ScrollbarOrientation::kHorizontal; } else if (scrollbar.IsLeftSideVerticalScrollbar()) { params.scrollbar_extra.orientation = WebThemeEngine::ScrollbarOrientation::kVerticalOnLeft; } params.scrollbar_extra.scrollbar_theme = (scrollbar.UsedColorScheme() == mojom::blink::ColorScheme::kDark) ? mojom::blink::ColorScheme::kDark : mojom::blink::ColorScheme::kLight; params.scrollbar_extra.is_overlay = overlay; if (overlay) { params.scrollbar_extra.scrollbar_theme = (scrollbar.GetScrollbarOverlayColorTheme() == kScrollbarOverlayColorThemeLight) ? mojom::blink::ColorScheme::kDark : mojom::blink::ColorScheme::kLight; } params.scrollbar_extra.is_hovering = scrollbar.HoveredPart() != ScrollbarPart::kNoPart; params.scrollbar_extra.scale_from_dip = scrollbar.ScaleFromDIP(); return params; } void ScrollbarThemeMac::PaintTrack(GraphicsContext& context, const Scrollbar& scrollbar, const gfx::Rect& rect) { GraphicsContextStateSaver state_saver(context); context.Translate(rect.x(), rect.y()); auto* mac_scrollbar = MacScrollbar::GetForScrollbar(scrollbar); if (!mac_scrollbar) return; // The track opacity will be read from the ScrollbarPainter. float opacity = mac_scrollbar->GetTrackAlpha(); if (opacity == 0) return; if (opacity != 1) context.BeginLayer(opacity); WebThemeEngine::ExtraParams params = GetPaintParams(scrollbar, UsesOverlayScrollbars()); gfx::Rect bounds(0, 0, scrollbar.FrameRect().width(), scrollbar.FrameRect().height()); WebThemeEngine::Part track_part = params.scrollbar_extra.orientation == WebThemeEngine::ScrollbarOrientation::kHorizontal ? WebThemeEngine::Part::kPartScrollbarHorizontalTrack : WebThemeEngine::Part::kPartScrollbarVerticalTrack; WebThemeEngineHelper::GetNativeThemeEngine()->Paint( context.Canvas(), track_part, WebThemeEngine::State::kStateNormal, bounds, ¶ms, params.scrollbar_extra.scrollbar_theme); if (opacity != 1) context.EndLayer(); } void ScrollbarThemeMac::PaintScrollCorner( GraphicsContext& context, const Scrollbar* vertical_scrollbar, const DisplayItemClient& item, const gfx::Rect& rect, mojom::blink::ColorScheme color_scheme) { if (!vertical_scrollbar) { ScrollbarTheme::PaintScrollCorner(context, vertical_scrollbar, item, rect, color_scheme); return; } if (DrawingRecorder::UseCachedDrawingIfPossible(context, item, DisplayItem::kScrollCorner)) { return; } DrawingRecorder recorder(context, item, DisplayItem::kScrollCorner, rect); GraphicsContextStateSaver state_saver(context); context.Translate(rect.x(), rect.y()); gfx::Rect bounds(0, 0, rect.width(), rect.height()); WebThemeEngine::ExtraParams params = GetPaintParams(*vertical_scrollbar, UsesOverlayScrollbars()); WebThemeEngineHelper::GetNativeThemeEngine()->Paint( context.Canvas(), WebThemeEngine::Part::kPartScrollbarCorner, WebThemeEngine::State::kStateNormal, bounds, ¶ms, params.scrollbar_extra.scrollbar_theme); } void ScrollbarThemeMac::PaintThumbInternal(GraphicsContext& context, const Scrollbar& scrollbar, const gfx::Rect& rect, float opacity) { if (DrawingRecorder::UseCachedDrawingIfPossible( context, scrollbar, DisplayItem::kScrollbarThumb)) { return; } DrawingRecorder recorder(context, scrollbar, DisplayItem::kScrollbarThumb, rect); GraphicsContextStateSaver state_saver(context); context.Translate(rect.x(), rect.y()); gfx::Rect local_rect(rect.size()); if (!scrollbar.Enabled()) return; auto* mac_scrollbar = MacScrollbar::GetForScrollbar(scrollbar); if (!mac_scrollbar) return; // The thumb size will be read from the ScrollbarPainter. const int thumb_size = mac_scrollbar->GetTrackBoxWidth() * scrollbar.ScaleFromDIP(); WebThemeEngine::ExtraParams params = GetPaintParams(scrollbar, UsesOverlayScrollbars()); // Compute the bounds for the thumb, accounting for lack of engorgement. gfx::Rect bounds; switch (params.scrollbar_extra.orientation) { case WebThemeEngine::ScrollbarOrientation::kVerticalOnRight: bounds = gfx::Rect(rect.width() - thumb_size, 0, thumb_size, rect.height()); break; case WebThemeEngine::ScrollbarOrientation::kVerticalOnLeft: bounds = gfx::Rect(0, 0, thumb_size, rect.height()); break; case WebThemeEngine::ScrollbarOrientation::kHorizontal: bounds = gfx::Rect(0, rect.height() - thumb_size, rect.width(), thumb_size); break; } if (opacity != 1.0f) { gfx::RectF float_local_rect(local_rect); context.BeginLayer(opacity, SkBlendMode::kSrcOver, &float_local_rect); } WebThemeEngine::Part thumb_part = params.scrollbar_extra.orientation == WebThemeEngine::ScrollbarOrientation::kHorizontal ? WebThemeEngine::Part::kPartScrollbarHorizontalThumb : WebThemeEngine::Part::kPartScrollbarVerticalThumb; WebThemeEngineHelper::GetNativeThemeEngine()->Paint( context.Canvas(), thumb_part, WebThemeEngine::State::kStateNormal, bounds, ¶ms, params.scrollbar_extra.scrollbar_theme); if (opacity != 1.0f) context.EndLayer(); } int ScrollbarThemeMac::ScrollbarThickness(float scale_from_dip, EScrollbarWidth scrollbar_width) { if (scrollbar_width == EScrollbarWidth::kNone) return 0; const auto& painter_values = GetScrollbarPainterValues(UsesOverlayScrollbars(), scrollbar_width); return painter_values.track_box_width * scale_from_dip; } bool ScrollbarThemeMac::UsesOverlayScrollbars() const { return PreferOverlayScrollerStyle(); } void ScrollbarThemeMac::UpdateScrollbarOverlayColorTheme( const Scrollbar& scrollbar) { if (auto* mac_scrollbar = MacScrollbar::GetForScrollbar(scrollbar)) { mac_scrollbar->SetOverlayColorTheme( scrollbar.GetScrollbarOverlayColorTheme()); } } bool ScrollbarThemeMac::HasThumb(const Scrollbar& scrollbar) { const auto& painter_values = GetScrollbarPainterValues(scrollbar); int min_length_for_thumb = painter_values.knob_min_length + painter_values.track_overlap_end_inset + painter_values.knob_overlap_end_inset + 2 * (painter_values.track_end_inset + painter_values.knob_end_inset); return scrollbar.Enabled() && (scrollbar.Orientation() == kHorizontalScrollbar ? scrollbar.Width() : scrollbar.Height()) >= min_length_for_thumb; } gfx::Rect ScrollbarThemeMac::BackButtonRect(const Scrollbar& scrollbar) { return gfx::Rect(); } gfx::Rect ScrollbarThemeMac::ForwardButtonRect(const Scrollbar& scrollbar) { return gfx::Rect(); } gfx::Rect ScrollbarThemeMac::TrackRect(const Scrollbar& scrollbar) { return scrollbar.FrameRect(); } int ScrollbarThemeMac::MinimumThumbLength(const Scrollbar& scrollbar) { const auto& painter_values = GetScrollbarPainterValues(scrollbar); return painter_values.knob_min_length; } void ScrollbarThemeMac::UpdateEnabledState(const Scrollbar& scrollbar) { if (auto* mac_scrollbar = MacScrollbar::GetForScrollbar(scrollbar)) return mac_scrollbar->SetEnabled(scrollbar.Enabled()); } float ScrollbarThemeMac::Opacity(const Scrollbar& scrollbar) const { if (auto* mac_scrollbar = MacScrollbar::GetForScrollbar(scrollbar)) return mac_scrollbar->GetKnobAlpha(); return 1.f; } bool ScrollbarThemeMac::JumpOnTrackClick() const { return s_jump_on_track_click; } // static void ScrollbarThemeMac::UpdateScrollbarsWithNSDefaults( absl::optional initial_button_delay, absl::optional autoscroll_button_delay, bool prefer_overlay_scroller_style, bool redraw, bool jump_on_track_click) { s_initial_button_delay = initial_button_delay.value_or(s_initial_button_delay); s_autoscroll_button_delay = autoscroll_button_delay.value_or(s_autoscroll_button_delay); if (s_prefer_overlay_scroller_style != prefer_overlay_scroller_style) { s_prefer_overlay_scroller_style = prefer_overlay_scroller_style; Page::UsesOverlayScrollbarsChanged(); } s_jump_on_track_click = jump_on_track_click; if (redraw) { for (const auto& scrollbar : GetScrollbarSet()) { scrollbar->StyleChanged(); scrollbar->SetNeedsPaintInvalidation(kAllParts); } } } // static bool ScrollbarThemeMac::PreferOverlayScrollerStyle() { if (OverlayScrollbarsEnabled()) return true; return s_prefer_overlay_scroller_style; } } // namespace blink