Skip to content

Commit

Permalink
QA Rich card: Create Rich answers card UI
Browse files Browse the repository at this point in the history
This change create an empty view for Rich answers card. Contents of each
answers type will be added in follow-up changes.

Bug: b/259449788
Test: None
Change-Id: I3fd95a93cadd761e02b6f27e0710603ae6846bfe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4159811
Reviewed-by: Tao Wu <wutao@chromium.org>
Commit-Queue: Angela Xiao <angelaxiao@chromium.org>
Reviewed-by: Angela Xiao <angelaxiao@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1098192}
  • Loading branch information
Yue Li authored and Chromium LUCI CQ committed Jan 28, 2023
1 parent 27c9cd9 commit 2f90be4
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 1 deletion.
3 changes: 3 additions & 0 deletions chrome/browser/ui/quick_answers/BUILD.gn
Expand Up @@ -18,6 +18,8 @@ source_set("quick_answers") {
"ui/quick_answers_pre_target_handler.h",
"ui/quick_answers_view.cc",
"ui/quick_answers_view.h",
"ui/rich_answers_view.cc",
"ui/rich_answers_view.h",
"ui/user_consent_view.cc",
"ui/user_consent_view.h",
]
Expand All @@ -27,6 +29,7 @@ source_set("quick_answers") {
"//chromeos/components/quick_answers",
"//chromeos/components/quick_answers/public/cpp:cpp",
"//chromeos/components/quick_answers/public/cpp:prefs",
"//chromeos/constants:constants",
"//chromeos/strings:strings_grit",
"//components/account_id",
"//components/prefs",
Expand Down
12 changes: 12 additions & 0 deletions chrome/browser/ui/quick_answers/quick_answers_ui_controller.cc
Expand Up @@ -9,8 +9,10 @@
#include "base/strings/stringprintf.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/ui/quick_answers/quick_answers_controller_impl.h"
#include "chrome/browser/ui/quick_answers/ui/rich_answers_view.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
Expand Down Expand Up @@ -97,6 +99,16 @@ void QuickAnswersUiController::CreateQuickAnswersView(const gfx::Rect& bounds,
}

void QuickAnswersUiController::OnQuickAnswersViewPressed() {
if (chromeos::features::IsQuickAnswersRichCardEnabled()) {
auto* const rich_answers_view =
new RichAnswersView(quick_answers_view_tracker_.view()->bounds(),
weak_factory_.GetWeakPtr());
rich_answers_view_tracker_.SetView(rich_answers_view);
rich_answers_view->GetWidget()->ShowInactive();
controller_->DismissQuickAnswers(QuickAnswersExitPoint::kQuickAnswersClick);
return;
}

// Route dismissal through |controller_| for logging impressions.
controller_->DismissQuickAnswers(QuickAnswersExitPoint::kQuickAnswersClick);

Expand Down
Expand Up @@ -99,9 +99,10 @@ class QuickAnswersUiController {
private:
raw_ptr<QuickAnswersControllerImpl> controller_ = nullptr;

// Trackers for quick answers and user consent view.
// Trackers for quick answers related views.
views::ViewTracker quick_answers_view_tracker_;
views::ViewTracker user_consent_view_tracker_;
views::ViewTracker rich_answers_view_tracker_;

std::string query_;

Expand Down
182 changes: 182 additions & 0 deletions chrome/browser/ui/quick_answers/ui/rich_answers_view.cc
@@ -0,0 +1,182 @@
// Copyright 2023 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/quick_answers/ui/rich_answers_view.h"

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/quick_answers/quick_answers_ui_controller.h"
#include "chrome/browser/ui/quick_answers/ui/quick_answers_pre_target_handler.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/display/screen.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"

namespace {

// Buttons view.
constexpr int kButtonsViewMarginDip = 4;
constexpr int kButtonsSpacingDip = 4;
constexpr int kSettingsButtonSizeDip = 14;
constexpr int kSettingsButtonBorderDip = 3;

} // namespace

// RichAnswersView -----------------------------------------------------------

RichAnswersView::RichAnswersView(
const gfx::Rect& anchor_view_bounds,
base::WeakPtr<QuickAnswersUiController> controller)
: anchor_view_bounds_(anchor_view_bounds),
controller_(std::move(controller)),
focus_search_(std::make_unique<QuickAnswersFocusSearch>(
this,
base::BindRepeating(&RichAnswersView::GetFocusableViews,
base::Unretained(this)))) {
InitLayout();
InitWidget();

// Focus.
SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
set_suppress_default_focus_handling();
}

RichAnswersView::~RichAnswersView() = default;

const char* RichAnswersView::GetClassName() const {
return "RichAnswersView";
}

void RichAnswersView::OnFocus() {
View* wants_focus = focus_search_->FindNextFocusableView(
nullptr, views::FocusSearch::SearchDirection::kForwards,
views::FocusSearch::TraversalDirection::kDown,
views::FocusSearch::StartingViewPolicy::kCheckStartingView,
views::FocusSearch::AnchoredDialogPolicy::kSkipAnchoredDialog, nullptr,
nullptr);
if (wants_focus != this) {
wants_focus->RequestFocus();
} else {
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
}

void RichAnswersView::OnThemeChanged() {
views::View::OnThemeChanged();
SetBackground(views::CreateSolidBackground(
GetColorProvider()->GetColor(ui::kColorPrimaryBackground)));
if (settings_button_) {
settings_button_->SetImage(
views::Button::ButtonState::STATE_NORMAL,
gfx::CreateVectorIcon(
vector_icons::kSettingsOutlineIcon, kSettingsButtonSizeDip,
GetColorProvider()->GetColor(ui::kColorIconSecondary)));
}
}

views::FocusTraversable* RichAnswersView::GetPaneFocusTraversable() {
return focus_search_.get();
}

void RichAnswersView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kDialog;

node_data->SetName(
l10n_util::GetStringUTF8(IDS_QUICK_ANSWERS_VIEW_A11Y_NAME_TEXT));
}

void RichAnswersView::InitLayout() {
SetLayoutManager(std::make_unique<views::FillLayout>());

// Base view Layout.
base_view_ = AddChildView(std::make_unique<View>());
auto* base_layout =
base_view_->SetLayoutManager(std::make_unique<views::FlexLayout>());
base_layout->SetOrientation(views::LayoutOrientation::kVertical)
.SetCrossAxisAlignment(views::LayoutAlignment::kStretch);

// TODO(b/259440976): Add child views for each result type.

// Add util buttons in the top-right corner.
AddFrameButtons();
}

void RichAnswersView::InitWidget() {
views::Widget::InitParams params;
params.activatable = views::Widget::InitParams::Activatable::kNo;
params.shadow_elevation = 2;
params.shadow_type = views::Widget::InitParams::ShadowType::kDrop;
params.type = views::Widget::InitParams::TYPE_POPUP;
params.z_order = ui::ZOrderLevel::kFloatingUIElement;

views::Widget* widget = new views::Widget();
widget->Init(std::move(params));
widget->SetContentsView(this);
UpdateBounds();
}

void RichAnswersView::AddFrameButtons() {
auto* buttons_view = AddChildView(std::make_unique<views::View>());
auto* layout =
buttons_view->SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetMainAxisAlignment(views::LayoutAlignment::kEnd)
.SetCrossAxisAlignment(views::LayoutAlignment::kStart)
.SetInteriorMargin(gfx::Insets(kButtonsViewMarginDip))
.SetDefault(views::kMarginsKey,
gfx::Insets::TLBR(0, kButtonsSpacingDip, 0, 0));

settings_button_ = buttons_view->AddChildView(
std::make_unique<views::ImageButton>(base::BindRepeating(
&QuickAnswersUiController::OnSettingsButtonPressed, controller_)));
settings_button_->SetTooltipText(l10n_util::GetStringUTF16(
IDS_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT));
settings_button_->SetBorder(
views::CreateEmptyBorder(kSettingsButtonBorderDip));
}

void RichAnswersView::UpdateBounds() {
auto display_bounds = display::Screen::GetScreen()
->GetDisplayMatching(anchor_view_bounds_)
.bounds();

// TODO(b/259440976): Calculate desired bounds based on anchor view bounds.
gfx::Rect bounds = {
{display_bounds.width() / 2 - 200, display_bounds.height() / 2 - 300},
{400, 600}};
#if BUILDFLAG(IS_CHROMEOS_ASH)
// For Ash, convert the position relative to the screen.
// For Lacros, `bounds` is already relative to the toplevel window and the
// position will be calculated on server side.
wm::ConvertRectFromScreen(GetWidget()->GetNativeWindow()->parent(), &bounds);
#endif
GetWidget()->SetBounds(bounds);
}

std::vector<views::View*> RichAnswersView::GetFocusableViews() {
std::vector<views::View*> focusable_views;
focusable_views.push_back(this);

if (settings_button_ && settings_button_->GetVisible()) {
focusable_views.push_back(settings_button_);
}

return focusable_views;
}
62 changes: 62 additions & 0 deletions chrome/browser/ui/quick_answers/ui/rich_answers_view.h
@@ -0,0 +1,62 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_UI_QUICK_ANSWERS_UI_RICH_ANSWERS_VIEW_H_
#define CHROME_BROWSER_UI_QUICK_ANSWERS_UI_RICH_ANSWERS_VIEW_H_

#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/quick_answers/ui/quick_answers_focus_search.h"
#include "ui/events/event_handler.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/view.h"

namespace views {
class ImageButton;
} // namespace views

class QuickAnswersUiController;

// A bubble style view to show QuickAnswer.
class RichAnswersView : public views::View {
public:
RichAnswersView(const gfx::Rect& anchor_view_bounds,
base::WeakPtr<QuickAnswersUiController> controller);

RichAnswersView(const RichAnswersView&) = delete;
RichAnswersView& operator=(const RichAnswersView&) = delete;

~RichAnswersView() override;

// views::View:
const char* GetClassName() const override;
void OnFocus() override;
void OnThemeChanged() override;
views::FocusTraversable* GetPaneFocusTraversable() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;

private:
void InitLayout();
void InitWidget();
void AddFrameButtons();
void UpdateBounds();

// QuickAnswersFocusSearch::GetFocusableViewsCallback to poll currently
// focusable views.
std::vector<views::View*> GetFocusableViews();

gfx::Rect anchor_view_bounds_;

base::WeakPtr<QuickAnswersUiController> controller_;

raw_ptr<views::View> base_view_ = nullptr;
raw_ptr<views::ImageButton> settings_button_ = nullptr;

std::unique_ptr<QuickAnswersFocusSearch> focus_search_;
base::WeakPtrFactory<RichAnswersView> weak_factory_{this};
};

#endif // CHROME_BROWSER_UI_QUICK_ANSWERS_UI_QUICK_ANSWERS_VIEW_H_

0 comments on commit 2f90be4

Please sign in to comment.