910e62b5创建于 1月15日历史提交
// 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