diff options
author | yoshiki <yoshiki@chromium.org> | 2016-03-23 18:24:43 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-03-24 01:26:06 +0000 |
commit | 32f516395c1d165053be9b93ef9d2cbdfd94685a (patch) | |
tree | 7e11f0316ea16dc9ddc52a4e322a2a773aad7ad8 /ash | |
parent | 5cb6ce43b7cb7d64d170d91a165910563537d10c (diff) | |
download | chromium_src-32f516395c1d165053be9b93ef9d2cbdfd94685a.zip chromium_src-32f516395c1d165053be9b93ef9d2cbdfd94685a.tar.gz chromium_src-32f516395c1d165053be9b93ef9d2cbdfd94685a.tar.bz2 |
Ash: Implement Toasts
This patch adds the implementation of toast. Currently this is not used from anywhere. The client code will be added in separated patch.
BUG=b/25797993
Review URL: https://codereview.chromium.org/1782793002
Cr-Commit-Position: refs/heads/master@{#383003}
Diffstat (limited to 'ash')
-rw-r--r-- | ash/ash.gyp | 5 | ||||
-rw-r--r-- | ash/ash_strings.grd | 4 | ||||
-rw-r--r-- | ash/shelf/shelf_layout_manager.h | 1 | ||||
-rw-r--r-- | ash/shell.cc | 7 | ||||
-rw-r--r-- | ash/shell.h | 4 | ||||
-rw-r--r-- | ash/system/toast/OWNER | 1 | ||||
-rw-r--r-- | ash/system/toast/toast_manager.cc | 63 | ||||
-rw-r--r-- | ash/system/toast/toast_manager.h | 53 | ||||
-rw-r--r-- | ash/system/toast/toast_manager_unittest.cc | 264 | ||||
-rw-r--r-- | ash/system/toast/toast_overlay.cc | 275 | ||||
-rw-r--r-- | ash/system/toast/toast_overlay.h | 70 |
11 files changed, 747 insertions, 0 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 4b49388..fee27ca 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -407,6 +407,10 @@ 'system/status_area_widget_delegate.h', 'system/system_notifier.cc', 'system/system_notifier.h', + 'system/toast/toast_manager.cc', + 'system/toast/toast_manager.h', + 'system/toast/toast_overlay.cc', + 'system/toast/toast_overlay.h', 'system/tray/actionable_view.cc', 'system/tray/actionable_view.h', 'system/tray/default_system_tray_delegate.cc', @@ -885,6 +889,7 @@ 'system/date/date_view_unittest.cc', 'system/ime/tray_ime_chromeos_unittest.cc', 'system/overview/overview_button_tray_unittest.cc', + 'system/toast/toast_manager_unittest.cc', 'system/tray/media_security/multi_profile_media_tray_item_unittest.cc', 'system/tray/system_tray_unittest.cc', 'system/tray/tray_details_view_unittest.cc', diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 7a6c86d..e3489bd 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd @@ -360,6 +360,10 @@ Press Ctrl+Alt+Z to disable. Previous menu </message> + <message name="IDS_ASH_TOAST_DISMISS_BUTTON" desc="The text button shown in toasts to close the toast immediately without waiting timeout."> + DISMISS + </message> + <message name="IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS" desc="The text shown in the notification icon for the unread count when the count is more than 9."> 9+ </message> diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h index ae4a281..6c9bff9 100644 --- a/ash/shelf/shelf_layout_manager.h +++ b/ash/shelf/shelf_layout_manager.h @@ -220,6 +220,7 @@ class ASH_EXPORT ShelfLayoutManager friend class ash::ScreenAsh; friend class PanelLayoutManagerTest; friend class ShelfLayoutManagerTest; + friend class ToastManagerTest; FRIEND_TEST_ALL_PREFIXES(ash::AshPopupAlignmentDelegateTest, AutoHide); FRIEND_TEST_ALL_PREFIXES(ash::WebNotificationTrayTest, PopupAndFullscreen); diff --git a/ash/shell.cc b/ash/shell.cc index 71231a2..2e100f0 100644 --- a/ash/shell.cc +++ b/ash/shell.cc @@ -53,6 +53,7 @@ #include "ash/shell_window_ids.h" #include "ash/system/locale/locale_notification_controller.h" #include "ash/system/status_area_widget.h" +#include "ash/system/toast/toast_manager.h" #include "ash/system/tray/system_tray_delegate.h" #include "ash/system/tray/system_tray_notifier.h" #include "ash/utility/partial_screenshot_controller.h" @@ -734,6 +735,9 @@ Shell::~Shell() { // layout. DeactivateKeyboard(); + // Destroy toasts + toast_manager_.reset(); + // Destroy SystemTrayDelegate before destroying the status area(s). Make sure // to deinitialize the shelf first, as it is initialized after the delegate. HideShelf(); @@ -1059,6 +1063,9 @@ void Shell::Init(const ShellInitParams& init_params) { // Initialize system_tray_delegate_ after StatusAreaWidget is created. system_tray_delegate_->Initialize(); + // Initialize toast manager + toast_manager_.reset(new ToastManager); + #if defined(OS_CHROMEOS) // Create the LogoutConfirmationController after the SystemTrayDelegate. logout_confirmation_controller_.reset(new LogoutConfirmationController( diff --git a/ash/shell.h b/ash/shell.h index d929dba..c71e003 100644 --- a/ash/shell.h +++ b/ash/shell.h @@ -141,6 +141,7 @@ class SystemModalContainerEventFilter; class SystemTray; class SystemTrayDelegate; class SystemTrayNotifier; +class ToastManager; class ToplevelWindowEventHandler; class TouchTransformerController; class TouchObserverHUD; @@ -585,6 +586,8 @@ class ASH_EXPORT Shell : public SystemModalContainerEventFilterDelegate, } #endif // defined(OS_CHROMEOS) + ToastManager* toast_manager() { return toast_manager_.get(); } + GPUSupport* gpu_support() { return gpu_support_.get(); } private: @@ -747,6 +750,7 @@ class ASH_EXPORT Shell : public SystemModalContainerEventFilterDelegate, scoped_ptr<ui::EventHandler> speech_feedback_handler_; #endif // defined(OS_CHROMEOS) + scoped_ptr<ToastManager> toast_manager_; scoped_ptr<MaximizeModeController> maximize_mode_controller_; // |native_cursor_manager_| is owned by |cursor_manager_|, but we keep a diff --git a/ash/system/toast/OWNER b/ash/system/toast/OWNER new file mode 100644 index 0000000..407b30d --- /dev/null +++ b/ash/system/toast/OWNER @@ -0,0 +1 @@ +yoshiki@chromium.org diff --git a/ash/system/toast/toast_manager.cc b/ash/system/toast/toast_manager.cc new file mode 100644 index 0000000..137a71f --- /dev/null +++ b/ash/system/toast/toast_manager.cc @@ -0,0 +1,63 @@ +// Copyright 2016 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/system/toast/toast_manager.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/thread_task_runner_handle.h" + +namespace ash { + +namespace { + +// Minimum duration for a toast to be visible (in millisecond). +uint64_t kMinimumDurationMs = 200; + +} // anonymous namespace + +ToastManager::ToastManager() : weak_ptr_factory_(this) {} + +ToastManager::~ToastManager() {} + +void ToastManager::Show(const std::string& text, uint64_t duration_ms) { + queue_.emplace(std::make_pair(text, duration_ms)); + + if (queue_.size() == 1 && overlay_ == nullptr) + ShowLatest(); +} + +void ToastManager::OnClosed() { + overlay_.reset(); + + // Show the next toast if available. + if (queue_.size() != 0) + ShowLatest(); +} + +void ToastManager::ShowLatest() { + DCHECK(!overlay_); + + auto data = queue_.front(); + uint64_t duration_ms = std::max(data.second, kMinimumDurationMs); + + toast_id_++; + + overlay_.reset(new ToastOverlay(this, data.first /* text */)); + overlay_->Show(true); + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&ToastManager::OnDurationPassed, + weak_ptr_factory_.GetWeakPtr(), toast_id_), + base::TimeDelta::FromMilliseconds(duration_ms)); + + queue_.pop(); +} + +void ToastManager::OnDurationPassed(int toast_id) { + if (overlay_ && toast_id_ == toast_id) + overlay_->Show(false); +} + +} // namespace ash diff --git a/ash/system/toast/toast_manager.h b/ash/system/toast/toast_manager.h new file mode 100644 index 0000000..4e1fc80 --- /dev/null +++ b/ash/system/toast/toast_manager.h @@ -0,0 +1,53 @@ +// Copyright 2016 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. + +#ifndef ASH_SYSTEM_TOAST_TOAST_MANAGER_H_ +#define ASH_SYSTEM_TOAST_TOAST_MANAGER_H_ + +#include <queue> +#include <string> + +#include "ash/ash_export.h" +#include "ash/shell_observer.h" +#include "ash/system/toast/toast_overlay.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" + +namespace ash { + +// Class managing toast requests. +class ASH_EXPORT ToastManager : public ToastOverlay::Delegate { + public: + ToastManager(); + ~ToastManager() override; + + // Show a toast. If there are queued toasts, succeeding toasts are queued as + // well, and are shown one by one. + void Show(const std::string& text, uint64_t duration_ms); + + // ToastOverlay::Delegate overrides: + void OnClosed() override; + + private: + friend class ToastManagerTest; + + void ShowLatest(); + void OnDurationPassed(int toast_id); + + int toast_id_for_testing() const { return toast_id_; } + ToastOverlay* GetCurrentOverlayForTesting() { return overlay_.get(); } + void ResetToastIdForTesting() { toast_id_ = 0; } + + int toast_id_ = 0; + std::queue<std::pair<std::string, uint64_t>> queue_; + scoped_ptr<ToastOverlay> overlay_; + + base::WeakPtrFactory<ToastManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ToastManager); +}; + +} // namespace ash + +#endif // ASH_SYSTEM_TOAST_TOAST_MANAGER_H_ diff --git a/ash/system/toast/toast_manager_unittest.cc b/ash/system/toast/toast_manager_unittest.cc new file mode 100644 index 0000000..99d4ad0 --- /dev/null +++ b/ash/system/toast/toast_manager_unittest.cc @@ -0,0 +1,264 @@ +// Copyright 2016 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/display/display_manager.h" +#include "ash/screen_util.h" +#include "ash/shelf/shelf.h" +#include "ash/shelf/shelf_layout_manager.h" +#include "ash/shell.h" +#include "ash/system/toast/toast_manager.h" +#include "ash/test/ash_test_base.h" +#include "base/run_loop.h" + +namespace ash { + +// Long duration so the timeout doesn't occur. +const int64_t kLongLongDuration = INT64_MAX; + +class DummyEvent : public ui::Event { + public: + DummyEvent() : Event(ui::ET_UNKNOWN, base::TimeDelta(), 0) {} + ~DummyEvent() override {} +}; + +class ToastManagerTest : public test::AshTestBase { + public: + ToastManagerTest() {} + ~ToastManagerTest() override {} + + private: + void SetUp() override { + test::AshTestBase::SetUp(); + + manager_ = Shell::GetInstance()->toast_manager(); + + manager_->ResetToastIdForTesting(); + EXPECT_EQ(0, GetToastId()); + } + + protected: + ToastManager* manager() { return manager_; } + + int GetToastId() { return manager_->toast_id_for_testing(); } + + ToastOverlay* GetCurrentOverlay() { + return manager_->GetCurrentOverlayForTesting(); + } + + views::Widget* GetCurrentWidget() { + ToastOverlay* overlay = GetCurrentOverlay(); + return overlay ? overlay->widget_for_testing() : nullptr; + } + + std::string GetCurrentText() { + ToastOverlay* overlay = GetCurrentOverlay(); + return overlay ? overlay->text_ : std::string(); + } + + void ClickDismissButton() { + ToastOverlay* overlay = GetCurrentOverlay(); + if (overlay) + overlay->ClickDismissButtonForTesting(DummyEvent()); + } + + void SetShelfAlignment(ShelfAlignment alignment) { + Shelf::ForPrimaryDisplay()->shelf_layout_manager()->SetAlignment(alignment); + } + + void SetShelfState(ShelfVisibilityState state) { + Shelf::ForPrimaryDisplay()->shelf_layout_manager()->SetState(state); + } + + void SetShelfAutoHideBehavior(ShelfAutoHideBehavior behavior) { + Shelf::ForPrimaryDisplay()->shelf_layout_manager()->SetAutoHideBehavior( + behavior); + } + + private: + ToastManager* manager_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ToastManagerTest); +}; + +TEST_F(ToastManagerTest, ShowAndCloseAutomatically) { + manager()->Show("DUMMY", 10); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, GetToastId()); + + while (GetCurrentOverlay() != nullptr) + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ToastManagerTest, ShowAndCloseManually) { + manager()->Show("DUMMY", kLongLongDuration /* prevent timeout */); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, GetToastId()); + + ClickDismissButton(); + + while (GetCurrentOverlay() != nullptr) + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ToastManagerTest, QueueMessage) { + manager()->Show("DUMMY1", 10); + manager()->Show("DUMMY2", 10); + manager()->Show("DUMMY3", 10); + + while (GetToastId() != 1) + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ("DUMMY1", GetCurrentText()); + + while (GetToastId() != 2) + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ("DUMMY2", GetCurrentText()); + + while (GetToastId() != 3) + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ("DUMMY3", GetCurrentText()); +} + +TEST_F(ToastManagerTest, PositionWithVisibleBottomShelf) { + ShelfLayoutManager* shelf = + Shelf::ForPrimaryDisplay()->shelf_layout_manager(); + SetShelfState(ash::SHELF_VISIBLE); + SetShelfAlignment(ash::SHELF_ALIGNMENT_BOTTOM); + + manager()->Show("DUMMY", kLongLongDuration /* prevent timeout */); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, GetToastId()); + + gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen(); + gfx::Rect root_bounds = + ScreenUtil::GetShelfDisplayBoundsInRoot(Shell::GetPrimaryRootWindow()); + + EXPECT_TRUE(toast_bounds.Intersects(shelf->user_work_area_bounds())); + EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1); + + if (SupportsHostWindowResize()) { + // If host resize is not supported, ShelfLayoutManager::GetIdealBounds() + // doesn't return correct value. + gfx::Rect shelf_bounds = shelf->GetIdealBounds(); + EXPECT_FALSE(toast_bounds.Intersects(shelf_bounds)); + EXPECT_EQ(shelf_bounds.y() - 5, toast_bounds.bottom()); + EXPECT_EQ(root_bounds.bottom() - shelf_bounds.height() - 5, + toast_bounds.bottom()); + } +} + +TEST_F(ToastManagerTest, PositionWithAutoHiddenBottomShelf) { + scoped_ptr<aura::Window> window( + CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4))); + + ShelfLayoutManager* shelf = + Shelf::ForPrimaryDisplay()->shelf_layout_manager(); + SetShelfAlignment(ash::SHELF_ALIGNMENT_BOTTOM); + SetShelfAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); + shelf->LayoutShelf(); + EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + + manager()->Show("DUMMY", kLongLongDuration /* prevent timeout */); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, GetToastId()); + + gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen(); + gfx::Rect root_bounds = + ScreenUtil::GetShelfDisplayBoundsInRoot(Shell::GetPrimaryRootWindow()); + + EXPECT_TRUE(toast_bounds.Intersects(shelf->user_work_area_bounds())); + EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1); + EXPECT_EQ(root_bounds.bottom() - ShelfLayoutManager::kAutoHideSize - 5, + toast_bounds.bottom()); +} + +TEST_F(ToastManagerTest, PositionWithHiddenBottomShelf) { + ShelfLayoutManager* shelf = + Shelf::ForPrimaryDisplay()->shelf_layout_manager(); + SetShelfAutoHideBehavior(SHELF_AUTO_HIDE_ALWAYS_HIDDEN); + SetShelfAlignment(ash::SHELF_ALIGNMENT_BOTTOM); + SetShelfState(ash::SHELF_HIDDEN); + + manager()->Show("DUMMY", kLongLongDuration /* prevent timeout */); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, GetToastId()); + + gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen(); + gfx::Rect root_bounds = + ScreenUtil::GetShelfDisplayBoundsInRoot(Shell::GetPrimaryRootWindow()); + + EXPECT_TRUE(toast_bounds.Intersects(shelf->user_work_area_bounds())); + EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1); + EXPECT_EQ(root_bounds.bottom() - 5, toast_bounds.bottom()); +} + +TEST_F(ToastManagerTest, PositionWithVisibleLeftShelf) { + ShelfLayoutManager* shelf = + Shelf::ForPrimaryDisplay()->shelf_layout_manager(); + SetShelfState(ash::SHELF_VISIBLE); + SetShelfAlignment(ash::SHELF_ALIGNMENT_LEFT); + + manager()->Show("DUMMY", kLongLongDuration /* prevent timeout */); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, GetToastId()); + + gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen(); + gfx::Rect root_bounds = + ScreenUtil::GetShelfDisplayBoundsInRoot(Shell::GetPrimaryRootWindow()); + + EXPECT_TRUE(toast_bounds.Intersects(shelf->user_work_area_bounds())); + EXPECT_EQ(root_bounds.bottom() - 5, toast_bounds.bottom()); + + if (SupportsHostWindowResize()) { + // If host resize is not supported, ShelfLayoutManager::GetIdealBounds() + // doesn't return correct value. + gfx::Rect shelf_bounds = shelf->GetIdealBounds(); + EXPECT_FALSE(toast_bounds.Intersects(shelf_bounds)); + EXPECT_EQ( + shelf_bounds.right() + (root_bounds.width() - shelf_bounds.width()) / 2, + toast_bounds.CenterPoint().x()); + } +} + +TEST_F(ToastManagerTest, PositionWithUnifiedDesktop) { + if (!SupportsMultipleDisplays()) + return; + + DisplayManager* display_manager = Shell::GetInstance()->display_manager(); + display_manager->SetUnifiedDesktopEnabled(true); + UpdateDisplay("1000x500,0+600-100x500"); + + ShelfLayoutManager* shelf = + Shelf::ForPrimaryDisplay()->shelf_layout_manager(); + SetShelfState(ash::SHELF_VISIBLE); + SetShelfAlignment(ash::SHELF_ALIGNMENT_BOTTOM); + + manager()->Show("DUMMY", kLongLongDuration /* prevent timeout */); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, GetToastId()); + + gfx::Rect toast_bounds = GetCurrentWidget()->GetWindowBoundsInScreen(); + gfx::Rect root_bounds = + ScreenUtil::GetShelfDisplayBoundsInRoot(Shell::GetPrimaryRootWindow()); + + EXPECT_TRUE(toast_bounds.Intersects(shelf->user_work_area_bounds())); + EXPECT_TRUE(root_bounds.Contains(toast_bounds)); + EXPECT_NEAR(root_bounds.CenterPoint().x(), toast_bounds.CenterPoint().x(), 1); + + if (SupportsHostWindowResize()) { + // If host resize is not supported, ShelfLayoutManager::GetIdealBounds() + // doesn't return correct value. + gfx::Rect shelf_bounds = shelf->GetIdealBounds(); + EXPECT_FALSE(toast_bounds.Intersects(shelf_bounds)); + EXPECT_EQ(shelf_bounds.y() - 5, toast_bounds.bottom()); + EXPECT_EQ(root_bounds.bottom() - shelf_bounds.height() - 5, + toast_bounds.bottom()); + } +} + +} // namespace ash diff --git a/ash/system/toast/toast_overlay.cc b/ash/system/toast/toast_overlay.cc new file mode 100644 index 0000000..3864c9c --- /dev/null +++ b/ash/system/toast/toast_overlay.cc @@ -0,0 +1,275 @@ +// Copyright 2016 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/system/toast/toast_overlay.h" + +#include "ash/screen_util.h" +#include "ash/shelf/shelf.h" +#include "ash/shelf/shelf_layout_manager.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/window_animations.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/thread_task_runner_handle.h" +#include "grit/ash_strings.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/font_list.h" +#include "ui/views/border.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace ash { + +namespace { + +// Horizontal offset of the overlay from the bottom of the screen. +const int kVerticalOffset = 5; + +// Font style used for modifier key labels. +const ui::ResourceBundle::FontStyle kTextFontStyle = + ui::ResourceBundle::MediumFont; + +// Duration of slide animation when overlay is shown or hidden. +const int kSlideAnimationDurationMs = 100; + +// Colors for the dismiss button. +const SkColor kButtonBackgroundColor = SkColorSetARGB(0xFF, 0x32, 0x32, 0x32); +const SkColor kNormalButtonColor = SkColorSetARGB(0xFF, 0x64, 0xA5, 0xF5); +const SkColor kHoveredButtonColor = SkColorSetARGB(0xFF, 0xE3, 0xF2, 0xFD); +const SkColor kPressedButtonColor = SkColorSetARGB(0xFF, 0x64, 0xA5, 0xF5); + +} // anonymous namespace + +/////////////////////////////////////////////////////////////////////////////// +// ToastOverlayLabel +class ToastOverlayLabel : public views::Label { + public: + explicit ToastOverlayLabel(const std::string& label); + ~ToastOverlayLabel() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ToastOverlayLabel); +}; + +ToastOverlayLabel::ToastOverlayLabel(const std::string& label) { + ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); + + SetText(base::UTF8ToUTF16(label)); + SetHorizontalAlignment(gfx::ALIGN_LEFT); + SetFontList(rb->GetFontList(kTextFontStyle)); + SetAutoColorReadabilityEnabled(false); + SetFocusable(false); + SetEnabledColor(SK_ColorWHITE); + SetDisabledColor(SK_ColorWHITE); + SetSubpixelRenderingEnabled(false); +} + +ToastOverlayLabel::~ToastOverlayLabel() {} + +/////////////////////////////////////////////////////////////////////////////// +// ToastOverlayButton +class ToastOverlayButton : public views::LabelButton { + public: + explicit ToastOverlayButton(views::ButtonListener* listener, + const base::string16& label); + ~ToastOverlayButton() override {} + + private: + friend class ToastOverlay; // for ToastOverlay::ClickDismissButtonForTesting. + + DISALLOW_COPY_AND_ASSIGN(ToastOverlayButton); +}; + +ToastOverlayButton::ToastOverlayButton(views::ButtonListener* listener, + const base::string16& label) + : views::LabelButton(listener, label) { + ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); + + SetTextColor(STATE_NORMAL, kNormalButtonColor); + SetTextColor(STATE_HOVERED, kHoveredButtonColor); + SetTextColor(STATE_PRESSED, kPressedButtonColor); + SetFontList(rb->GetFontList(kTextFontStyle)); + SetBorder(views::Border::NullBorder()); +} + +/////////////////////////////////////////////////////////////////////////////// +// ToastOverlayView +class ToastOverlayView : public views::View, public views::ButtonListener { + public: + // This object is not owned by the views hiearchy or by the widget. + ToastOverlayView(ToastOverlay* overlay, const std::string& text); + ~ToastOverlayView() override; + + // views::View overrides: + void OnPaint(gfx::Canvas* canvas) override; + + ToastOverlayButton* button() { return button_; } + + private: + ToastOverlay* overlay_; // weak + ToastOverlayButton* button_; // weak + + void ButtonPressed(views::Button* sender, const ui::Event& event) override; + + DISALLOW_COPY_AND_ASSIGN(ToastOverlayView); +}; + +ToastOverlayView::ToastOverlayView(ToastOverlay* overlay, + const std::string& text) + : overlay_(overlay), + button_(new ToastOverlayButton( + this, + l10n_util::GetStringUTF16(IDS_ASH_TOAST_DISMISS_BUTTON))) { + const gfx::Font& font = + ui::ResourceBundle::GetSharedInstance().GetFont(kTextFontStyle); + int font_size = font.GetFontSize(); + + // Text should have a margin of 0.5 times the font size on each side, so + // the spacing between two labels will be the same as the font size. + int horizontal_spacing = font_size * 2; + int vertical_spacing = font_size; + int child_spacing = font_size * 4; + + SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, + horizontal_spacing, vertical_spacing, + child_spacing)); + + ToastOverlayLabel* label = new ToastOverlayLabel(text); + AddChildView(label); + label->SetVisible(true); + + AddChildView(button_); +} + +ToastOverlayView::~ToastOverlayView() {} + +void ToastOverlayView::OnPaint(gfx::Canvas* canvas) { + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(kButtonBackgroundColor); + canvas->DrawRoundRect(GetLocalBounds(), 2, paint); + views::View::OnPaint(canvas); +} + +void ToastOverlayView::ButtonPressed(views::Button* sender, + const ui::Event& event) { + overlay_->Show(false); +} + +/////////////////////////////////////////////////////////////////////////////// +// ToastOverlay +ToastOverlay::ToastOverlay(Delegate* delegate, const std::string& text) + : delegate_(delegate), + text_(text), + overlay_view_(new ToastOverlayView(this, text)), + widget_size_(overlay_view_->GetPreferredSize()) { + views::Widget::InitParams params; + params.type = views::Widget::InitParams::TYPE_POPUP; + params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.accept_events = true; + params.keep_on_top = true; + params.remove_standard_frame = true; + params.bounds = CalculateOverlayBounds(); + // Show toasts above the app list and below the lock screen. + params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(), + kShellWindowId_SystemModalContainer); + overlay_widget_.reset(new views::Widget); + overlay_widget_->Init(params); + overlay_widget_->SetVisibilityChangedAnimationsEnabled(true); + overlay_widget_->SetContentsView(overlay_view_.get()); + overlay_widget_->SetBounds(CalculateOverlayBounds()); + overlay_widget_->GetNativeView()->SetName("ToastOverlay"); + + gfx::NativeWindow native_view = overlay_widget_->GetNativeView(); + wm::SetWindowVisibilityAnimationType( + native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); + wm::SetWindowVisibilityAnimationDuration( + native_view, + base::TimeDelta::FromMilliseconds(kSlideAnimationDurationMs)); +} + +ToastOverlay::~ToastOverlay() { + gfx::NativeWindow native_view = overlay_widget_->GetNativeView(); + wm::SetWindowVisibilityAnimationTransition(native_view, wm::ANIMATE_NONE); + + // Remove ourself from the animator to avoid being re-entrantly called in + // |overlay_widget_|'s destructor. + ui::Layer* layer = overlay_widget_->GetLayer(); + if (layer) { + ui::LayerAnimator* animator = layer->GetAnimator(); + if (animator) + animator->RemoveObserver(this); + } + + overlay_widget_->Close(); +} + +void ToastOverlay::Show(bool visible) { + if (is_visible_ == visible) + return; + + is_visible_ = visible; + + overlay_widget_->GetLayer()->GetAnimator()->AddObserver(this); + + if (is_visible_) + overlay_widget_->Show(); + else + overlay_widget_->Hide(); +} + +gfx::Rect ToastOverlay::CalculateOverlayBounds() { + ShelfLayoutManager* shelf_layout_manager = + Shelf::ForPrimaryDisplay()->shelf_layout_manager(); + gfx::Rect work_area_bounds = shelf_layout_manager->user_work_area_bounds(); + + gfx::Rect bounds = shelf_layout_manager->user_work_area_bounds(); + int target_y = bounds.bottom() - widget_size_.height() - kVerticalOffset; + bounds.ClampToCenteredSize(widget_size_); + bounds.set_y(target_y); + return bounds; +} + +void ToastOverlay::OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) { + ui::LayerAnimator* animator = overlay_widget_->GetLayer()->GetAnimator(); + if (animator) + animator->RemoveObserver(this); + if (!is_visible_) { + // Acync operation, since delegate may remove this instance and removing + // this here causes crash. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&Delegate::OnClosed, + base::Unretained(delegate_) /* |delegate| lives longer */)); + } +} + +void ToastOverlay::OnLayerAnimationAborted( + ui::LayerAnimationSequence* sequence) { + ui::LayerAnimator* animator = overlay_widget_->GetLayer()->GetAnimator(); + if (animator) + animator->RemoveObserver(this); +} + +void ToastOverlay::OnLayerAnimationScheduled( + ui::LayerAnimationSequence* sequence) {} + +views::Widget* ToastOverlay::widget_for_testing() { + return overlay_widget_.get(); +} + +void ToastOverlay::ClickDismissButtonForTesting(const ui::Event& event) { + overlay_view_->button()->NotifyClick(event); +} + +} // namespace ash diff --git a/ash/system/toast/toast_overlay.h b/ash/system/toast/toast_overlay.h new file mode 100644 index 0000000..4bb3c55 --- /dev/null +++ b/ash/system/toast/toast_overlay.h @@ -0,0 +1,70 @@ +// Copyright 2016 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. + +#ifndef ASH_SYSTEM_TOAST_TOAST_OVERLAY_H_ +#define ASH_SYSTEM_TOAST_TOAST_OVERLAY_H_ + +#include "ash/ash_export.h" +#include "base/memory/scoped_ptr.h" +#include "ui/compositor/layer_animation_observer.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/geometry/size.h" +#include "ui/views/view.h" + +namespace gfx { +class Rect; +} + +namespace views { +class Widget; +} + +namespace ash { + +class ToastManagerTest; +class ToastOverlayView; +class ToastOverlayButton; + +class ASH_EXPORT ToastOverlay : public ui::LayerAnimationObserver { + public: + class ASH_EXPORT Delegate { + public: + virtual ~Delegate() {} + virtual void OnClosed() = 0; + }; + + ToastOverlay(Delegate* delegate, const std::string& text); + ~ToastOverlay() override; + + // Shows or hides the overlay. + void Show(bool visible); + + private: + friend class ToastManagerTest; + + // Returns the current bounds of the overlay, which is based on visibility. + gfx::Rect CalculateOverlayBounds(); + + // gfx::LayerAnimationObserver overrides: + void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override; + void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override; + void OnLayerAnimationScheduled(ui::LayerAnimationSequence* sequence) override; + + views::Widget* widget_for_testing(); + void ClickDismissButtonForTesting(const ui::Event& event); + + bool is_visible_ = false; + Delegate* const delegate_; + const std::string text_; + scoped_ptr<views::Widget> overlay_widget_; + scoped_ptr<ToastOverlayView> overlay_view_; + gfx::Size widget_size_; + + DISALLOW_COPY_AND_ASSIGN(ToastOverlay); +}; + +} // namespace ash + +#endif // ASH_SYSTEM_TOAST_TOAST_OVERLAY_H_ |