From 4de38671036a9d406988116e5da5dec00282f294 Mon Sep 17 00:00:00 2001 From: Michelle Chen <michellegc@google.com> Date: Wed, 22 Jan 2025 20:51:21 -0800 Subject: [PATCH] scanner: Add ErrorView to show in the action button container. Add an ErrorView to be shown in the action button container if actions are not available. Followups will handle the logic for showing and hiding the ErrorView. Bug: b:378582420 Change-Id: I87471fa9465ee3f53dcab39ad96a91950e6552ce Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6189039 Reviewed-by: Elijah Hewer <hewer@chromium.org> Commit-Queue: Michelle Chen <michellegc@google.com> Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Cr-Commit-Position: refs/heads/main@{#1410114} --- .../action_button_container_view.cc | 113 +++++++++++++++++- .../action_button_container_view.h | 46 ++++++- .../action_button_container_view_unittest.cc | 17 +++ 3 files changed, 174 insertions(+), 2 deletions(-) diff --git a/ash/capture_mode/action_button_container_view.cc b/ash/capture_mode/action_button_container_view.cc index 2d235c22c9f8f..783db7bc5130d 100644 --- a/ash/capture_mode/action_button_container_view.cc +++ b/ash/capture_mode/action_button_container_view.cc @@ -11,24 +11,35 @@ #include "ash/capture_mode/action_button_view.h" #include "ash/capture_mode/capture_mode_types.h" +#include "ash/capture_mode/capture_mode_util.h" +#include "ash/style/system_shadow.h" +#include "ash/style/typography.h" #include "base/check.h" #include "base/functional/bind.h" #include "base/ranges/algorithm.h" #include "base/time/time.h" #include "ui/aura/window.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/compositor/layer_animator.h" #include "ui/gfx/animation/tween.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/rounded_corners_f.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/transform.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/views/animation/animation_builder.h" +#include "ui/views/background.h" #include "ui/views/controls/button/button.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout_view.h" +#include "ui/views/vector_icons.h" #include "ui/views/view.h" +#include "ui/views/view_class_properties.h" #include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" @@ -36,6 +47,19 @@ namespace ash { namespace { +// Horizontal spacing between the error view and action buttons. +constexpr int kErrorViewActionButtonSpacing = 6; + +constexpr auto kErrorViewBorderInsets = gfx::Insets::TLBR(8, 8, 8, 12); + +constexpr int kErrorViewCornerRadius = 18; + +constexpr int kErrorViewLeadingIconSize = 20; + +// Padding to the right of the error view's leading icon, to separate the icon +// from the error message label. +constexpr auto kErrorViewLeadingIconRightPadding = 4; + // The horizontal distance between action buttons in a row. constexpr int kActionButtonSpacing = 10; @@ -56,8 +80,82 @@ constexpr base::TimeDelta kSmartActionsButtonTransitionSlideInDuration = } // namespace +ActionButtonContainerView::ErrorView::ErrorView() + : shadow_(SystemShadow::CreateShadowOnTextureLayer( + SystemShadow::Type::kElevation12)) { + SetOrientation(views::BoxLayout::Orientation::kHorizontal); + SetInsideBorderInsets(kErrorViewBorderInsets); + + SetBackground(views::CreateThemedRoundedRectBackground( + cros_tokens::kCrosSysSystemBaseElevated, + gfx::RoundedCornersF(kErrorViewCornerRadius))); + SetPaintToLayer(); + layer()->SetFillsBoundsOpaquely(false); + shadow_->SetRoundedCornerRadius(kErrorViewCornerRadius); + capture_mode_util::SetHighlightBorder( + this, kErrorViewCornerRadius, + views::HighlightBorder::Type::kHighlightBorderNoShadow); + + AddChildView(views::Builder<views::ImageView>() + .SetPreferredSize(gfx::Size(kErrorViewLeadingIconSize, + kErrorViewLeadingIconSize)) + .SetImage(ui::ImageModel::FromVectorIcon( + views::kInfoIcon, cros_tokens::kCrosSysSecondary)) + .SetProperty(views::kMarginsKey, + gfx::Insets::TLBR( + 0, 0, 0, kErrorViewLeadingIconRightPadding)) + .Build()); + + AddChildView( + views::Builder<views::Label>() + .CopyAddressTo(&error_label_) + .SetEnabledColorId(cros_tokens::kCrosSysSecondary) + .SetFontList(TypographyProvider::Get()->ResolveTypographyToken( + TypographyToken::kCrosAnnotation1)) + .Build()); +} + +ActionButtonContainerView::ErrorView::~ErrorView() = default; + +void ActionButtonContainerView::ErrorView::AddedToWidget() { + views::BoxLayoutView::AddedToWidget(); + + // Attach the shadow at the bottom of the widget layer. + ui::Layer* shadow_layer = shadow_->GetLayer(); + ui::Layer* widget_layer = GetWidget()->GetLayer(); + widget_layer->Add(shadow_layer); + widget_layer->StackAtBottom(shadow_layer); + + // Make the shadow observe the color provider source change to update the + // colors. + shadow_->ObserveColorProviderSource(GetWidget()); +} + +void ActionButtonContainerView::ErrorView::OnBoundsChanged( + const gfx::Rect& previous_bounds) { + // The shadow layer is a sibling of this view's layer, and should have the + // same bounds. + shadow_->SetContentBounds(layer()->bounds()); +} + +void ActionButtonContainerView::ErrorView::SetErrorMessage( + const std::u16string& error_message) { + error_label_->SetText(error_message); +} + +const std::u16string& +ActionButtonContainerView::ErrorView::GetErrorMessageForTesting() const { + return error_label_->GetText(); +} + ActionButtonContainerView::ActionButtonContainerView() { - SetUseDefaultFillLayout(true); + auto* box_layout = SetLayoutManager(std::make_unique<views::BoxLayout>( + views::BoxLayout::Orientation::kHorizontal)); + box_layout->set_between_child_spacing(kErrorViewActionButtonSpacing); + + error_view_ = AddChildView(std::make_unique<ErrorView>()); + error_view_->SetVisible(false); + AddChildView( views::Builder<views::BoxLayoutView>() .CopyAddressTo(&action_button_row_) @@ -124,6 +222,16 @@ const views::View::Views& ActionButtonContainerView::GetActionButtons() const { return action_button_row_->children(); } +void ActionButtonContainerView::ShowErrorView( + const std::u16string& error_message) { + error_view_->SetErrorMessage(error_message); + error_view_->SetVisible(true); +} + +void ActionButtonContainerView::HideErrorView() { + error_view_->SetVisible(false); +} + void ActionButtonContainerView::StartSmartActionsButtonTransition() { views::Widget* widget = GetWidget(); if (!widget) { @@ -214,6 +322,9 @@ void ActionButtonContainerView::SetWidgetEventsEnabled(bool enabled) { : aura::EventTargetingPolicy::kNone); } +BEGIN_METADATA(ActionButtonContainerView, ErrorView) +END_METADATA + BEGIN_METADATA(ActionButtonContainerView) END_METADATA diff --git a/ash/capture_mode/action_button_container_view.h b/ash/capture_mode/action_button_container_view.h index cffce6f621f1b..26a7059355c3f 100644 --- a/ash/capture_mode/action_button_container_view.h +++ b/ash/capture_mode/action_button_container_view.h @@ -5,6 +5,7 @@ #ifndef ASH_CAPTURE_MODE_ACTION_BUTTON_CONTAINER_VIEW_H_ #define ASH_CAPTURE_MODE_ACTION_BUTTON_CONTAINER_VIEW_H_ +#include <memory> #include <string> #include "ash/ash_export.h" @@ -13,21 +14,53 @@ #include "base/memory/weak_ptr.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/controls/button/button.h" +#include "ui/views/layout/box_layout_view.h" #include "ui/views/view.h" namespace gfx { struct VectorIcon; } // namespace gfx +namespace views { +class Label; +} + namespace ash { class ActionButtonView; +class SystemShadow; -// A view that displays a row of action buttons near the capture region. +// A view that displays a row of action buttons near the capture region. It may +// display an error message if actions are not available. class ASH_EXPORT ActionButtonContainerView : public views::View { METADATA_HEADER(ActionButtonContainerView, views::View) public: + // A view that displays an error message and icon. + class ASH_EXPORT ErrorView : public views::BoxLayoutView { + METADATA_HEADER(ErrorView, views::BoxLayoutView) + + public: + ErrorView(); + ErrorView(const ErrorView&) = delete; + ErrorView& operator=(const ErrorView&) = delete; + ~ErrorView() override; + + // views::BoxLayoutView: + void AddedToWidget() override; + void OnBoundsChanged(const gfx::Rect& previous_bounds) override; + + // Sets the error message to show on the error view. + void SetErrorMessage(const std::u16string& error_message); + + const std::u16string& GetErrorMessageForTesting() const; + + private: + std::unique_ptr<SystemShadow> shadow_; + + raw_ptr<views::Label> error_label_ = nullptr; + }; + ActionButtonContainerView(); ActionButtonContainerView(const ActionButtonContainerView&) = delete; ActionButtonContainerView& operator=(const ActionButtonContainerView&) = @@ -50,12 +83,20 @@ class ASH_EXPORT ActionButtonContainerView : public views::View { // Returns the action buttons in this container. const views::View::Views& GetActionButtons() const; + // Shows an error view with the given `error_message`. + void ShowErrorView(const std::u16string& error_message); + + // Hides the error view. + void HideErrorView(); + // Starts performing the button transition triggered after pressing the smart // actions button. This will fade out existing action buttons, remove the // smart actions button, then animate in new icon buttons to replace the old // copy text and search buttons. void StartSmartActionsButtonTransition(); + const ErrorView* error_view_for_testing() const { return error_view_; } + private: // Called when the smart actions button has faded out, to start the transition // to new buttons. See `StartSmartActionsButtonTransition()`. @@ -67,6 +108,9 @@ class ASH_EXPORT ActionButtonContainerView : public views::View { // Contains the row of action buttons. raw_ptr<views::View> action_button_row_ = nullptr; + // Used to show an error message. + raw_ptr<ErrorView> error_view_ = nullptr; + base::WeakPtrFactory<ActionButtonContainerView> weak_ptr_factory_{this}; }; diff --git a/ash/capture_mode/action_button_container_view_unittest.cc b/ash/capture_mode/action_button_container_view_unittest.cc index 778837c6a63ac..d753487cc44b2 100644 --- a/ash/capture_mode/action_button_container_view_unittest.cc +++ b/ash/capture_mode/action_button_container_view_unittest.cc @@ -124,5 +124,22 @@ TEST_F(ActionButtonContainerViewTest, SmartActionsButtonTransition) { EXPECT_TRUE(IsActionButtonCollapsed(search_button)); } +TEST_F(ActionButtonContainerViewTest, ShowsErrorView) { + ActionButtonContainerView action_button_container; + const ActionButtonContainerView::ErrorView* error_view = + action_button_container.error_view_for_testing(); + + EXPECT_FALSE(error_view->GetVisible()); + + action_button_container.ShowErrorView(u"Error message"); + + EXPECT_TRUE(error_view->GetVisible()); + EXPECT_EQ(error_view->GetErrorMessageForTesting(), u"Error message"); + + action_button_container.HideErrorView(); + + EXPECT_FALSE(error_view->GetVisible()); +} + } // namespace } // namespace ash