chromium/ash/picker/views/picker_submenu_view.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/picker/views/picker_submenu_view.h"

#include <memory>
#include <utility>

#include "ash/picker/views/picker_list_item_view.h"
#include "ash/picker/views/picker_section_view.h"
#include "ash/picker/views/picker_style.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/background.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

namespace ash {
namespace {

constexpr int kSubmenuWidth = 256;
constexpr auto kInsets = gfx::Insets::VH(8, 0);
constexpr int kSubmenuHorizontalOverlap = 4;

std::unique_ptr<views::BubbleBorder> CreateBorder() {
  auto border = std::make_unique<views::BubbleBorder>(
      base::i18n::IsRTL() ? views::BubbleBorder::Arrow::RIGHT_TOP
                          : views::BubbleBorder::Arrow::LEFT_TOP,
      views::BubbleBorder::CHROMEOS_SYSTEM_UI_SHADOW);
  border->SetCornerRadius(kPickerContainerBorderRadius);
  border->SetColor(SK_ColorTRANSPARENT);
  return border;
}

}  // namespace

PickerSubmenuView::PickerSubmenuView(
    const gfx::Rect& anchor_rect,
    std::vector<std::unique_ptr<PickerListItemView>> items) {
  SetShowCloseButton(false);
  set_desired_bounds_delegate(
      base::BindRepeating(&PickerSubmenuView::GetDesiredBounds,
                          base::Unretained(this), anchor_rect));
  SetLayoutManager(std::make_unique<views::BoxLayout>(
                       views::BoxLayout::Orientation::kVertical,
                       /*inside_border_insets=*/kInsets))
      ->set_cross_axis_alignment(
          views::BoxLayout::CrossAxisAlignment::kStretch);
  SetBackground(views::CreateThemedRoundedRectBackground(
      kPickerContainerBackgroundColor, kPickerContainerBorderRadius));

  // Don't allow submenus within submenus.
  section_view_ = AddChildView(std::make_unique<PickerSectionView>(
      kSubmenuWidth, /*asset_fetcher=*/nullptr,
      /*submenu_controller=*/nullptr));

  for (std::unique_ptr<PickerListItemView>& item : items) {
    section_view_->AddListItem(std::move(item));
  }
}

PickerSubmenuView::~PickerSubmenuView() = default;

std::unique_ptr<views::NonClientFrameView>
PickerSubmenuView::CreateNonClientFrameView(views::Widget* widget) {
  auto frame =
      std::make_unique<views::BubbleFrameView>(gfx::Insets(), gfx::Insets());
  frame->SetBubbleBorder(CreateBorder());
  return frame;
}

gfx::Size PickerSubmenuView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  const int preferred_height =
      views::WidgetDelegateView::CalculatePreferredSize(available_size)
          .height();
  return gfx::Size(kSubmenuWidth, preferred_height);
}

views::View* PickerSubmenuView::GetTopItem() {
  return section_view_->GetTopItem();
}

views::View* PickerSubmenuView::GetBottomItem() {
  return section_view_->GetBottomItem();
}

views::View* PickerSubmenuView::GetItemAbove(views::View* item) {
  return section_view_->GetItemAbove(item);
}

views::View* PickerSubmenuView::GetItemBelow(views::View* item) {
  return section_view_->GetItemBelow(item);
}

views::View* PickerSubmenuView::GetItemLeftOf(views::View* item) {
  return section_view_->GetItemLeftOf(item);
}

views::View* PickerSubmenuView::GetItemRightOf(views::View* item) {
  return section_view_->GetItemRightOf(item);
}

bool PickerSubmenuView::ContainsItem(views::View* item) {
  return Contains(item);
}

gfx::Rect PickerSubmenuView::GetDesiredBounds(gfx::Rect anchor_rect) {
  // Inset the anchor rect so that the submenu overlaps slightly with the
  // anchor.
  anchor_rect.Inset(gfx::Insets::VH(0, kSubmenuHorizontalOverlap));

  auto* bubble_frame_view = static_cast<views::BubbleFrameView*>(
      GetWidget()->non_client_view()->frame_view());
  gfx::Rect bounds = bubble_frame_view->GetUpdatedWindowBounds(
      anchor_rect, bubble_frame_view->GetArrow(), GetPreferredSize({}),
      /*adjust_to_fit_available_bounds=*/true);

  // Adjust the bounds to be relative to the parent's bounds.
  const gfx::Rect parent_bounds =
      GetWidget()->parent()->GetWindowBoundsInScreen();
  bounds.Offset(-parent_bounds.OffsetFromOrigin());

  // Shift by the insets to align the first item with the anchor rect.
  bounds.Offset(0, -kInsets.top());
  return bounds;
}

BEGIN_METADATA(PickerSubmenuView)
END_METADATA

}  // namespace ash