chromium/ash/capture_mode/search_results_panel.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/capture_mode/search_results_panel.h"

#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/ash_web_view.h"
#include "ash/public/cpp/ash_web_view_factory.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/style/color_provider.h"
#include "ash/shell.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/wm/core/shadow_types.h"

namespace ash {

namespace {

// TODO(sophiewen): Remove hardcoded values when we get UX specs.
inline constexpr int kPanelCornerRadius = 16;
inline constexpr int kSearchResultsPanelWidth = 600;
constexpr char kValidSearchUrl[] =
    "https://www.google.com/search?q=cat&gsc=1&masfc=c";
const std::u16string kSearchBoxPlaceholderText = u"Add to your search";
inline constexpr gfx::Insets kPanelPadding = gfx::Insets::TLBR(12, 15, 15, 15);

}  // namespace

// `SunfishSearchBoxView` contains an image thumbnail and a textfield.
class SunfishSearchBoxView : public views::View {
  METADATA_HEADER(SunfishSearchBoxView, views::View)

 public:
  SunfishSearchBoxView() {
    SetLayoutManager(std::make_unique<views::FlexLayout>())
        ->SetOrientation(views::LayoutOrientation::kHorizontal)
        .SetMainAxisAlignment(views::LayoutAlignment::kStart)
        .SetCrossAxisAlignment(views::LayoutAlignment::kStretch)
        .SetCollapseMargins(true);
    // TODO(b/356878705): Replace with the captured region screenshot when the
    // backend is hooked up. Currently using the search icon as a placeholder.
    AddChildView(views::Builder<views::ImageView>()
                     .CopyAddressTo(&image_view_)
                     .SetImage(ui::ImageModel::FromVectorIcon(
                         vector_icons::kGoogleColorIcon))
                     .Build());
    AddChildView(views::Builder<views::Textfield>()
                     .CopyAddressTo(&textfield_)
                     .SetTextInputType(ui::TEXT_INPUT_TYPE_TEXT)
                     .SetPlaceholderText(kSearchBoxPlaceholderText)
                     .SetProperty(views::kFlexBehaviorKey,
                                  views::FlexSpecification(
                                      views::LayoutOrientation::kHorizontal,
                                      views::MinimumFlexSizeRule::kPreferred,
                                      views::MaximumFlexSizeRule::kUnbounded))
                     .SetBackgroundEnabled(false)
                     .SetBorder(nullptr)
                     .Build());
    SetBackground(views::CreateThemedSolidBackground(
        cros_tokens::kCrosSysSystemBaseElevated));
  }
  SunfishSearchBoxView(const SunfishSearchBoxView&) = delete;
  SunfishSearchBoxView& operator=(const SunfishSearchBoxView&) = delete;
  ~SunfishSearchBoxView() override = default;

  void SetImage(const gfx::ImageSkia& image) {
    if (image.isNull()) {
      return;
    }
    // Resize the image to fit in the searchbox, keeping the same aspect ratio.
    const int target_height = height();
    const int target_width = (image.width() * target_height) / image.height();
    image_view_->SetImage(image);
    image_view_->SetImageSize(gfx::Size(target_width, target_height));
  }

 private:
  // Owned by the views hierarchy.
  raw_ptr<views::ImageView> image_view_;
  raw_ptr<views::Textfield> textfield_;
};

BEGIN_METADATA(SunfishSearchBoxView)
END_METADATA

SearchResultsPanel::SearchResultsPanel() {
  DCHECK(features::IsSunfishFeatureEnabled());
  SetLayoutManager(std::make_unique<views::FlexLayout>())
      ->SetOrientation(views::LayoutOrientation::kVertical)
      .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
      .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
      .SetInteriorMargin(kPanelPadding)
      .SetCollapseMargins(true);
  search_box_view_ = AddChildView(std::make_unique<SunfishSearchBoxView>());
  search_results_view_ =
      AddChildView(CaptureModeController::Get()->CreateSearchResultsView());
  search_results_view_->SetProperty(
      views::kFlexBehaviorKey,
      views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
                               views::MaximumFlexSizeRule::kUnbounded));

  // TODO(b/356878705): Replace this when the backend is hooked up. Currently
  // used for UI debugging.
  search_results_view_->Navigate(GURL(kValidSearchUrl));

  SetBackground(views::CreateThemedRoundedRectBackground(
      cros_tokens::kCrosSysSystemBaseElevated, kPanelCornerRadius));
  SetPaintToLayer();
  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{kPanelCornerRadius});
  layer()->SetFillsBoundsOpaquely(false);
  layer()->SetIsFastRoundedCorner(true);
  layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
}

SearchResultsPanel::~SearchResultsPanel() = default;

// static
std::unique_ptr<views::Widget> SearchResultsPanel::CreateWidget(
    aura::Window* const root) {
  views::Widget::InitParams params(
      views::Widget::InitParams::CLIENT_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  // TODO(b/362284723): Ensure tooltips are visible over overlay container.
  params.parent = Shell::GetContainer(root, kShellWindowId_OverlayContainer);
  const gfx::Rect work_area(
      display::Screen::GetScreen()->GetDisplayNearestWindow(root).work_area());
  gfx::Rect bounds(work_area.right() - kSearchResultsPanelWidth, work_area.y(),
                   kSearchResultsPanelWidth, work_area.height());
  params.bounds = bounds;
  params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
  params.activatable = views::Widget::InitParams::Activatable::kYes;
  params.shadow_elevation = wm::kShadowElevationInactiveWindow;
  auto widget = std::make_unique<views::Widget>(std::move(params));
  widget->SetContentsView(std::make_unique<SearchResultsPanel>());
  return widget;
}

void SearchResultsPanel::SetSearchBoxImage(const gfx::ImageSkia& image) {
  search_box_view_->SetImage(image);
}

BEGIN_METADATA(SearchResultsPanel)
END_METADATA

}  // namespace ash