1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_COLLECTION_H_
6 #define UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_COLLECTION_H_
7 
8 #include <memory>
9 
10 #include "base/memory/weak_ptr.h"
11 #include "ui/gfx/animation/animation_delegate.h"
12 #include "ui/gfx/geometry/rect.h"
13 #include "ui/message_center/message_center_export.h"
14 #include "ui/message_center/message_center_observer.h"
15 #include "ui/views/widget/widget.h"
16 
17 namespace gfx {
18 class LinearAnimation;
19 }  // namespace gfx
20 
21 namespace display {
22 class Display;
23 }  // namespace display
24 
25 namespace message_center {
26 
27 class MessagePopupView;
28 class Notification;
29 class PopupAlignmentDelegate;
30 
31 // Container of notification popups usually shown at the right bottom of the
32 // screen. Manages animation state and updates these popup widgets.
33 class MESSAGE_CENTER_EXPORT MessagePopupCollection
34     : public MessageCenterObserver,
35       public gfx::AnimationDelegate {
36  public:
37   MessagePopupCollection();
38   ~MessagePopupCollection() override;
39 
40   // Update popups based on current |state_|.
41   void Update();
42 
43   // Reset all popup positions. Called from PopupAlignmentDelegate when
44   // alignment and work area might be changed.
45   void ResetBounds();
46 
47   // Notify the popup size is changed. Called from MessagePopupView.
48   void NotifyPopupResized();
49 
50   // Notify the popup is closed. Called from MessagePopupView.
51   virtual void NotifyPopupClosed(MessagePopupView* popup);
52 
53   // MessageCenterObserver:
54   void OnNotificationAdded(const std::string& notification_id) override;
55   void OnNotificationRemoved(const std::string& notification_id,
56                              bool by_user) override;
57   void OnNotificationUpdated(const std::string& notification_id) override;
58   void OnCenterVisibilityChanged(Visibility visibility) override;
59   void OnBlockingStateChanged(NotificationBlocker* blocker) override;
60 
61   // AnimationDelegate:
62   void AnimationEnded(const gfx::Animation* animation) override;
63   void AnimationProgressed(const gfx::Animation* animation) override;
64   void AnimationCanceled(const gfx::Animation* animation) override;
65 
66   // Find the message popup view for the given notification id. Return nullptr
67   // if it does not exist.
68   MessagePopupView* GetPopupViewForNotificationID(
69       const std::string& notification_id);
70 
71   // Called when a new toast appears or toasts are rearranged in the |display|.
72   // The subclass may override this method to check the current desktop status
73   // so that the toasts are arranged at the correct place. Return true if
74   // alignment is actually changed.
75   virtual bool RecomputeAlignment(const display::Display& display) = 0;
76 
77   // Sets the parent container for popups. If it does not set a parent a
78   // default parent will be used (e.g. the native desktop on Windows).
79   virtual void ConfigureWidgetInitParamsForContainer(
80       views::Widget* widget,
81       views::Widget::InitParams* init_params) = 0;
82 
set_inverse()83   void set_inverse() { inverse_ = true; }
84 
85  protected:
86   // Returns the x-origin for the given toast bounds in the current work area.
87   virtual int GetToastOriginX(const gfx::Rect& toast_bounds) const = 0;
88 
89   // Returns the baseline height of the current work area. That is the starting
90   // point if there are no other toasts.
91   virtual int GetBaseline() const = 0;
92 
93   // Returns the rect of the current work area.
94   virtual gfx::Rect GetWorkArea() const = 0;
95 
96   // Returns true if the toast should be aligned top down.
97   virtual bool IsTopDown() const = 0;
98 
99   // Returns true if the toasts are positioned at the left side of the desktop
100   // so that their reveal animation should happen from left side.
101   virtual bool IsFromLeft() const = 0;
102 
103   // Returns true if the display which notifications show on is the primary
104   // display.
105   virtual bool IsPrimaryDisplayForNotification() const = 0;
106 
107   // Called when a new popup item is added.
NotifyPopupAdded(MessagePopupView * popup)108   virtual void NotifyPopupAdded(MessagePopupView* popup) {}
109 
110   // virtual for testing.
111   virtual MessagePopupView* CreatePopup(const Notification& notification);
112   virtual void RestartPopupTimers();
113   virtual void PausePopupTimers();
114 
animation()115   gfx::LinearAnimation* animation() { return animation_.get(); }
116 
117  private:
118   // MessagePopupCollection always runs single animation at one time.
119   // State is an enum of which animation is running right now.
120   // If |state_| is IDLE, animation_->is_animating() is always false and vice
121   // versa.
122   enum class State {
123     // No animation is running.
124     IDLE,
125 
126     // Fading in an added notification.
127     FADE_IN,
128 
129     // Fading out a removed notification. After the animation, if there are
130     // still remaining notifications, it will transition to MOVE_DOWN.
131     FADE_OUT,
132 
133     // Moving down notifications. Notification collapsing and resizing are also
134     // done in MOVE_DOWN.
135     MOVE_DOWN,
136 
137     // Moving up notifications in order to show new one by FADE_IN. This is only
138     // used when |inverse_| is true.
139     MOVE_UP_FOR_INVERSE
140   };
141 
142   // Stores animation related state of a popup.
143   struct PopupItem {
144     // Notification ID.
145     std::string id;
146 
147     // The bounds that the popup starts animating from.
148     // If |is_animating| is false, it is ignored. Also the value is only used
149     // when the animation type is FADE_IN or MOVE_DOWN.
150     gfx::Rect start_bounds;
151 
152     // The final bounds of the popup.
153     gfx::Rect bounds;
154 
155     // The popup is waiting for MOVE_UP_FOR_INVERSE animation so that it can
156     // FADE_IN after that. The value is only used when the animation type is
157     // MOVE_UP_FOR_INVERSE.
158     bool will_fade_in = false;
159 
160     // If the popup is animating.
161     bool is_animating = false;
162 
163     // Unowned.
164     MessagePopupView* popup = nullptr;
165   };
166 
167   // Transition from animation state (FADE_IN, FADE_OUT, and MOVE_DOWN) to
168   // IDLE state or next animation state (MOVE_DOWN).
169   void TransitionFromAnimation();
170 
171   // Transition from IDLE state to animation state (FADE_IN, FADE_OUT or
172   // MOVE_DOWN).
173   void TransitionToAnimation();
174 
175   // Pause or restart popup timers depending on |state_|.
176   void UpdatePopupTimers();
177 
178   // Calculate |bounds| of all popups and moves old |bounds| to |start_bounds|.
179   void CalculateBounds();
180 
181   // Update bounds and opacity of popups during animation.
182   void UpdateByAnimation();
183 
184   // Add a new popup to |popup_items_| for FADE_IN animation.
185   // Return true if a popup is actually added. It may still return false when
186   // HasAddedPopup() return true by the lack of work area to show popup.
187   bool AddPopup();
188 
189   // Mark |is_animating| flag of removed popup to true for FADE_OUT animation.
190   void MarkRemovedPopup();
191 
192   // Mark |is_animating| flag of all popups for MOVE_DOWN animation.
193   void MoveDownPopups();
194 
195   // Get the y-axis edge of the new popup. In usual bottom-to-top layout, it
196   // means the topmost y-axis when |item| is added.
197   int GetNextEdge(const PopupItem& item) const;
198 
199   // Returns true if the edge is outside work area.
200   bool IsNextEdgeOutsideWorkArea(const PopupItem& item) const;
201 
202   // Implements hot mode. The purpose of hot mode is to allow a user to
203   // continually close many notifications by mouse without moving it. Similar
204   // functionality is also implemented in browser tab strips.
205   void StartHotMode();
206   void ResetHotMode();
207 
208   void CloseAnimatingPopups();
209   bool CloseTransparentPopups();
210   void ClosePopupsOutsideWorkArea();
211   void RemoveClosedPopupItems();
212 
213   // Stops all the animation and closes all the popups immediately.
214   void CloseAllPopupsNow();
215 
216   // Collapse all existing popups. Return true if size of any popup is actually
217   // changed.
218   bool CollapseAllPopups();
219 
220   // Return true if there is a new popup to add.
221   bool HasAddedPopup() const;
222   // Return true is there is a old popup to remove.
223   bool HasRemovedPopup() const;
224 
225   // Return true if any popup is hovered by mouse.
226   bool IsAnyPopupHovered() const;
227   // Return true if any popup is activated.
228   bool IsAnyPopupActive() const;
229 
230   // Returns the popup which is visually |index_from_top|-th from the top.
231   // When |inverse_| is false, it's same as popup_items_[i].
232   PopupItem* GetPopupItem(size_t index_from_top);
233 
234   // Animation state. See the comment of State.
235   State state_ = State::IDLE;
236 
237   // Covers all animation performed by MessagePopupCollection. When the
238   // animation is running, it is always one of FADE_IN (sliding in and opacity
239   // change), FADE_OUT (opacity change), and MOVE_DOWN (sliding down).
240   // MessagePopupCollection does not use implicit animation. The position and
241   // opacity changes are explicitly set from UpdateByAnimation().
242   const std::unique_ptr<gfx::LinearAnimation> animation_;
243 
244   // Notification popups. The first element is the oldest one.
245   std::vector<PopupItem> popup_items_;
246 
247   // True during Update() to avoid reentrancy. For example, popup size change
248   // might be notified during Update() because Update() changes popup sizes, but
249   // popup might change the size by itself e.g. expanding notification by mouse.
250   bool is_updating_ = false;
251 
252   // If true, popup sizes are resized on the next time Update() is called with
253   // IDLE state.
254   bool resize_requested_ = false;
255 
256   // Hot mode related variables. See StartHotMode() and ResetHotMode().
257 
258   // True if the close button of the popup at |hot_index_| is hot.
259   bool is_hot_ = false;
260 
261   // An index in |popup_items_|. Only valid if |is_hot_| is true.
262   size_t hot_index_ = 0;
263 
264   // Fixed Y coordinate of the popup at |hot_index_|. While |is_hot_| is true,
265   // CalculateBounds() always lays out popups in a way the top of the popup at
266   // |hot_index_| is aligned to |hot_top_|. Only valid if |is_hot_| is true.
267   int hot_top_ = 0;
268 
269   // Invert ordering of notification popups i.e. showing the latest notification
270   // at the top. It changes the state transition like this:
271   // Normal:
272   //   * a new notification comes in: FADE_IN
273   //   * a notification comes out: FADE_OUT -> MOVE_DOWN
274   // Inverted:
275   //   * a new notification comes in: MOVE_UP_FOR_INVERSE -> FADE_IN
276   //   * a notification comes out: FADE_OUT
277   bool inverse_ = false;
278 
279   base::WeakPtrFactory<MessagePopupCollection> weak_ptr_factory_{this};
280 
281   DISALLOW_COPY_AND_ASSIGN(MessagePopupCollection);
282 };
283 
284 }  // namespace message_center
285 
286 #endif  // UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_COLLECTION_H_
287