summaryrefslogtreecommitdiffstats
path: root/ash
diff options
context:
space:
mode:
authoryoshiki <yoshiki@chromium.org>2016-03-23 18:24:43 -0700
committerCommit bot <commit-bot@chromium.org>2016-03-24 01:26:06 +0000
commit32f516395c1d165053be9b93ef9d2cbdfd94685a (patch)
tree7e11f0316ea16dc9ddc52a4e322a2a773aad7ad8 /ash
parent5cb6ce43b7cb7d64d170d91a165910563537d10c (diff)
downloadchromium_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.gyp5
-rw-r--r--ash/ash_strings.grd4
-rw-r--r--ash/shelf/shelf_layout_manager.h1
-rw-r--r--ash/shell.cc7
-rw-r--r--ash/shell.h4
-rw-r--r--ash/system/toast/OWNER1
-rw-r--r--ash/system/toast/toast_manager.cc63
-rw-r--r--ash/system/toast/toast_manager.h53
-rw-r--r--ash/system/toast/toast_manager_unittest.cc264
-rw-r--r--ash/system/toast/toast_overlay.cc275
-rw-r--r--ash/system/toast/toast_overlay.h70
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_