blob: e07b50a89775e259bc2ff151695aeb284cc44e82 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/scrollable_area_painter.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/object_paint_properties.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/scrollbar_painter.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_theme.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/graphics_layer.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h"
namespace blink {
void ScrollableAreaPainter::PaintResizer(GraphicsContext& context,
const IntPoint& paint_offset,
const CullRect& cull_rect) {
if (GetScrollableArea().GetLayoutBox()->StyleRef().Resize() == EResize::kNone)
return;
IntRect abs_rect = GetScrollableArea().ResizerCornerRect(
GetScrollableArea().GetLayoutBox()->PixelSnappedBorderBoxRect(
GetScrollableArea().Layer()->SubpixelAccumulation()),
kResizerForPointer);
if (abs_rect.IsEmpty())
return;
abs_rect.MoveBy(paint_offset);
if (const auto* resizer = GetScrollableArea().Resizer()) {
if (!cull_rect.Intersects(abs_rect))
return;
ScrollbarPainter::PaintIntoRect(*resizer, context, paint_offset,
LayoutRect(abs_rect));
return;
}
const auto& client = DisplayItemClientForCorner();
if (DrawingRecorder::UseCachedDrawingIfPossible(context, client,
DisplayItem::kResizer))
return;
DrawingRecorder recorder(context, client, DisplayItem::kResizer);
DrawPlatformResizerImage(context, abs_rect);
// Draw a frame around the resizer (1px grey line) if there are any scrollbars
// present. Clipping will exclude the right and bottom edges of this frame.
if (!GetScrollableArea().HasOverlayScrollbars() &&
GetScrollableArea().HasScrollbar()) {
GraphicsContextStateSaver state_saver(context);
context.Clip(abs_rect);
IntRect larger_corner = abs_rect;
larger_corner.SetSize(
IntSize(larger_corner.Width() + 1, larger_corner.Height() + 1));
context.SetStrokeColor(Color(217, 217, 217));
context.SetStrokeThickness(1.0f);
context.SetFillColor(Color::kTransparent);
context.DrawRect(larger_corner);
}
}
void ScrollableAreaPainter::DrawPlatformResizerImage(
GraphicsContext& context,
IntRect resizer_corner_rect) {
IntPoint points[4];
bool on_left = false;
if (GetScrollableArea()
.GetLayoutBox()
->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
on_left = true;
points[0].SetX(resizer_corner_rect.X() + 1);
points[1].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() -
resizer_corner_rect.Width() / 2);
points[2].SetX(points[0].X());
points[3].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() -
resizer_corner_rect.Width() * 3 / 4);
} else {
points[0].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() - 1);
points[1].SetX(resizer_corner_rect.X() + resizer_corner_rect.Width() / 2);
points[2].SetX(points[0].X());
points[3].SetX(resizer_corner_rect.X() +
resizer_corner_rect.Width() * 3 / 4);
}
points[0].SetY(resizer_corner_rect.Y() + resizer_corner_rect.Height() / 2);
points[1].SetY(resizer_corner_rect.Y() + resizer_corner_rect.Height() - 1);
points[2].SetY(resizer_corner_rect.Y() +
resizer_corner_rect.Height() * 3 / 4);
points[3].SetY(points[1].Y());
PaintFlags paint_flags;
paint_flags.setStyle(PaintFlags::kStroke_Style);
paint_flags.setStrokeWidth(1);
SkPath line_path;
// Draw a dark line, to ensure contrast against a light background
line_path.moveTo(points[0].X(), points[0].Y());
line_path.lineTo(points[1].X(), points[1].Y());
line_path.moveTo(points[2].X(), points[2].Y());
line_path.lineTo(points[3].X(), points[3].Y());
paint_flags.setColor(SkColorSetARGB(153, 0, 0, 0));
context.DrawPath(line_path, paint_flags);
// Draw a light line one pixel below the light line,
// to ensure contrast against a dark background
line_path.rewind();
line_path.moveTo(points[0].X(), points[0].Y() + 1);
line_path.lineTo(points[1].X() + (on_left ? -1 : 1), points[1].Y());
line_path.moveTo(points[2].X(), points[2].Y() + 1);
line_path.lineTo(points[3].X() + (on_left ? -1 : 1), points[3].Y());
paint_flags.setColor(SkColorSetARGB(153, 255, 255, 255));
context.DrawPath(line_path, paint_flags);
}
void ScrollableAreaPainter::PaintOverflowControls(
const PaintInfo& paint_info,
const IntPoint& paint_offset,
bool painting_overlay_controls) {
// Don't do anything if we have no overflow.
if (!GetScrollableArea().GetLayoutBox()->HasOverflowClip())
return;
IntPoint adjusted_paint_offset = paint_offset;
if (painting_overlay_controls)
adjusted_paint_offset = GetScrollableArea().CachedOverlayScrollbarOffset();
CullRect adjusted_cull_rect = paint_info.GetCullRect();
adjusted_cull_rect.MoveBy(-adjusted_paint_offset);
// Overlay scrollbars paint in a second pass through the layer tree so that
// they will paint on top of everything else. If this is the normal painting
// pass, paintingOverlayControls will be false, and we should just tell the
// root layer that there are overlay scrollbars that need to be painted. That
// will cause the second pass through the layer tree to run, and we'll paint
// the scrollbars then. In the meantime, cache tx and ty so that the second
// pass doesn't need to re-enter the LayoutTree to get it right.
if (GetScrollableArea().HasOverlayScrollbars() &&
!painting_overlay_controls) {
GetScrollableArea().SetCachedOverlayScrollbarOffset(paint_offset);
// It's not necessary to do the second pass if the scrollbars paint into
// layers.
if ((GetScrollableArea().HorizontalScrollbar() &&
GetScrollableArea().LayerForHorizontalScrollbar()) ||
(GetScrollableArea().VerticalScrollbar() &&
GetScrollableArea().LayerForVerticalScrollbar()))
return;
if (!OverflowControlsIntersectRect(adjusted_cull_rect))
return;
LayoutView* layout_view = GetScrollableArea().GetLayoutBox()->View();
PaintLayer* painting_root =
GetScrollableArea().Layer()->EnclosingLayerWithCompositedLayerMapping(
kIncludeSelf);
if (!painting_root)
painting_root = layout_view->Layer();
painting_root->SetContainsDirtyOverlayScrollbars(true);
return;
}
// This check is required to avoid painting custom CSS scrollbars twice.
if (painting_overlay_controls && !GetScrollableArea().HasOverlayScrollbars())
return;
GraphicsContext& context = paint_info.context;
const auto& box = *GetScrollableArea().GetLayoutBox();
const auto* fragment = paint_info.FragmentToPaint(box);
if (!fragment)
return;
base::Optional<ScopedPaintChunkProperties> scoped_paint_chunk_properties;
const auto* properties = fragment->PaintProperties();
// TODO(crbug.com/849278): Remove either the DCHECK or the if condition
// when we figure out in what cases that the box doesn't have properties.
DCHECK(properties);
if (properties) {
if (const auto* clip = properties->OverflowControlsClip()) {
scoped_paint_chunk_properties.emplace(context.GetPaintController(), *clip,
box,
DisplayItem::kOverflowControls);
}
}
if (GetScrollableArea().HorizontalScrollbar() &&
!GetScrollableArea().LayerForHorizontalScrollbar()) {
GetScrollableArea().HorizontalScrollbar()->Paint(context,
adjusted_cull_rect);
}
if (GetScrollableArea().VerticalScrollbar() &&
!GetScrollableArea().LayerForVerticalScrollbar()) {
GetScrollableArea().VerticalScrollbar()->Paint(context, adjusted_cull_rect);
}
if (!GetScrollableArea().LayerForScrollCorner()) {
// We fill our scroll corner with white if we have a scrollbar that doesn't
// run all the way up to the edge of the box.
PaintScrollCorner(context, adjusted_paint_offset, paint_info.GetCullRect());
// Paint our resizer last, since it sits on top of the scroll corner.
PaintResizer(context, adjusted_paint_offset, paint_info.GetCullRect());
}
}
bool ScrollableAreaPainter::OverflowControlsIntersectRect(
const CullRect& cull_rect) const {
const IntRect border_box =
GetScrollableArea().GetLayoutBox()->PixelSnappedBorderBoxRect(
GetScrollableArea().Layer()->SubpixelAccumulation());
if (cull_rect.Intersects(
GetScrollableArea().RectForHorizontalScrollbar(border_box)))
return true;
if (cull_rect.Intersects(
GetScrollableArea().RectForVerticalScrollbar(border_box)))
return true;
if (cull_rect.Intersects(GetScrollableArea().ScrollCornerRect()))
return true;
if (cull_rect.Intersects(GetScrollableArea().ResizerCornerRect(
border_box, kResizerForPointer)))
return true;
return false;
}
void ScrollableAreaPainter::PaintScrollCorner(
GraphicsContext& context,
const IntPoint& paint_offset,
const CullRect& adjusted_cull_rect) {
IntRect abs_rect = GetScrollableArea().ScrollCornerRect();
if (abs_rect.IsEmpty())
return;
abs_rect.MoveBy(paint_offset);
if (const auto* scroll_corner = GetScrollableArea().ScrollCorner()) {
if (!adjusted_cull_rect.Intersects(abs_rect))
return;
ScrollbarPainter::PaintIntoRect(*scroll_corner, context, paint_offset,
LayoutRect(abs_rect));
return;
}
// We don't want to paint opaque if we have overlay scrollbars, since we need
// to see what is behind it.
if (GetScrollableArea().HasOverlayScrollbars())
return;
ScrollbarTheme* theme = nullptr;
if (GetScrollableArea().HorizontalScrollbar()) {
theme = &GetScrollableArea().HorizontalScrollbar()->GetTheme();
} else if (GetScrollableArea().VerticalScrollbar()) {
theme = &GetScrollableArea().VerticalScrollbar()->GetTheme();
} else {
NOTREACHED();
}
const auto& client = DisplayItemClientForCorner();
theme->PaintScrollCorner(context, client, abs_rect);
}
PaintLayerScrollableArea& ScrollableAreaPainter::GetScrollableArea() const {
return *scrollable_area_;
}
const DisplayItemClient& ScrollableAreaPainter::DisplayItemClientForCorner()
const {
if (const auto* graphics_layer = GetScrollableArea().LayerForScrollCorner())
return *graphics_layer;
return *GetScrollableArea().GetLayoutBox();
}
} // namespace blink