blob: 599767220466b7f606aaa088267ee730843b8672 [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 "core/paint/ScrollableAreaPainter.h"
#include "core/layout/LayoutView.h"
#include "core/page/Page.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/PaintLayerScrollableArea.h"
#include "core/paint/ScrollbarPainter.h"
#include "core/paint/TransformRecorder.h"
#include "platform/PlatformChromeClient.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/paint/ClipRecorder.h"
#include "platform/graphics/paint/DrawingRecorder.h"
#include "platform/graphics/paint/ScopedPaintChunkProperties.h"
namespace blink {
void ScrollableAreaPainter::PaintResizer(GraphicsContext& context,
const IntPoint& paint_offset,
const CullRect& cull_rect) {
if (GetScrollableArea().GetLayoutBox()->Style()->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.IntersectsCullRect(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_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;
Optional<ClipRecorder> clip_recorder;
Optional<ScopedPaintChunkProperties> scoped_paint_chunk_properties;
if (RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) {
const auto& box = *GetScrollableArea().GetLayoutBox();
if (const auto* fragment = paint_info.FragmentToPaint(box)) {
const auto* properties = fragment->PaintProperties();
DCHECK(properties);
if (const auto* clip = properties->OverflowControlsClip()) {
scoped_paint_chunk_properties.emplace(
context.GetPaintController(), clip, box,
DisplayItem::kClipLayerOverflowControls);
}
}
} else {
IntRect clip_rect(adjusted_paint_offset,
GetScrollableArea().Layer()->PixelSnappedSize());
clip_recorder.emplace(context, *GetScrollableArea().GetLayoutBox(),
DisplayItem::kClipLayerOverflowControls, clip_rect);
}
if (GetScrollableArea().HorizontalScrollbar() &&
!GetScrollableArea().LayerForHorizontalScrollbar()) {
TransformRecorder translate_recorder(
context, *GetScrollableArea().HorizontalScrollbar(),
AffineTransform::Translation(adjusted_paint_offset.X(),
adjusted_paint_offset.Y()));
GetScrollableArea().HorizontalScrollbar()->Paint(context,
adjusted_cull_rect);
}
if (GetScrollableArea().VerticalScrollbar() &&
!GetScrollableArea().LayerForVerticalScrollbar()) {
TransformRecorder translate_recorder(
context, *GetScrollableArea().VerticalScrollbar(),
AffineTransform::Translation(adjusted_paint_offset.X(),
adjusted_paint_offset.Y()));
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.IntersectsCullRect(
GetScrollableArea().RectForHorizontalScrollbar(border_box)))
return true;
if (cull_rect.IntersectsCullRect(
GetScrollableArea().RectForVerticalScrollbar(border_box)))
return true;
if (cull_rect.IntersectsCullRect(GetScrollableArea().ScrollCornerRect()))
return true;
if (cull_rect.IntersectsCullRect(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.IntersectsCullRect(abs_rect))
return;
ScrollbarPainter::PaintIntoRect(*scroll_corner, context, paint_offset,
LayoutRect(abs_rect));
return;
}
// We don't want to paint white if we have overlay scrollbars, since we need
// to see what is behind it.
if (GetScrollableArea().HasOverlayScrollbars())
return;
const auto& client = DisplayItemClientForCorner();
if (DrawingRecorder::UseCachedDrawingIfPossible(
context, client, DisplayItem::kScrollbarCorner))
return;
DrawingRecorder recorder(context, client, DisplayItem::kScrollbarCorner);
context.FillRect(abs_rect, Color::kWhite);
}
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