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