Add RefreshBannerView for Mahi Panel.
Bug: b:319731624 Change-Id: I60ab57ca1dca1590d0872416a13701182cde4f9f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5336604 Commit-Queue: Ahmed Mehfooz <amehfooz@chromium.org> Reviewed-by: Andre Le <leandre@chromium.org> Cr-Commit-Position: refs/heads/main@{#1268822}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
2dd86ccb39
commit
6995e7f697
@ -1777,6 +1777,8 @@ component("ash") {
|
||||
"system/mahi/mahi_panel_view.h",
|
||||
"system/mahi/mahi_panel_widget.cc",
|
||||
"system/mahi/mahi_panel_widget.h",
|
||||
"system/mahi/refresh_banner_view.cc",
|
||||
"system/mahi/refresh_banner_view.h",
|
||||
"system/mahi/summary_outlines_section.cc",
|
||||
"system/mahi/summary_outlines_section.h",
|
||||
"system/media/media_color_theme.cc",
|
||||
|
@ -5132,6 +5132,9 @@ Some features are limited to increase battery life.
|
||||
<message name="IDS_ASH_MAHI_LEARN_MORE_LINK_LABEL_TEXT" desc="Text for the learn more link that user can navigate to learn more about the project information">
|
||||
Learn more
|
||||
</message>
|
||||
<message name="IDS_ASH_MAHI_REFRESH_BANNER_LABEL_TEXT" desc="Text for the refresh banner shown above the mahi panel.">
|
||||
Refresh <ph name="CONTENT_TITLE">$1<ex>test content title</ex></ph>
|
||||
</message>
|
||||
|
||||
<!-- Multi-profiles intro dialog -->
|
||||
<message name="IDS_ASH_MULTIPROFILES_INTRO_HEADLINE" desc="Describes which feature multi-profiles intro dialog presents.">
|
||||
|
@ -0,0 +1 @@
|
||||
c2c9d1ddb31f999d972f73a2a31939b30725348f
|
@ -18,6 +18,8 @@ enum ViewId {
|
||||
kThumbsUpButton,
|
||||
kThumbsDownButton,
|
||||
kLearnMoreLink,
|
||||
kRefreshView,
|
||||
kMahiPanelView,
|
||||
};
|
||||
|
||||
// The size of the icon that appears in the panel's source row.
|
||||
@ -28,6 +30,9 @@ inline constexpr char kMahiFeedbackHistogramName[] = "Ash.Mahi.Feedback";
|
||||
// TODO(b/319264190): Replace the string here with the correct URL.
|
||||
inline constexpr char kLearnMorePage[] = "https://google.com";
|
||||
|
||||
inline constexpr int kRefreshBannerStackDepth = 25;
|
||||
inline constexpr int kPanelCornerRadius = 16;
|
||||
|
||||
} // namespace ash::mahi_constants
|
||||
|
||||
#endif // ASH_SYSTEM_MAHI_MAHI_CONSTANTS_H_
|
||||
|
@ -58,7 +58,6 @@ namespace ash {
|
||||
namespace {
|
||||
|
||||
constexpr SkScalar kContentScrollViewCornerRadius = 16;
|
||||
constexpr int kPanelCornerRadius = 16;
|
||||
constexpr gfx::Insets kPanelPadding = gfx::Insets(16);
|
||||
constexpr int kPanelChildSpacing = 8;
|
||||
constexpr int kHeaderRowSpacing = 8;
|
||||
@ -116,19 +115,22 @@ MahiPanelView::MahiPanelView() {
|
||||
SetDefault(views::kMarginsKey, gfx::Insets::VH(kPanelChildSpacing, 0));
|
||||
SetIgnoreDefaultMainAxisMargins(true);
|
||||
SetCollapseMargins(true);
|
||||
SetID(mahi_constants::ViewId::kMahiPanelView);
|
||||
|
||||
SetBackground(views::CreateThemedRoundedRectBackground(
|
||||
cros_tokens::kCrosSysSystemBaseElevated, kPanelCornerRadius));
|
||||
cros_tokens::kCrosSysSystemBaseElevated,
|
||||
mahi_constants::kPanelCornerRadius));
|
||||
|
||||
// Create a layer for the view for background blur and rounded corners.
|
||||
SetPaintToLayer();
|
||||
layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{kPanelCornerRadius});
|
||||
layer()->SetRoundedCornerRadius(
|
||||
gfx::RoundedCornersF{mahi_constants::kPanelCornerRadius});
|
||||
layer()->SetFillsBoundsOpaquely(false);
|
||||
layer()->SetIsFastRoundedCorner(true);
|
||||
layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
|
||||
layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
|
||||
SetBorder(std::make_unique<views::HighlightBorder>(
|
||||
kPanelCornerRadius,
|
||||
mahi_constants::kPanelCornerRadius,
|
||||
views::HighlightBorder::Type::kHighlightBorderOnShadow,
|
||||
/*insets_type=*/views::HighlightBorder::InsetsType::kHalfInsets));
|
||||
|
||||
|
@ -9,10 +9,13 @@
|
||||
|
||||
#include "ash/public/cpp/shell_window_ids.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/system/mahi/mahi_constants.h"
|
||||
#include "ash/system/mahi/mahi_panel_view.h"
|
||||
#include "ash/system/mahi/refresh_banner_view.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/compositor/layer_type.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/views/layout/box_layout_view.h"
|
||||
#include "ui/views/widget/unique_widget_ptr.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
@ -22,25 +25,44 @@ namespace {
|
||||
|
||||
constexpr int kPanelDefaultWidth = 340;
|
||||
constexpr int kPanelDefaultHeight = 450;
|
||||
constexpr int kPanelHeightWithRefreshBanner = 482;
|
||||
constexpr int kPanelBoundsPadding = 8;
|
||||
|
||||
gfx::Rect CalculateWidgetBounds(aura::Window* root_window) {
|
||||
gfx::Rect CalculateWidgetBounds(aura::Window* root_window,
|
||||
bool refresh_banner_shown = false) {
|
||||
auto display =
|
||||
display::Screen::GetScreen()->GetDisplayNearestWindow(root_window);
|
||||
auto bottom_right = display.work_area().bottom_right();
|
||||
int height = refresh_banner_shown ? kPanelHeightWithRefreshBanner
|
||||
: kPanelDefaultHeight;
|
||||
|
||||
// The panel is positioned at the bottom right corner of the screen.
|
||||
// TODO(b/319476980): Make sure Mahi main panel bounds work when shelf
|
||||
// alignment changes.
|
||||
return gfx::Rect(bottom_right.x() - kPanelDefaultWidth - kPanelBoundsPadding,
|
||||
bottom_right.y() - kPanelDefaultHeight - kPanelBoundsPadding,
|
||||
kPanelDefaultWidth, kPanelDefaultHeight);
|
||||
bottom_right.y() - height - kPanelBoundsPadding,
|
||||
kPanelDefaultWidth, height);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MahiPanelWidget::MahiPanelWidget(InitParams params)
|
||||
: views::Widget(std::move(params)) {}
|
||||
: views::Widget(std::move(params)) {
|
||||
auto* contents_view = SetContentsView(
|
||||
views::Builder<views::BoxLayoutView>()
|
||||
// We need to set a negative value for between child spacing here
|
||||
// because we need the `RefreshBannerView` to overlap with the
|
||||
// `MahiPanelView`.
|
||||
.SetBetweenChildSpacing(-mahi_constants::kRefreshBannerStackDepth)
|
||||
.SetOrientation(views::BoxLayout::Orientation::kVertical)
|
||||
.Build());
|
||||
|
||||
refresh_view_ =
|
||||
contents_view->AddChildView(std::make_unique<RefreshBannerView>());
|
||||
refresh_view_observation_.Observe(refresh_view_);
|
||||
|
||||
contents_view->AddChildView(std::make_unique<MahiPanelView>());
|
||||
}
|
||||
|
||||
MahiPanelWidget::~MahiPanelWidget() = default;
|
||||
|
||||
@ -61,13 +83,35 @@ views::UniqueWidgetPtr MahiPanelWidget::CreatePanelWidget(int64_t display_id) {
|
||||
views::UniqueWidgetPtr widget =
|
||||
std::make_unique<MahiPanelWidget>(std::move(params));
|
||||
|
||||
widget->SetContentsView(std::make_unique<MahiPanelView>());
|
||||
widget->SetBounds(CalculateWidgetBounds(root_window));
|
||||
return widget;
|
||||
}
|
||||
|
||||
void MahiPanelWidget::SetRefreshViewVisible(bool visible) {
|
||||
// TODO(b/319731624): Finish this function.
|
||||
if (!refresh_view_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refresh_view_->GetVisible() == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
visible ? refresh_view_->Show() : refresh_view_->Hide();
|
||||
}
|
||||
|
||||
void MahiPanelWidget::OnViewVisibilityChanged(views::View* observed_view,
|
||||
views::View* starting_view) {
|
||||
CHECK_EQ(observed_view, refresh_view_);
|
||||
|
||||
SetBounds(
|
||||
CalculateWidgetBounds(GetNativeWindow(), observed_view->GetVisible()));
|
||||
}
|
||||
|
||||
void MahiPanelWidget::OnViewIsDeleting(views::View* observed_view) {
|
||||
CHECK_EQ(observed_view, refresh_view_);
|
||||
|
||||
refresh_view_observation_.Reset();
|
||||
refresh_view_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace ash
|
||||
|
@ -6,15 +6,18 @@
|
||||
#define ASH_SYSTEM_MAHI_MAHI_PANEL_WIDGET_H_
|
||||
|
||||
#include "ash/ash_export.h"
|
||||
#include "ui/views/view_observer.h"
|
||||
#include "ui/views/widget/unique_widget_ptr.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
class RefreshBannerView;
|
||||
|
||||
// The widget that contains the Mahi panel.
|
||||
// TODO(b/319329379): Use this class in `CreatePanelWidget()` when resizing and
|
||||
// closing capability is added.
|
||||
class ASH_EXPORT MahiPanelWidget : public views::Widget {
|
||||
class ASH_EXPORT MahiPanelWidget : public views::Widget, views::ViewObserver {
|
||||
public:
|
||||
explicit MahiPanelWidget(InitParams params);
|
||||
|
||||
@ -28,6 +31,18 @@ class ASH_EXPORT MahiPanelWidget : public views::Widget {
|
||||
|
||||
// Shows/hides the refresh UI in the panel.
|
||||
void SetRefreshViewVisible(bool visible);
|
||||
|
||||
private:
|
||||
// views::ViewObserver:
|
||||
void OnViewVisibilityChanged(views::View* observed_view,
|
||||
views::View* starting_view) override;
|
||||
void OnViewIsDeleting(views::View* observed_view) override;
|
||||
|
||||
// Owned by views hierarchy.
|
||||
raw_ptr<RefreshBannerView> refresh_view_ = nullptr;
|
||||
|
||||
base::ScopedObservation<views::View, views::ViewObserver>
|
||||
refresh_view_observation_{this};
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "ash/public/cpp/shelf_config.h"
|
||||
#include "ash/system/mahi/fake_mahi_manager.h"
|
||||
#include "ash/system/mahi/mahi_constants.h"
|
||||
#include "ash/test/ash_test_base.h"
|
||||
#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
|
||||
#include "ui/aura/window.h"
|
||||
@ -58,5 +59,31 @@ TEST_F(MahiPanelWidgetTest, WidgetBounds) {
|
||||
widget->GetRestoredBounds());
|
||||
}
|
||||
|
||||
TEST_F(MahiPanelWidgetTest, WidgetBoundsWithRefreshBanner) {
|
||||
auto widget = MahiPanelWidget::CreatePanelWidget(GetPrimaryDisplay().id());
|
||||
|
||||
auto* panel_view = widget->GetContentsView()->GetViewByID(
|
||||
mahi_constants::ViewId::kMahiPanelView);
|
||||
|
||||
auto* refresh_view = widget->GetContentsView()->GetViewByID(
|
||||
mahi_constants::ViewId::kRefreshView);
|
||||
|
||||
auto panel_view_bounds = panel_view->GetBoundsInScreen();
|
||||
auto widget_bounds = widget->GetRestoredBounds();
|
||||
|
||||
refresh_view->SetVisible(true);
|
||||
|
||||
// Make sure the `MahiPanelView` has the exact same location on the screen
|
||||
// after the `RefreshBannerView` changes visibility.
|
||||
EXPECT_EQ(panel_view_bounds, panel_view->GetBoundsInScreen());
|
||||
|
||||
// The widget's height should increase by the height of the
|
||||
// `RefreshBannerView` subtracted by `kRefreshBannerStackDepth`.
|
||||
int height_delta =
|
||||
widget->GetRestoredBounds().height() - widget_bounds.height();
|
||||
EXPECT_EQ(height_delta, refresh_view->GetBoundsInScreen().height() -
|
||||
mahi_constants::kRefreshBannerStackDepth);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace ash
|
||||
|
158
ash/system/mahi/refresh_banner_view.cc
Normal file
158
ash/system/mahi/refresh_banner_view.cc
Normal file
@ -0,0 +1,158 @@
|
||||
// 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/system/mahi/refresh_banner_view.h"
|
||||
#include <string>
|
||||
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/style/icon_button.h"
|
||||
#include "ash/style/typography.h"
|
||||
#include "ash/system/mahi/mahi_constants.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/time/time.h"
|
||||
#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
|
||||
#include "components/vector_icons/vector_icons.h"
|
||||
#include "third_party/skia/include/core/SkPath.h"
|
||||
#include "third_party/skia/include/core/SkPathBuilder.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
|
||||
#include "ui/compositor/layer.h"
|
||||
#include "ui/views/animation/animation_builder.h"
|
||||
#include "ui/views/background.h"
|
||||
#include "ui/views/controls/label.h"
|
||||
#include "ui/views/layout/flex_layout_types.h"
|
||||
#include "ui/views/layout/flex_layout_view.h"
|
||||
#include "ui/views/layout/layout_types.h"
|
||||
#include "ui/views/view_class_properties.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kRefreshBannerCornerRadius = 20;
|
||||
constexpr base::TimeDelta kRefreshBannerAnimationDurationMs =
|
||||
base::Milliseconds(100);
|
||||
constexpr gfx::Insets kRefreshBannerInteriorMargin =
|
||||
gfx::Insets::TLBR(4, 28, mahi_constants::kRefreshBannerStackDepth + 4, 28);
|
||||
constexpr gfx::Insets kTitleLabelMargin = gfx::Insets::TLBR(0, 0, 0, 8);
|
||||
|
||||
SkPath GetClipPath(gfx::Size size) {
|
||||
int width = size.width();
|
||||
int height = size.height();
|
||||
|
||||
auto top_left = SkPoint::Make(0, 0);
|
||||
auto top_right = SkPoint::Make(width, 0);
|
||||
auto bottom_left = SkPoint::Make(0, height);
|
||||
auto bottom_right = SkPoint::Make(width, height);
|
||||
int radius = kRefreshBannerCornerRadius;
|
||||
int bottom_radius = mahi_constants::kPanelCornerRadius;
|
||||
|
||||
const auto horizontal_offset = SkPoint::Make(radius, 0.f);
|
||||
const auto vertical_offset = SkPoint::Make(0.f, radius);
|
||||
const auto bottom_vertical_offset =
|
||||
SkPoint::Make(0.f, mahi_constants::kRefreshBannerStackDepth - 1);
|
||||
const auto bottom_horizontal_offset = SkPoint::Make(bottom_radius, 0.f);
|
||||
|
||||
return SkPathBuilder()
|
||||
// Start just before the curve of the top-left corner.
|
||||
.moveTo(radius, 0.f)
|
||||
// Draw the top-left rounded corner.
|
||||
.arcTo(top_left, top_left + vertical_offset, radius)
|
||||
// Draw the bottom-left rounded corner and the vertical line
|
||||
// connecting it to the top-left corner.
|
||||
.lineTo(bottom_left)
|
||||
.arcTo(bottom_left - bottom_vertical_offset,
|
||||
bottom_left - bottom_vertical_offset + bottom_horizontal_offset,
|
||||
radius)
|
||||
// Draw the bottom-right rounded corner and the horizontal line
|
||||
// connecting it to the bottom-left corner.
|
||||
.arcTo(bottom_right - bottom_vertical_offset, bottom_right, bottom_radius)
|
||||
.lineTo(bottom_right)
|
||||
.arcTo(top_right, top_right - horizontal_offset, bottom_radius)
|
||||
.lineTo(radius, 0.f)
|
||||
.close()
|
||||
.detach();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RefreshBannerView::RefreshBannerView() {
|
||||
auto* manager = chromeos::MahiManager::Get();
|
||||
|
||||
SetBackground(views::CreateThemedRoundedRectBackground(
|
||||
cros_tokens::kCrosSysSystemPrimaryContainer, /*radius=*/0));
|
||||
|
||||
SetOrientation(views::LayoutOrientation::kHorizontal);
|
||||
SetMainAxisAlignment(views::LayoutAlignment::kCenter);
|
||||
SetInteriorMargin(kRefreshBannerInteriorMargin);
|
||||
SetID(mahi_constants::ViewId::kRefreshView);
|
||||
|
||||
// We need to paint this view to a layer for animations.
|
||||
SetPaintToLayer();
|
||||
SetVisible(false);
|
||||
|
||||
AddChildView(
|
||||
views::Builder<views::Label>()
|
||||
.SetText(l10n_util::GetStringFUTF16(
|
||||
IDS_ASH_MAHI_REFRESH_BANNER_LABEL_TEXT,
|
||||
manager ? manager->GetContentTitle() : base::EmptyString16()))
|
||||
.SetEnabledColorId(cros_tokens::kCrosSysOnPrimaryContainer)
|
||||
.SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
|
||||
TypographyToken::kCrosAnnotation2))
|
||||
.SetProperty(views::kMarginsKey, kTitleLabelMargin)
|
||||
.Build());
|
||||
auto* icon_button =
|
||||
AddChildView(IconButton::Builder()
|
||||
.SetVectorIcon(&vector_icons::kReloadChromeRefreshIcon)
|
||||
.SetType(IconButton::Type::kSmallProminentFloating)
|
||||
.Build());
|
||||
icon_button->SetIconColor(cros_tokens::kCrosSysPrimary);
|
||||
}
|
||||
|
||||
RefreshBannerView::~RefreshBannerView() = default;
|
||||
|
||||
void RefreshBannerView::Show() {
|
||||
SetVisible(true);
|
||||
gfx::Transform transform;
|
||||
transform.Translate(
|
||||
gfx::Vector2d(0, mahi_constants::kRefreshBannerStackDepth));
|
||||
views::AnimationBuilder()
|
||||
.Once()
|
||||
.SetDuration(base::TimeDelta())
|
||||
.SetOpacity(this, 0)
|
||||
.SetTransform(this, transform)
|
||||
.At(base::Milliseconds(0))
|
||||
.SetDuration(kRefreshBannerAnimationDurationMs)
|
||||
.SetOpacity(this, 1)
|
||||
.SetTransform(this, gfx::Transform());
|
||||
}
|
||||
|
||||
void RefreshBannerView::Hide() {
|
||||
views::AnimationBuilder()
|
||||
.OnEnded(base::BindOnce(
|
||||
[](base::WeakPtr<views::View> view) {
|
||||
if (view) {
|
||||
view->SetVisible(false);
|
||||
}
|
||||
},
|
||||
weak_ptr_factory_.GetWeakPtr()))
|
||||
.Once()
|
||||
.SetDuration(kRefreshBannerAnimationDurationMs)
|
||||
.SetOpacity(this, 0.0);
|
||||
}
|
||||
|
||||
void RefreshBannerView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
|
||||
SetClipPath(GetClipPath(GetContentsBounds().size()));
|
||||
|
||||
// Make sure the refresh banner is always shown on top.
|
||||
if (layer() && layer()->parent()) {
|
||||
layer()->parent()->StackAtTop(layer());
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN_METADATA(RefreshBannerView)
|
||||
END_METADATA
|
||||
|
||||
} // namespace ash
|
39
ash/system/mahi/refresh_banner_view.h
Normal file
39
ash/system/mahi/refresh_banner_view.h
Normal file
@ -0,0 +1,39 @@
|
||||
// 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.
|
||||
|
||||
#ifndef ASH_SYSTEM_MAHI_REFRESH_BANNER_VIEW_H_
|
||||
#define ASH_SYSTEM_MAHI_REFRESH_BANNER_VIEW_H_
|
||||
|
||||
#include "ash/ash_export.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "ui/base/metadata/metadata_header_macros.h"
|
||||
#include "ui/views/layout/flex_layout_view.h"
|
||||
|
||||
namespace ash {
|
||||
|
||||
class ASH_EXPORT RefreshBannerView : public views::FlexLayoutView {
|
||||
METADATA_HEADER(RefreshBannerView, views::FlexLayoutView)
|
||||
|
||||
public:
|
||||
RefreshBannerView();
|
||||
RefreshBannerView(const RefreshBannerView&) = delete;
|
||||
RefreshBannerView& operator=(const RefreshBannerView&) = delete;
|
||||
~RefreshBannerView() override;
|
||||
|
||||
// Shows the refresh banner on top of the Mahi panel by animating it.
|
||||
void Show();
|
||||
|
||||
// Hides the refresh banner by animating it out.
|
||||
void Hide();
|
||||
|
||||
private:
|
||||
// views::FlexLayoutView:
|
||||
void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
|
||||
|
||||
base::WeakPtrFactory<RefreshBannerView> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
||||
#endif // ASH_SYSTEM_MAHI_REFRESH_BANNER_VIEW_H_
|
Reference in New Issue
Block a user