Files
Simon Hong d4ca8edbfd Skip repaint in ViewShadow::SetCornerRadii when radii are unchanged (#35987)
We don't need to repaint the shadow when radii is not changed.

Add an early return when the new value equals the stored one to avoid
unnecessary repaints.

Resolves brave/brave-browser#55024
2026-04-29 13:07:44 +09:00

174 lines
5.4 KiB
C++

/* Copyright (c) 2023 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "brave/browser/ui/views/view_shadow.h"
#include <array>
#include <map>
#include <memory>
#include <tuple>
#include "base/check.h"
#include "base/check_op.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/view.h"
namespace {
const gfx::ShadowValues& GetCachedShadowValues(
const ViewShadow::ShadowParameters& params) {
using ShadowKey = std::tuple<int, int, int, SkColor>;
static base::NoDestructor<std::map<ShadowKey, gfx::ShadowValues>> map;
ShadowKey key(params.offset_x, params.offset_y, params.blur_radius,
params.shadow_color);
if (auto iter = map->find(key); iter != map->end()) {
return iter->second;
}
// In Skia, the blur value refers to the blur distance both inside and
// outside of the rectangle. Double the `blur_radius` parameter to convert
// the CSS-style blur value to the Skia blur value.
double blur = params.blur_radius * 2;
gfx::ShadowValue shadow_value({params.offset_x, params.offset_y}, blur,
params.shadow_color);
auto result = map->emplace(key, gfx::ShadowValues{shadow_value});
return result.first->second;
}
} // namespace
ViewShadow::ViewShadow(views::View* view,
const gfx::RoundedCornersF& corner_radii,
const ShadowParameters& params)
: layer_owner_(std::make_unique<ui::Layer>()),
view_(view),
corner_radii_(corner_radii),
shadow_values_(GetCachedShadowValues(params)) {
DCHECK(view_);
if (!view_->layer()) {
view_->SetPaintToLayer();
}
shadow_layer()->set_delegate(this);
shadow_layer()->SetFillsBoundsOpaquely(false);
view_->AddLayerToRegion(shadow_layer(), views::LayerRegion::kBelow);
view_->AddObserver(this);
layer_owner_observation_.Observe(&layer_owner_);
OnViewLayerBoundsSet(view_);
}
ViewShadow::~ViewShadow() {
if (view_) {
OnViewIsDeleting(view_);
}
}
void ViewShadow::SetCornerRadii(const gfx::RoundedCornersF& corner_radii) {
if (corner_radii_ == corner_radii) {
return;
}
corner_radii_ = corner_radii;
shadow_layer()->SchedulePaint(gfx::Rect(shadow_layer()->size()));
}
void ViewShadow::SetInsets(const gfx::Insets& insets) {
insets_ = insets;
UpdateBounds();
}
void ViewShadow::SetVisible(bool visible) {
shadow_layer()->SetVisible(visible);
}
void ViewShadow::OnViewLayerBoundsSet(views::View* view) {
DCHECK(view->layer());
DCHECK_EQ(view, view_.get());
UpdateBounds();
}
void ViewShadow::OnViewIsDeleting(views::View* view) {
layer_owner_observation_.Reset();
view_->RemoveObserver(this);
view_ = nullptr;
}
void ViewShadow::OnLayerRecreated(ui::Layer* old_layer) {
if (!view_) {
return;
}
// During the window closing, shadow layer seems destroyed first
// before |view_| is destroying in some situation.
// Crash happens when view tree layouts w/o removing it from layer tree.
view_->RemoveLayerFromRegionsKeepInLayerTree(old_layer);
layer_owner_observation_.Reset();
}
void ViewShadow::OnPaintLayer(const ui::PaintContext& context) {
ui::PaintRecorder recorder(context, shadow_layer()->size());
// Clear out the canvas so that transparency can be applied properly.
recorder.canvas()->DrawColor(SK_ColorTRANSPARENT);
cc::PaintFlags flags;
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setAntiAlias(true);
flags.setColor(SK_ColorTRANSPARENT);
flags.setLooper(gfx::CreateShadowDrawLooper(*shadow_values_));
gfx::Rect shadow_bounds(shadow_layer()->size());
shadow_bounds.Inset(gfx::ShadowValue::GetBlurRegion(*shadow_values_));
gfx::Canvas* canvas = recorder.canvas();
gfx::ScopedCanvas scoped_canvas(canvas);
const float scale = canvas->UndoDeviceScaleFactor();
const gfx::Rect scaled_bounds =
gfx::ScaleToEnclosedRect(shadow_bounds, scale);
gfx::RectF rect_f(scaled_bounds);
const std::array<SkVector, 4> scaled_radii = {
{{corner_radii_.upper_left() * scale, corner_radii_.upper_left() * scale},
{corner_radii_.upper_right() * scale,
corner_radii_.upper_right() * scale},
{corner_radii_.lower_right() * scale,
corner_radii_.lower_right() * scale},
{corner_radii_.lower_left() * scale,
corner_radii_.lower_left() * scale}}};
const SkPath path = SkPath::RRect(
SkRRect::MakeRectRadii(gfx::RectFToSkRect(rect_f), scaled_radii.data()));
canvas->DrawPath(path, flags);
}
void ViewShadow::OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) {}
void ViewShadow::UpdateBounds() {
if (!view_) {
return;
}
// Expand the bounds of the specified view by the blur region.
gfx::Rect shadow_bounds = view_->layer()->bounds();
shadow_bounds.Inset(insets_ -
gfx::ShadowValue::GetBlurRegion(*shadow_values_));
shadow_layer()->SetBounds(shadow_bounds);
}