blob: a3cc878d4deea886fcd0d6c79ef2cfc85a2fc40e [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/frame/contents_container_outline.h"
#include "base/i18n/rtl.h"
#include "chrome/browser/ui/ui_features.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPathBuilder.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/view_class_properties.h"
ContentsContainerOutline::ContentsContainerOutline(views::View* mini_toolbar)
: mini_toolbar_(mini_toolbar) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
SetCanProcessEventsWithinSubtree(false);
SetVisible(false);
GetViewAccessibility().SetIsInvisible(true);
view_bounds_observer_.Observe(mini_toolbar);
}
ContentsContainerOutline::~ContentsContainerOutline() = default;
// static
int ContentsContainerOutline::GetThickness(bool is_highlighted) {
return is_highlighted ? kHighlightThickness : kThickness;
}
// static
ui::ColorId ContentsContainerOutline::GetColor(bool is_active,
bool is_highlighted) {
if (is_active) {
return is_highlighted ? kColorMultiContentsViewHighlightContentOutline
: kColorMultiContentsViewActiveContentOutline;
}
return kColorMultiContentsViewInactiveContentOutline;
}
void ContentsContainerOutline::UpdateState(bool is_active,
bool is_highlighted) {
is_active_ = is_active;
is_highlighted_ = is_highlighted;
SetVisible(true);
SchedulePaint();
}
void ContentsContainerOutline::OnPaint(gfx::Canvas* canvas) {
// Draw the bordering stroke.
cc::PaintFlags flags;
flags.setStrokeWidth(GetThickness(is_highlighted_));
flags.setColor(
GetColorProvider()->GetColor(GetColor(is_active_, is_highlighted_)));
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setAntiAlias(true);
gfx::RectF local_bounds(GetLocalBounds());
const float half_thickness = GetThickness(is_highlighted_) / 2.0f;
local_bounds.Inset(half_thickness);
const float corner_radius = kCornerRadius - half_thickness;
// Generate the outline path starting from the left edge right below the
// rounded rect arc and then conditionally either raws out a rounded rect
// or a path that overlaps with the mini-toolbar view in clockwise direction.
SkPathBuilder path;
path.moveTo(local_bounds.x(), local_bounds.y() + corner_radius);
path.arcTo(SkVector(corner_radius, corner_radius), 0.0f,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(local_bounds.x() + corner_radius, local_bounds.y()));
path.lineTo(local_bounds.right() - corner_radius, local_bounds.y());
path.arcTo(SkVector(corner_radius, corner_radius), 0.0f,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(local_bounds.right(), local_bounds.y() + corner_radius));
if (is_active_ &&
(is_highlighted_ ||
features::kSideBySideMiniToolbarActiveConfiguration.Get() ==
features::MiniToolbarActiveConfiguration::Hide)) {
// If the mini toolbar is hidden on active view, just draw the rounded rect.
path.lineTo(local_bounds.right(), local_bounds.bottom() - corner_radius);
path.arcTo(SkVector(corner_radius, corner_radius), 0.0f,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(local_bounds.right() - corner_radius,
local_bounds.bottom()));
} else {
// Draw the path around the mini toolbar. This uses a corner radius which
// is half thickness greater than the clip path. The outline path needs
// to match the clip path.
CHECK(mini_toolbar_.get());
const gfx::SizeF mini_toolbar_size(mini_toolbar_->size());
path.lineTo(local_bounds.right(),
local_bounds.bottom() - mini_toolbar_size.height());
path.arcTo(SkVector(kCornerRadius, kCornerRadius), 0,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(local_bounds.right() - kCornerRadius,
local_bounds.bottom() - mini_toolbar_size.height() +
kCornerRadius));
path.lineTo(
local_bounds.right() - mini_toolbar_size.width() + kCornerRadius * 2,
local_bounds.bottom() - mini_toolbar_size.height() + kCornerRadius);
path.arcTo(SkVector(kCornerRadius, kCornerRadius), 0,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCCW,
SkPoint(local_bounds.right() - mini_toolbar_size.width() +
kCornerRadius,
local_bounds.bottom() - mini_toolbar_size.height() +
kCornerRadius * 2));
path.lineTo(
local_bounds.right() - mini_toolbar_size.width() + kCornerRadius,
local_bounds.bottom() - kCornerRadius);
path.arcTo(SkVector(kCornerRadius, kCornerRadius), 0,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(local_bounds.right() - mini_toolbar_size.width(),
local_bounds.bottom()));
}
path.lineTo(local_bounds.x() + corner_radius, local_bounds.bottom());
path.arcTo(SkVector(corner_radius, corner_radius), 0.0f,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(local_bounds.x(), local_bounds.bottom() - corner_radius));
path.close();
if (base::i18n::IsRTL()) {
// Mirror the path for outline in case of RTL.
gfx::PointF center = gfx::RectF(GetLocalBounds()).CenterPoint();
SkMatrix flip;
flip.setScale(-1, 1, center.x(), center.y());
path.transform(flip);
}
canvas->DrawPath(path.detach(), flags);
}
void ContentsContainerOutline::OnViewBoundsChanged(views::View* observed_view) {
CHECK(observed_view == mini_toolbar_.get());
SetClipPath();
SchedulePaint();
}
void ContentsContainerOutline::OnViewIsDeleting(views::View* observed_view) {
CHECK(observed_view == mini_toolbar_.get());
view_bounds_observer_.Reset();
mini_toolbar_ = nullptr;
}
void ContentsContainerOutline::SetClipPath() {
// Set clip path on the mini toolbar view, it uses half thickness less corner
// radius than the outline. This path needs to match the outline path.
gfx::Rect mini_toolbar_rect(mini_toolbar_->GetLocalBounds());
const float half_thickness = GetThickness(is_highlighted_) / 2.0f;
mini_toolbar_rect.Inset(half_thickness);
const float corner_radius = kCornerRadius - half_thickness;
SkPathBuilder clip_path;
clip_path.moveTo(mini_toolbar_rect.right(), mini_toolbar_rect.y());
clip_path.arcTo(SkVector(corner_radius, corner_radius), 0,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(mini_toolbar_rect.right() - corner_radius,
mini_toolbar_rect.y() + corner_radius));
clip_path.lineTo(mini_toolbar_rect.x() + corner_radius * 2,
mini_toolbar_rect.y() + corner_radius);
clip_path.arcTo(SkVector(corner_radius, corner_radius), 0,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCCW,
SkPoint(mini_toolbar_rect.x() + corner_radius,
mini_toolbar_rect.y() + corner_radius * 2));
clip_path.lineTo(mini_toolbar_rect.x() + corner_radius,
mini_toolbar_rect.bottom() - corner_radius);
clip_path.arcTo(SkVector(corner_radius, corner_radius), 0,
SkPathBuilder::kSmall_ArcSize, SkPathDirection::kCW,
SkPoint(mini_toolbar_rect.x(), mini_toolbar_rect.bottom()));
clip_path.lineTo(mini_toolbar_rect.right(), mini_toolbar_rect.bottom());
clip_path.lineTo(mini_toolbar_rect.right(), mini_toolbar_rect.y());
if (base::i18n::IsRTL()) {
// Mirror the clip path in case of RTL.
gfx::PointF center =
gfx::RectF(mini_toolbar_->GetLocalBounds()).CenterPoint();
SkMatrix flip;
flip.setScale(-1, 1, center.x(), center.y());
clip_path.transform(flip);
}
mini_toolbar_->SetClipPath(clip_path.detach());
}
BEGIN_METADATA(ContentsContainerOutline)
END_METADATA