| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/quick_answers/ui/quick_answers_view.h" |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/public/cpp/app_list/vector_icons/vector_icons.h" |
| #include "ash/public/cpp/assistant/assistant_interface_binder.h" |
| #include "ash/quick_answers/quick_answers_ui_controller.h" |
| #include "ash/quick_answers/ui/quick_answers_pre_target_handler.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "base/bind.h" |
| #include "chromeos/components/quick_answers/quick_answers_model.h" |
| #include "chromeos/ui/vector_icons/vector_icons.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/animation/ink_drop_impl.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/button_controller.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/menu/menu_config.h" |
| #include "ui/views/controls/menu/menu_controller.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/layout/flex_layout.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/widget/tooltip_manager.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| |
| namespace ash { |
| namespace { |
| |
| using chromeos::quick_answers::QuickAnswer; |
| using chromeos::quick_answers::QuickAnswerText; |
| using chromeos::quick_answers::QuickAnswerUiElement; |
| using chromeos::quick_answers::QuickAnswerUiElementType; |
| using views::Button; |
| using views::Label; |
| using views::View; |
| |
| // Spacing between this view and the anchor view. |
| constexpr int kMarginDip = 10; |
| |
| constexpr gfx::Insets kMainViewInsets(4, 0); |
| constexpr gfx::Insets kContentViewInsets(8, 0, 8, 16); |
| constexpr float kHoverStateAlpha = 0.06f; |
| constexpr int kMaxRows = 3; |
| |
| // Assistant icon. |
| constexpr int kAssistantIconSizeDip = 16; |
| constexpr gfx::Insets kAssistantIconInsets(10, 10, 0, 8); |
| |
| // Google icon. |
| constexpr int kGoogleIconSizeDip = 16; |
| constexpr gfx::Insets kGoogleIconInsets(10, 10, 0, 8); |
| |
| // Info icon. |
| constexpr int kFeedbackIconSizeDip = 16; |
| constexpr gfx::Insets kFeedbackIconInsets(8, 10, 8, 8); |
| |
| // Spacing between lines in the main view. |
| constexpr int kLineSpacingDip = 4; |
| constexpr int kLineHeightDip = 20; |
| |
| // Spacing between labels in the horizontal elements view. |
| constexpr int kLabelSpacingDip = 2; |
| |
| // Dogfood button. |
| constexpr int kDogfoodButtonMarginDip = 4; |
| constexpr int kDogfoodButtonSizeDip = 20; |
| constexpr SkColor kDogfoodButtonColor = gfx::kGoogleGrey500; |
| |
| // Settings button. |
| constexpr int kSettingsButtonMarginDip = 8; |
| constexpr int kSettingsButtonSizeDip = 14; |
| constexpr SkColor kSettingsButtonColor = gfx::kGoogleGrey500; |
| constexpr SkColor kSettingsButtonInkDropColor = gfx::kGoogleGrey500; |
| |
| // ReportQueryView. |
| constexpr char kGoogleSansFont[] = "Google Sans"; |
| constexpr int kReportQueryButtonMarginDip = 12; |
| constexpr int kReportQueryViewFontSize = 10; |
| |
| // Maximum height QuickAnswersView can expand to. |
| int MaximumViewHeight() { |
| return kMainViewInsets.height() + kContentViewInsets.height() + |
| kMaxRows * kLineHeightDip + (kMaxRows - 1) * kLineSpacingDip; |
| } |
| |
| // Adds |text_element| as label to the container. |
| Label* AddTextElement(const QuickAnswerText& text_element, View* container) { |
| auto* label = |
| container->AddChildView(std::make_unique<Label>(text_element.text)); |
| label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT); |
| label->SetEnabledColor(text_element.color); |
| label->SetLineHeight(kLineHeightDip); |
| return label; |
| } |
| |
| // Adds the list of |QuickAnswerUiElement| horizontally to the container. |
| View* AddHorizontalUiElements( |
| const std::vector<std::unique_ptr<QuickAnswerUiElement>>& elements, |
| View* container) { |
| auto* labels_container = |
| container->AddChildView(std::make_unique<views::View>()); |
| auto* layout = |
| labels_container->SetLayoutManager(std::make_unique<views::FlexLayout>()); |
| layout->SetOrientation(views::LayoutOrientation::kHorizontal) |
| .SetDefault(views::kMarginsKey, gfx::Insets(/*top=*/0, /*left=*/0, |
| /*bottom=*/0, |
| /*right=*/kLabelSpacingDip)); |
| |
| for (const auto& element : elements) { |
| switch (element->type) { |
| case QuickAnswerUiElementType::kText: |
| AddTextElement(*static_cast<QuickAnswerText*>(element.get()), |
| labels_container); |
| break; |
| case QuickAnswerUiElementType::kImage: |
| // TODO(yanxiao): Add image view |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return labels_container; |
| } |
| |
| class ReportQueryView : public views::Button { |
| public: |
| METADATA_HEADER(ReportQueryView); |
| |
| ReportQueryView(PressedCallback callback) : Button(std::move(callback)) { |
| auto* layout = SetLayoutManager(std::make_unique<views::FlexLayout>()); |
| layout->SetOrientation(views::LayoutOrientation::kHorizontal) |
| .SetMainAxisAlignment(views::LayoutAlignment::kStart); |
| SetBackground(views::CreateSolidBackground(gfx::kGoogleBlue050)); |
| |
| auto* feedback_icon = AddChildView(std::make_unique<views::ImageView>()); |
| feedback_icon->SetBorder(views::CreateEmptyBorder(kFeedbackIconInsets)); |
| feedback_icon->SetImage( |
| gfx::CreateVectorIcon(kPersistentDesksBarFeedbackIcon, |
| kFeedbackIconSizeDip, gfx::kGoogleBlue600)); |
| |
| auto* description_label = AddChildView(std::make_unique<Label>( |
| l10n_util::GetStringUTF16( |
| IDS_ASH_QUICK_ANSWERS_VIEW_REPORT_QUERY_IMPROVE_LABEL), |
| Label::CustomFont{gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, |
| kReportQueryViewFontSize, |
| gfx::Font::Weight::MEDIUM)})); |
| description_label->SetHorizontalAlignment( |
| gfx::HorizontalAlignment::ALIGN_LEFT); |
| description_label->SetEnabledColor(gfx::kGoogleBlue600); |
| |
| auto* report_label = AddChildView(std::make_unique<Label>( |
| l10n_util::GetStringUTF16( |
| IDS_ASH_QUICK_ANSWERS_VIEW_REPORT_QUERY_REPORT_LABEL), |
| Label::CustomFont{gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL, |
| kReportQueryViewFontSize, |
| gfx::Font::Weight::SEMIBOLD)})); |
| report_label->SetProperty( |
| views::kFlexBehaviorKey, |
| views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred, |
| views::MaximumFlexSizeRule::kUnbounded) |
| .WithAlignment(views::LayoutAlignment::kEnd)); |
| report_label->SetProperty( |
| views::kMarginsKey, gfx::Insets(/*top=*/0, /*left=*/0, /*bottom=*/0, |
| /*right=*/kReportQueryButtonMarginDip)); |
| report_label->SetEnabledColor(gfx::kGoogleBlue600); |
| } |
| |
| // Disallow copy and assign. |
| ReportQueryView(const ReportQueryView&) = delete; |
| ReportQueryView& operator=(const ReportQueryView&) = delete; |
| |
| ~ReportQueryView() override = default; |
| }; |
| |
| BEGIN_METADATA(ReportQueryView, views::Button) |
| END_METADATA |
| |
| } // namespace |
| |
| // QuickAnswersView ----------------------------------------------------------- |
| |
| QuickAnswersView::QuickAnswersView(const gfx::Rect& anchor_view_bounds, |
| const std::string& title, |
| bool is_internal, |
| QuickAnswersUiController* controller) |
| : Button(base::BindRepeating(&QuickAnswersView::SendQuickAnswersQuery, |
| base::Unretained(this))), |
| anchor_view_bounds_(anchor_view_bounds), |
| controller_(controller), |
| title_(title), |
| is_internal_(is_internal), |
| quick_answers_view_handler_( |
| std::make_unique<QuickAnswersPreTargetHandler>(this)), |
| focus_search_(std::make_unique<QuickAnswersFocusSearch>( |
| this, |
| base::BindRepeating(&QuickAnswersView::GetFocusableViews, |
| base::Unretained(this)))) { |
| InitLayout(); |
| InitWidget(); |
| |
| // Focus. |
| SetFocusBehavior(views::View::FocusBehavior::ALWAYS); |
| SetInstallFocusRingOnFocus(false); |
| |
| // This is because waiting for mouse-release to fire buttons would be too |
| // late, since mouse-press dismisses the menu. |
| SetButtonNotifyActionToOnPress(this); |
| |
| // Allow tooltips to be shown despite menu-controller owning capture. |
| GetWidget()->SetNativeWindowProperty( |
| views::TooltipManager::kGroupingPropertyKey, |
| reinterpret_cast<void*>(views::MenuConfig::kMenuControllerGroupingId)); |
| } |
| |
| QuickAnswersView::~QuickAnswersView() = default; |
| |
| const char* QuickAnswersView::GetClassName() const { |
| return "QuickAnswersView"; |
| } |
| |
| void QuickAnswersView::OnFocus() { |
| SetBackgroundState(true); |
| 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 QuickAnswersView::OnBlur() { |
| SetBackgroundState(false); |
| } |
| |
| views::FocusTraversable* QuickAnswersView::GetPaneFocusTraversable() { |
| return focus_search_.get(); |
| } |
| |
| void QuickAnswersView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| // The view itself is not focused for retry-mode, so should not be announced |
| // by the screen reader. |
| if (retry_label_) { |
| node_data->role = ax::mojom::Role::kNone; |
| node_data->SetName(std::string()); |
| node_data->SetDescription(std::string()); |
| return; |
| } |
| |
| node_data->role = ax::mojom::Role::kDialog; |
| node_data->SetName( |
| l10n_util::GetStringUTF8(IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_NAME_TEXT)); |
| } |
| |
| void QuickAnswersView::StateChanged(views::Button::ButtonState old_state) { |
| Button::StateChanged(old_state); |
| const bool hovered = GetState() == Button::STATE_HOVERED; |
| if (hovered || (GetState() == Button::STATE_NORMAL)) |
| SetBackgroundState(hovered); |
| } |
| |
| void QuickAnswersView::SendQuickAnswersQuery() { |
| controller_->OnQuickAnswersViewPressed(); |
| } |
| |
| void QuickAnswersView::UpdateAnchorViewBounds( |
| const gfx::Rect& anchor_view_bounds) { |
| anchor_view_bounds_ = anchor_view_bounds; |
| UpdateBounds(); |
| } |
| |
| void QuickAnswersView::UpdateView(const gfx::Rect& anchor_view_bounds, |
| const QuickAnswer& quick_answer) { |
| has_second_row_answer_ = !quick_answer.second_answer_row.empty(); |
| anchor_view_bounds_ = anchor_view_bounds; |
| retry_label_ = nullptr; |
| |
| UpdateQuickAnswerResult(quick_answer); |
| UpdateBounds(); |
| } |
| |
| void QuickAnswersView::ShowRetryView() { |
| if (retry_label_) |
| return; |
| |
| ResetContentView(); |
| main_view_->SetBackground(views::CreateSolidBackground(SK_ColorTRANSPARENT)); |
| |
| // Add title. |
| AddTextElement({title_}, content_view_); |
| |
| // Add error label. |
| std::vector<std::unique_ptr<QuickAnswerUiElement>> description_labels; |
| description_labels.push_back(std::make_unique<QuickAnswerText>( |
| l10n_util::GetStringUTF8(IDS_ASH_QUICK_ANSWERS_VIEW_NETWORK_ERROR), |
| gfx::kGoogleGrey700)); |
| auto* description_container = |
| AddHorizontalUiElements(description_labels, content_view_); |
| |
| // Add retry label. |
| retry_label_ = |
| description_container->AddChildView(std::make_unique<views::LabelButton>( |
| base::BindRepeating(&QuickAnswersUiController::OnRetryLabelPressed, |
| base::Unretained(controller_)), |
| l10n_util::GetStringUTF16(IDS_ASH_QUICK_ANSWERS_VIEW_RETRY))); |
| retry_label_->SetEnabledTextColors(gfx::kGoogleBlue600); |
| retry_label_->SetRequestFocusOnPress(true); |
| SetButtonNotifyActionToOnPress(retry_label_); |
| retry_label_->SetAccessibleName(l10n_util::GetStringFUTF16( |
| IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_RETRY_LABEL_NAME_TEMPLATE, |
| l10n_util::GetStringUTF16(IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_NAME_TEXT))); |
| retry_label_->GetViewAccessibility().OverrideDescription( |
| l10n_util::GetStringUTF8( |
| IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_RETRY_LABEL_DESC)); |
| } |
| |
| void QuickAnswersView::InitLayout() { |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| SetBackground(views::CreateSolidBackground(SK_ColorWHITE)); |
| |
| 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); |
| |
| main_view_ = base_view_->AddChildView(std::make_unique<View>()); |
| auto* layout = |
| main_view_->SetLayoutManager(std::make_unique<views::FlexLayout>()); |
| layout->SetOrientation(views::LayoutOrientation::kHorizontal) |
| .SetInteriorMargin(kMainViewInsets) |
| .SetCrossAxisAlignment(views::LayoutAlignment::kStart); |
| |
| // Add branding icon. |
| if (chromeos::features::IsQuickAnswersV2Enabled()) { |
| AddGoogleIcon(); |
| } else { |
| AddAssistantIcon(); |
| } |
| |
| AddContentView(); |
| |
| if (chromeos::features::IsQuickAnswersV2Enabled() && is_internal_) { |
| base_view_->AddChildView( |
| std::make_unique<ReportQueryView>(base::BindRepeating( |
| &QuickAnswersUiController::OnReportQueryButtonPressed, |
| base::Unretained(controller_)))); |
| } |
| |
| if (chromeos::features::IsQuickAnswersV2Enabled()) { |
| AddSettingsButton(); |
| } else if (chromeos::features::IsQuickAnswersDogfood()) { |
| AddDogfoodButton(); |
| } |
| } |
| |
| void QuickAnswersView::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; |
| |
| // Parent the widget depending on the context. |
| auto* active_menu_controller = views::MenuController::GetActiveInstance(); |
| if (active_menu_controller && active_menu_controller->owner()) { |
| params.parent = active_menu_controller->owner()->GetNativeView(); |
| params.child = true; |
| } else { |
| params.context = Shell::Get()->GetRootWindowForNewWindows(); |
| } |
| |
| views::Widget* widget = new views::Widget(); |
| widget->Init(std::move(params)); |
| widget->SetContentsView(this); |
| UpdateBounds(); |
| } |
| |
| void QuickAnswersView::AddContentView() { |
| // Add content view. |
| content_view_ = main_view_->AddChildView(std::make_unique<View>()); |
| auto* layout = |
| content_view_->SetLayoutManager(std::make_unique<views::FlexLayout>()); |
| layout->SetOrientation(views::LayoutOrientation::kVertical) |
| .SetInteriorMargin(kContentViewInsets) |
| .SetDefault(views::kMarginsKey, |
| gfx::Insets(/*top=*/0, /*left=*/0, /*bottom=*/kLineSpacingDip, |
| /*right=*/0)); |
| AddTextElement({title_}, content_view_); |
| AddTextElement({l10n_util::GetStringUTF8(IDS_ASH_QUICK_ANSWERS_VIEW_LOADING), |
| gfx::kGoogleGrey700}, |
| content_view_); |
| } |
| |
| void QuickAnswersView::AddDogfoodButton() { |
| auto* dogfood_view = AddChildView(std::make_unique<View>()); |
| auto* layout = |
| dogfood_view->SetLayoutManager(std::make_unique<views::FlexLayout>()); |
| layout->SetOrientation(views::LayoutOrientation::kVertical) |
| .SetInteriorMargin(gfx::Insets(kDogfoodButtonMarginDip)) |
| .SetCrossAxisAlignment(views::LayoutAlignment::kEnd); |
| dogfood_button_ = |
| dogfood_view->AddChildView(std::make_unique<views::ImageButton>( |
| base::BindRepeating(&QuickAnswersUiController::OnDogfoodButtonPressed, |
| base::Unretained(controller_)))); |
| dogfood_button_->SetImage( |
| views::Button::ButtonState::STATE_NORMAL, |
| gfx::CreateVectorIcon(kDogfoodIcon, kDogfoodButtonSizeDip, |
| kDogfoodButtonColor)); |
| dogfood_button_->SetTooltipText(l10n_util::GetStringUTF16( |
| IDS_ASH_QUICK_ANSWERS_DOGFOOD_BUTTON_TOOLTIP_TEXT)); |
| SetButtonNotifyActionToOnPress(dogfood_button_); |
| } |
| |
| void QuickAnswersView::AddSettingsButton() { |
| auto* settings_view = AddChildView(std::make_unique<views::View>()); |
| auto* layout = |
| settings_view->SetLayoutManager(std::make_unique<views::FlexLayout>()); |
| layout->SetOrientation(views::LayoutOrientation::kVertical) |
| .SetInteriorMargin(gfx::Insets(kSettingsButtonMarginDip)) |
| .SetCrossAxisAlignment(views::LayoutAlignment::kEnd); |
| settings_button_ = settings_view->AddChildView( |
| std::make_unique<views::ImageButton>(base::BindRepeating( |
| &QuickAnswersUiController::OnSettingsButtonPressed, |
| base::Unretained(controller_)))); |
| settings_button_->SetImage( |
| views::Button::ButtonState::STATE_NORMAL, |
| gfx::CreateVectorIcon(kUnifiedMenuSettingsIcon, kSettingsButtonSizeDip, |
| kSettingsButtonColor)); |
| settings_button_->SetTooltipText(l10n_util::GetStringUTF16( |
| IDS_ASH_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT)); |
| |
| views::InkDropHost* const ink_drop = views::InkDrop::Get(settings_button_); |
| ink_drop->SetBaseColor(kSettingsButtonInkDropColor); |
| ink_drop->SetMode(views::InkDropHost::InkDropMode::ON); |
| settings_button_->SetHasInkDropActionOnClick(true); |
| views::InstallCircleHighlightPathGenerator(settings_button_); |
| } |
| |
| void QuickAnswersView::AddAssistantIcon() { |
| // Add Assistant icon. |
| auto* assistant_icon = |
| main_view_->AddChildView(std::make_unique<views::ImageView>()); |
| assistant_icon->SetBorder(views::CreateEmptyBorder(kAssistantIconInsets)); |
| assistant_icon->SetImage(gfx::CreateVectorIcon( |
| chromeos::kAssistantIcon, kAssistantIconSizeDip, gfx::kPlaceholderColor)); |
| } |
| |
| void QuickAnswersView::AddGoogleIcon() { |
| // Add Google icon. |
| auto* google_icon = |
| main_view_->AddChildView(std::make_unique<views::ImageView>()); |
| google_icon->SetBorder(views::CreateEmptyBorder(kGoogleIconInsets)); |
| google_icon->SetImage(gfx::CreateVectorIcon( |
| kGoogleColorIcon, kGoogleIconSizeDip, gfx::kPlaceholderColor)); |
| } |
| |
| void QuickAnswersView::ResetContentView() { |
| content_view_->RemoveAllChildViews(true); |
| first_answer_label_ = nullptr; |
| } |
| |
| void QuickAnswersView::SetBackgroundState(bool highlight) { |
| if (highlight && !retry_label_) { |
| main_view_->SetBackground(views::CreateBackgroundFromPainter( |
| views::Painter::CreateSolidRoundRectPainter( |
| SkColorSetA(SK_ColorBLACK, kHoverStateAlpha * 0xFF), |
| /*radius=*/0, kMainViewInsets))); |
| } else if (!highlight) { |
| main_view_->SetBackground(views::CreateSolidBackground(SK_ColorWHITE)); |
| } |
| } |
| |
| void QuickAnswersView::UpdateBounds() { |
| int desired_width = anchor_view_bounds_.width(); |
| |
| // Multi-line labels need to be resized to be compatible with |desired_width|. |
| if (first_answer_label_) { |
| int label_desired_width = |
| desired_width - kMainViewInsets.width() - kContentViewInsets.width() - |
| kAssistantIconInsets.width() - kAssistantIconSizeDip; |
| first_answer_label_->SizeToFit(label_desired_width); |
| } |
| |
| int height = GetHeightForWidth(desired_width); |
| int y = anchor_view_bounds_.y() - kMarginDip - height; |
| |
| // Reserve space at the top since the view might expand for two-line answers. |
| int y_min = anchor_view_bounds_.y() - kMarginDip - MaximumViewHeight(); |
| if (y_min < display::Screen::GetScreen() |
| ->GetDisplayMatching(anchor_view_bounds_) |
| .bounds() |
| .y()) { |
| // The Quick Answers view will be off screen if showing above the anchor. |
| // Show below the anchor instead. |
| y = anchor_view_bounds_.bottom() + kMarginDip; |
| } |
| |
| gfx::Rect bounds = {{anchor_view_bounds_.x(), y}, {desired_width, height}}; |
| wm::ConvertRectFromScreen(GetWidget()->GetNativeWindow()->parent(), &bounds); |
| GetWidget()->SetBounds(bounds); |
| } |
| |
| void QuickAnswersView::UpdateQuickAnswerResult( |
| const QuickAnswer& quick_answer) { |
| // Check if the view (or any of its children) had focus before resetting the |
| // view, so it can be restored for the updated view. |
| bool pane_already_had_focus = Contains(GetFocusManager()->GetFocusedView()); |
| ResetContentView(); |
| |
| // Add title. |
| AddHorizontalUiElements(quick_answer.title, content_view_); |
| |
| // Add first row answer. |
| View* first_answer_view = nullptr; |
| if (!quick_answer.first_answer_row.empty()) { |
| first_answer_view = |
| AddHorizontalUiElements(quick_answer.first_answer_row, content_view_); |
| } |
| bool first_answer_is_single_label = |
| first_answer_view->children().size() == 1 && |
| first_answer_view->children().front()->GetClassName() == |
| views::Label::kViewClassName; |
| if (first_answer_is_single_label) { |
| // Update answer announcement. |
| auto* answer_label = |
| static_cast<Label*>(first_answer_view->children().front()); |
| GetViewAccessibility().OverrideDescription(l10n_util::GetStringFUTF8( |
| IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_DESC_TEMPLATE, |
| answer_label->GetText())); |
| } |
| |
| // Add second row answer. |
| if (!quick_answer.second_answer_row.empty()) { |
| AddHorizontalUiElements(quick_answer.second_answer_row, content_view_); |
| } else { |
| // If secondary-answer does not exist and primary-answer is a single label, |
| // allow that label to wrap through to the row intended for the former. |
| if (first_answer_is_single_label) { |
| // Cache multi-line label for resizing when view bounds change. |
| first_answer_label_ = |
| static_cast<Label*>(first_answer_view->children().front()); |
| first_answer_label_->SetMultiLine(true); |
| first_answer_label_->SetMaxLines(kMaxRows - /*exclude title*/ 1); |
| } |
| } |
| |
| // Restore focus if the view had one prior to updating the answer. |
| if (pane_already_had_focus) { |
| RequestFocus(); |
| } else { |
| // Announce that a Quick Answer is available. |
| GetViewAccessibility().AnnounceText(l10n_util::GetStringUTF16( |
| IDS_ASH_QUICK_ANSWERS_VIEW_A11Y_INFO_ALERT_TEXT)); |
| } |
| } |
| |
| void QuickAnswersView::SetButtonNotifyActionToOnPress(views::Button* button) { |
| DCHECK(button); |
| button->button_controller()->set_notify_action( |
| views::ButtonController::NotifyAction::kOnPress); |
| } |
| |
| std::vector<views::View*> QuickAnswersView::GetFocusableViews() { |
| std::vector<views::View*> focusable_views; |
| // The view itself does not gain focus for retry-view and transfers it to the |
| // retry-label, and so is not included when this is the case. |
| if (!retry_label_) |
| focusable_views.push_back(this); |
| if (retry_label_ && retry_label_->GetVisible()) |
| focusable_views.push_back(retry_label_); |
| if (dogfood_button_ && dogfood_button_->GetVisible()) |
| focusable_views.push_back(dogfood_button_); |
| return focusable_views; |
| } |
| |
| } // namespace ash |