OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 #include "ash/wm/shelf_layout_manager.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cmath> | |
9 | |
10 #include "ash/ash_switches.h" | |
11 #include "ash/launcher/launcher.h" | |
12 #include "ash/root_window_controller.h" | |
13 #include "ash/screen_ash.h" | |
14 #include "ash/shell.h" | |
15 #include "ash/shell_delegate.h" | |
16 #include "ash/shell_window_ids.h" | |
17 #include "ash/system/status_area_widget.h" | |
18 #include "ash/wm/property_util.h" | |
19 #include "ash/wm/window_cycle_controller.h" | |
20 #include "ash/wm/window_util.h" | |
21 #include "ash/wm/workspace_controller.h" | |
22 #include "ash/wm/workspace/workspace_animations.h" | |
23 #include "base/auto_reset.h" | |
24 #include "base/command_line.h" | |
25 #include "base/i18n/rtl.h" | |
26 #include "ui/aura/client/activation_client.h" | |
27 #include "ui/aura/root_window.h" | |
28 #include "ui/base/events/event.h" | |
29 #include "ui/base/events/event_handler.h" | |
30 #include "ui/compositor/layer.h" | |
31 #include "ui/compositor/layer_animation_observer.h" | |
32 #include "ui/compositor/layer_animator.h" | |
33 #include "ui/compositor/scoped_layer_animation_settings.h" | |
34 #include "ui/gfx/screen.h" | |
35 #include "ui/views/widget/widget.h" | |
36 | |
37 namespace ash { | |
38 namespace internal { | |
39 | |
40 namespace { | |
41 | |
42 // Delay before showing the launcher. This is after the mouse stops moving. | |
43 const int kAutoHideDelayMS = 200; | |
44 | |
45 // To avoid hiding the shelf when the mouse transitions from a message bubble | |
46 // into the shelf, the hit test area is enlarged by this amount of pixels to | |
47 // keep the shelf from hiding. | |
48 const int kNotificationBubbleGapHeight = 6; | |
49 | |
50 ui::Layer* GetLayer(views::Widget* widget) { | |
51 return widget->GetNativeView()->layer(); | |
52 } | |
53 | |
54 bool IsDraggingTrayEnabled() { | |
55 static bool dragging_tray_allowed = CommandLine::ForCurrentProcess()-> | |
56 HasSwitch(ash::switches::kAshEnableTrayDragging); | |
57 return dragging_tray_allowed; | |
58 } | |
59 | |
60 } // namespace | |
61 | |
62 // static | |
63 const int ShelfLayoutManager::kWorkspaceAreaBottomInset = 2; | |
64 | |
65 // static | |
66 const int ShelfLayoutManager::kAutoHideSize = 3; | |
67 | |
68 // ShelfLayoutManager::AutoHideEventFilter ------------------------------------- | |
69 | |
70 // Notifies ShelfLayoutManager any time the mouse moves. | |
71 class ShelfLayoutManager::AutoHideEventFilter : public ui::EventHandler { | |
72 public: | |
73 explicit AutoHideEventFilter(ShelfLayoutManager* shelf); | |
74 virtual ~AutoHideEventFilter(); | |
75 | |
76 // Returns true if the last mouse event was a mouse drag. | |
77 bool in_mouse_drag() const { return in_mouse_drag_; } | |
78 | |
79 // Overridden from ui::EventHandler: | |
80 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; | |
81 | |
82 private: | |
83 ShelfLayoutManager* shelf_; | |
84 bool in_mouse_drag_; | |
85 | |
86 DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter); | |
87 }; | |
88 | |
89 ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter( | |
90 ShelfLayoutManager* shelf) | |
91 : shelf_(shelf), | |
92 in_mouse_drag_(false) { | |
93 Shell::GetInstance()->AddPreTargetHandler(this); | |
94 } | |
95 | |
96 ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() { | |
97 Shell::GetInstance()->RemovePreTargetHandler(this); | |
98 } | |
99 | |
100 void ShelfLayoutManager::AutoHideEventFilter::OnMouseEvent( | |
101 ui::MouseEvent* event) { | |
102 // This also checks IsShelfWindow() to make sure we don't attempt to hide the | |
103 // shelf if the mouse down occurs on the shelf. | |
104 in_mouse_drag_ = (event->type() == ui::ET_MOUSE_DRAGGED || | |
105 (in_mouse_drag_ && event->type() != ui::ET_MOUSE_RELEASED && | |
106 event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)) && | |
107 !shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target())); | |
108 if (event->type() == ui::ET_MOUSE_MOVED) | |
109 shelf_->UpdateAutoHideState(); | |
110 return; | |
111 } | |
112 | |
113 // ShelfLayoutManager:UpdateShelfObserver -------------------------------------- | |
114 | |
115 // UpdateShelfObserver is used to delay updating the background until the | |
116 // animation completes. | |
117 class ShelfLayoutManager::UpdateShelfObserver | |
118 : public ui::ImplicitAnimationObserver { | |
119 public: | |
120 explicit UpdateShelfObserver(ShelfLayoutManager* shelf) : shelf_(shelf) { | |
121 shelf_->update_shelf_observer_ = this; | |
122 } | |
123 | |
124 void Detach() { | |
125 shelf_ = NULL; | |
126 } | |
127 | |
128 virtual void OnImplicitAnimationsCompleted() OVERRIDE { | |
129 if (shelf_) { | |
130 shelf_->UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE); | |
131 } | |
132 delete this; | |
133 } | |
134 | |
135 private: | |
136 virtual ~UpdateShelfObserver() { | |
137 if (shelf_) | |
138 shelf_->update_shelf_observer_ = NULL; | |
139 } | |
140 | |
141 // Shelf we're in. NULL if deleted before we're deleted. | |
142 ShelfLayoutManager* shelf_; | |
143 | |
144 DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver); | |
145 }; | |
146 | |
147 // ShelfLayoutManager ---------------------------------------------------------- | |
148 | |
149 ShelfLayoutManager::ShelfLayoutManager(StatusAreaWidget* status_area_widget) | |
150 : root_window_(status_area_widget->GetNativeView()->GetRootWindow()), | |
151 in_layout_(false), | |
152 auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER), | |
153 alignment_(SHELF_ALIGNMENT_BOTTOM), | |
154 launcher_(NULL), | |
155 status_area_widget_(status_area_widget), | |
156 workspace_controller_(NULL), | |
157 window_overlaps_shelf_(false), | |
158 gesture_drag_status_(GESTURE_DRAG_NONE), | |
159 gesture_drag_amount_(0.f), | |
160 gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN), | |
161 update_shelf_observer_(NULL) { | |
162 Shell::GetInstance()->AddShellObserver(this); | |
163 aura::client::GetActivationClient(root_window_)->AddObserver(this); | |
164 } | |
165 | |
166 ShelfLayoutManager::~ShelfLayoutManager() { | |
167 if (update_shelf_observer_) | |
168 update_shelf_observer_->Detach(); | |
169 | |
170 FOR_EACH_OBSERVER(Observer, observers_, WillDeleteShelf()); | |
171 Shell::GetInstance()->RemoveShellObserver(this); | |
172 aura::client::GetActivationClient(root_window_)->RemoveObserver(this); | |
173 } | |
174 | |
175 void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) { | |
176 if (auto_hide_behavior_ == behavior) | |
177 return; | |
178 auto_hide_behavior_ = behavior; | |
179 UpdateVisibilityState(); | |
180 FOR_EACH_OBSERVER(Observer, observers_, | |
181 OnAutoHideStateChanged(state_.auto_hide_state)); | |
182 } | |
183 | |
184 bool ShelfLayoutManager::IsVisible() const { | |
185 return status_area_widget_->IsVisible() && | |
186 (state_.visibility_state == SHELF_VISIBLE || | |
187 (state_.visibility_state == SHELF_AUTO_HIDE && | |
188 state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN)); | |
189 } | |
190 | |
191 void ShelfLayoutManager::SetLauncher(Launcher* launcher) { | |
192 if (launcher == launcher_) | |
193 return; | |
194 | |
195 launcher_ = launcher; | |
196 if (launcher_) | |
197 launcher_->SetAlignment(alignment_); | |
198 LayoutShelf(); | |
199 } | |
200 | |
201 bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) { | |
202 if (alignment_ == alignment) | |
203 return false; | |
204 | |
205 alignment_ = alignment; | |
206 if (launcher_) | |
207 launcher_->SetAlignment(alignment); | |
208 status_area_widget_->SetShelfAlignment(alignment); | |
209 LayoutShelf(); | |
210 return true; | |
211 } | |
212 | |
213 gfx::Rect ShelfLayoutManager::GetIdealBounds() { | |
214 // TODO(oshima): this is wrong. Figure out what display shelf is on | |
215 // and everything should be based on it. | |
216 gfx::Rect bounds(ScreenAsh::GetDisplayBoundsInParent( | |
217 status_area_widget_->GetNativeView())); | |
218 int width = 0, height = 0; | |
219 GetShelfSize(&width, &height); | |
220 return SelectValueForShelfAlignment( | |
221 gfx::Rect(bounds.x(), bounds.bottom() - height, bounds.width(), height), | |
222 gfx::Rect(bounds.x(), bounds.y(), width, bounds.height()), | |
223 gfx::Rect(bounds.right() - width, bounds.y(), width, bounds.height()), | |
224 gfx::Rect(bounds.x(), bounds.y(), bounds.width(), height)); | |
225 } | |
226 | |
227 void ShelfLayoutManager::LayoutShelf() { | |
228 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); | |
229 StopAnimating(); | |
230 TargetBounds target_bounds; | |
231 CalculateTargetBounds(state_, &target_bounds); | |
232 if (launcher_widget()) { | |
233 GetLayer(launcher_widget())->SetOpacity(target_bounds.opacity); | |
234 launcher_->SetWidgetBounds( | |
235 ScreenAsh::ConvertRectToScreen( | |
236 launcher_widget()->GetNativeView()->parent(), | |
237 target_bounds.launcher_bounds_in_root)); | |
238 launcher_->SetStatusSize(target_bounds.status_bounds_in_root.size()); | |
239 } | |
240 GetLayer(status_area_widget_)->SetOpacity(target_bounds.opacity); | |
241 status_area_widget_->SetBounds( | |
242 ScreenAsh::ConvertRectToScreen( | |
243 status_area_widget_->GetNativeView()->parent(), | |
244 target_bounds.status_bounds_in_root)); | |
245 Shell::GetInstance()->SetDisplayWorkAreaInsets( | |
246 root_window_, target_bounds.work_area_insets); | |
247 UpdateHitTestBounds(); | |
248 } | |
249 | |
250 ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() { | |
251 switch(auto_hide_behavior_) { | |
252 case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: | |
253 return SHELF_AUTO_HIDE; | |
254 case SHELF_AUTO_HIDE_BEHAVIOR_NEVER: | |
255 return SHELF_VISIBLE; | |
256 case SHELF_AUTO_HIDE_ALWAYS_HIDDEN: | |
257 return SHELF_HIDDEN; | |
258 } | |
259 return SHELF_VISIBLE; | |
260 } | |
261 | |
262 ShelfVisibilityState | |
263 ShelfLayoutManager::CalculateShelfVisibilityWhileDragging() { | |
264 switch(auto_hide_behavior_) { | |
265 case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: | |
266 case SHELF_AUTO_HIDE_BEHAVIOR_NEVER: | |
267 return SHELF_AUTO_HIDE; | |
268 case SHELF_AUTO_HIDE_ALWAYS_HIDDEN: | |
269 return SHELF_HIDDEN; | |
270 } | |
271 return SHELF_VISIBLE; | |
272 } | |
273 | |
274 void ShelfLayoutManager::UpdateVisibilityState() { | |
275 ShellDelegate* delegate = Shell::GetInstance()->delegate(); | |
276 if (delegate && delegate->IsScreenLocked()) { | |
277 SetState(SHELF_VISIBLE); | |
278 } else if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS) { | |
279 // TODO(zelidrag): Verify shelf drag animation still shows on the device | |
280 // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN. | |
281 SetState(CalculateShelfVisibilityWhileDragging()); | |
282 } else if (GetRootWindowController(root_window_)->IsImmersiveMode()) { | |
283 // The user choosing immersive mode indicates he or she wants to maximize | |
284 // screen real-estate for content, so always auto-hide the shelf. | |
285 DCHECK_NE(auto_hide_behavior_, SHELF_AUTO_HIDE_ALWAYS_HIDDEN); | |
286 SetState(SHELF_AUTO_HIDE); | |
287 } else { | |
288 WorkspaceWindowState window_state(workspace_controller_->GetWindowState()); | |
289 switch (window_state) { | |
290 case WORKSPACE_WINDOW_STATE_FULL_SCREEN: | |
291 SetState(SHELF_HIDDEN); | |
292 break; | |
293 | |
294 case WORKSPACE_WINDOW_STATE_MAXIMIZED: | |
295 SetState(CalculateShelfVisibility()); | |
296 break; | |
297 | |
298 case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF: | |
299 case WORKSPACE_WINDOW_STATE_DEFAULT: | |
300 SetState(CalculateShelfVisibility()); | |
301 SetWindowOverlapsShelf(window_state == | |
302 WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF); | |
303 break; | |
304 } | |
305 } | |
306 } | |
307 | |
308 void ShelfLayoutManager::UpdateAutoHideState() { | |
309 ShelfAutoHideState auto_hide_state = | |
310 CalculateAutoHideState(state_.visibility_state); | |
311 if (auto_hide_state != state_.auto_hide_state) { | |
312 if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) { | |
313 // Hides happen immediately. | |
314 SetState(state_.visibility_state); | |
315 FOR_EACH_OBSERVER(Observer, observers_, | |
316 OnAutoHideStateChanged(auto_hide_state)); | |
317 } else { | |
318 auto_hide_timer_.Stop(); | |
319 auto_hide_timer_.Start( | |
320 FROM_HERE, | |
321 base::TimeDelta::FromMilliseconds(kAutoHideDelayMS), | |
322 this, &ShelfLayoutManager::UpdateAutoHideStateNow); | |
323 FOR_EACH_OBSERVER(Observer, observers_, OnAutoHideStateChanged( | |
324 CalculateAutoHideState(state_.visibility_state))); | |
325 } | |
326 } else { | |
327 auto_hide_timer_.Stop(); | |
328 } | |
329 } | |
330 | |
331 void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) { | |
332 window_overlaps_shelf_ = value; | |
333 UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE); | |
334 } | |
335 | |
336 void ShelfLayoutManager::AddObserver(Observer* observer) { | |
337 observers_.AddObserver(observer); | |
338 } | |
339 | |
340 void ShelfLayoutManager::RemoveObserver(Observer* observer) { | |
341 observers_.RemoveObserver(observer); | |
342 } | |
343 | |
344 //////////////////////////////////////////////////////////////////////////////// | |
345 // ShelfLayoutManager, Gesture dragging: | |
346 | |
347 void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) { | |
348 gesture_drag_status_ = GESTURE_DRAG_IN_PROGRESS; | |
349 gesture_drag_amount_ = 0.f; | |
350 gesture_drag_auto_hide_state_ = visibility_state() == SHELF_AUTO_HIDE ? | |
351 auto_hide_state() : SHELF_AUTO_HIDE_SHOWN; | |
352 UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE); | |
353 } | |
354 | |
355 ShelfLayoutManager::DragState ShelfLayoutManager::UpdateGestureDrag( | |
356 const ui::GestureEvent& gesture) { | |
357 bool horizontal = IsHorizontalAlignment(); | |
358 gesture_drag_amount_ += horizontal ? gesture.details().scroll_y() : | |
359 gesture.details().scroll_x(); | |
360 LayoutShelf(); | |
361 | |
362 // Start reveling the status menu when: | |
363 // - dragging up on an already visible shelf | |
364 // - dragging up on a hidden shelf, but it is currently completely visible. | |
365 if (horizontal && gesture.details().scroll_y() < 0) { | |
366 int min_height = 0; | |
367 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && | |
368 launcher_widget()) | |
369 min_height = launcher_widget()->GetContentsView()-> | |
370 GetPreferredSize().height(); | |
371 | |
372 if (min_height < launcher_widget()->GetWindowBoundsInScreen().height() && | |
373 gesture.root_location().x() >= | |
374 status_area_widget_->GetWindowBoundsInScreen().x() && | |
375 IsDraggingTrayEnabled()) | |
376 return DRAG_TRAY; | |
377 } | |
378 | |
379 return DRAG_SHELF; | |
380 } | |
381 | |
382 void ShelfLayoutManager::CompleteGestureDrag(const ui::GestureEvent& gesture) { | |
383 bool horizontal = IsHorizontalAlignment(); | |
384 bool should_change = false; | |
385 if (gesture.type() == ui::ET_GESTURE_SCROLL_END) { | |
386 // The visibility of the shelf changes only if the shelf was dragged X% | |
387 // along the correct axis. If the shelf was already visible, then the | |
388 // direction of the drag does not matter. | |
389 const float kDragHideThreshold = 0.4f; | |
390 gfx::Rect bounds = GetIdealBounds(); | |
391 float drag_ratio = fabs(gesture_drag_amount_) / | |
392 (horizontal ? bounds.height() : bounds.width()); | |
393 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) { | |
394 should_change = drag_ratio > kDragHideThreshold; | |
395 } else { | |
396 bool correct_direction = false; | |
397 switch (alignment_) { | |
398 case SHELF_ALIGNMENT_BOTTOM: | |
399 case SHELF_ALIGNMENT_RIGHT: | |
400 correct_direction = gesture_drag_amount_ < 0; | |
401 break; | |
402 case SHELF_ALIGNMENT_LEFT: | |
403 case SHELF_ALIGNMENT_TOP: | |
404 correct_direction = gesture_drag_amount_ > 0; | |
405 break; | |
406 } | |
407 should_change = correct_direction && drag_ratio > kDragHideThreshold; | |
408 } | |
409 } else if (gesture.type() == ui::ET_SCROLL_FLING_START) { | |
410 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) { | |
411 should_change = horizontal ? fabs(gesture.details().velocity_y()) > 0 : | |
412 fabs(gesture.details().velocity_x()) > 0; | |
413 } else { | |
414 should_change = SelectValueForShelfAlignment( | |
415 gesture.details().velocity_y() < 0, | |
416 gesture.details().velocity_x() > 0, | |
417 gesture.details().velocity_x() < 0, | |
418 gesture.details().velocity_y() > 0); | |
419 } | |
420 } else { | |
421 NOTREACHED(); | |
422 } | |
423 | |
424 if (!should_change) { | |
425 CancelGestureDrag(); | |
426 return; | |
427 } | |
428 | |
429 gesture_drag_auto_hide_state_ = | |
430 gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ? | |
431 SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN; | |
432 if (launcher_widget()) | |
433 launcher_widget()->Deactivate(); | |
434 status_area_widget_->Deactivate(); | |
435 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && | |
436 auto_hide_behavior_ != SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS) { | |
437 gesture_drag_status_ = GESTURE_DRAG_NONE; | |
438 SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); | |
439 } else if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN && | |
440 auto_hide_behavior_ != SHELF_AUTO_HIDE_BEHAVIOR_NEVER) { | |
441 gesture_drag_status_ = GESTURE_DRAG_NONE; | |
442 SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); | |
443 } else { | |
444 gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS; | |
445 UpdateVisibilityState(); | |
446 gesture_drag_status_ = GESTURE_DRAG_NONE; | |
447 } | |
448 } | |
449 | |
450 void ShelfLayoutManager::CancelGestureDrag() { | |
451 gesture_drag_status_ = GESTURE_DRAG_NONE; | |
452 ui::ScopedLayerAnimationSettings | |
453 launcher_settings(GetLayer(launcher_widget())->GetAnimator()), | |
454 status_settings(GetLayer(status_area_widget_)->GetAnimator()); | |
455 LayoutShelf(); | |
456 UpdateVisibilityState(); | |
457 UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE); | |
458 } | |
459 | |
460 //////////////////////////////////////////////////////////////////////////////// | |
461 // ShelfLayoutManager, aura::LayoutManager implementation: | |
462 | |
463 void ShelfLayoutManager::OnWindowResized() { | |
464 LayoutShelf(); | |
465 } | |
466 | |
467 void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) { | |
468 } | |
469 | |
470 void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) { | |
471 } | |
472 | |
473 void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) { | |
474 } | |
475 | |
476 void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child, | |
477 bool visible) { | |
478 } | |
479 | |
480 void ShelfLayoutManager::SetChildBounds(aura::Window* child, | |
481 const gfx::Rect& requested_bounds) { | |
482 SetChildBoundsDirect(child, requested_bounds); | |
483 // We may contain other widgets (such as frame maximize bubble) but they don't | |
484 // effect the layout in anyway. | |
485 if (!in_layout_ && | |
486 ((launcher_widget() && launcher_widget()->GetNativeView() == child) || | |
487 (status_area_widget_->GetNativeView() == child))) { | |
488 LayoutShelf(); | |
489 } | |
490 } | |
491 | |
492 void ShelfLayoutManager::OnLockStateChanged(bool locked) { | |
493 UpdateVisibilityState(); | |
494 } | |
495 | |
496 void ShelfLayoutManager::OnWindowActivated(aura::Window* gained_active, | |
497 aura::Window* lost_active) { | |
498 UpdateAutoHideStateNow(); | |
499 } | |
500 | |
501 bool ShelfLayoutManager::IsHorizontalAlignment() const { | |
502 return alignment_ == SHELF_ALIGNMENT_BOTTOM || | |
503 alignment_ == SHELF_ALIGNMENT_TOP; | |
504 } | |
505 | |
506 // static | |
507 ShelfLayoutManager* ShelfLayoutManager::ForLauncher(aura::Window* window) { | |
508 return RootWindowController::ForLauncher(window)->shelf(); | |
509 } | |
510 | |
511 //////////////////////////////////////////////////////////////////////////////// | |
512 // ShelfLayoutManager, private: | |
513 | |
514 ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {} | |
515 | |
516 void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state) { | |
517 ShellDelegate* delegate = Shell::GetInstance()->delegate(); | |
518 State state; | |
519 state.visibility_state = visibility_state; | |
520 state.auto_hide_state = CalculateAutoHideState(visibility_state); | |
521 state.is_screen_locked = delegate && delegate->IsScreenLocked(); | |
522 | |
523 // It's possible for SetState() when a window becomes maximized but the state | |
524 // won't have changed value. Do the dimming check before the early exit. | |
525 if (launcher_ && workspace_controller_) { | |
526 launcher_->SetDimsShelf( | |
527 (state.visibility_state == SHELF_VISIBLE) && | |
528 workspace_controller_->GetWindowState() == | |
529 WORKSPACE_WINDOW_STATE_MAXIMIZED); | |
530 } | |
531 | |
532 if (state_.Equals(state)) | |
533 return; // Nothing changed. | |
534 | |
535 FOR_EACH_OBSERVER(Observer, observers_, | |
536 WillChangeVisibilityState(visibility_state)); | |
537 | |
538 if (state.visibility_state == SHELF_AUTO_HIDE) { | |
539 // When state is SHELF_AUTO_HIDE we need to track when the mouse is over the | |
540 // launcher to unhide the shelf. AutoHideEventFilter does that for us. | |
541 if (!event_filter_.get()) | |
542 event_filter_.reset(new AutoHideEventFilter(this)); | |
543 } else { | |
544 event_filter_.reset(NULL); | |
545 } | |
546 | |
547 auto_hide_timer_.Stop(); | |
548 | |
549 // Animating the background when transitioning from auto-hide & hidden to | |
550 // visible is janky. Update the background immediately in this case. | |
551 BackgroundAnimator::ChangeType change_type = | |
552 (state_.visibility_state == SHELF_AUTO_HIDE && | |
553 state_.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN && | |
554 state.visibility_state == SHELF_VISIBLE) ? | |
555 BackgroundAnimator::CHANGE_IMMEDIATE : BackgroundAnimator::CHANGE_ANIMATE; | |
556 StopAnimating(); | |
557 | |
558 State old_state = state_; | |
559 state_ = state; | |
560 TargetBounds target_bounds; | |
561 CalculateTargetBounds(state_, &target_bounds); | |
562 if (launcher_widget()) { | |
563 ui::ScopedLayerAnimationSettings launcher_animation_setter( | |
564 GetLayer(launcher_widget())->GetAnimator()); | |
565 launcher_animation_setter.SetTransitionDuration( | |
566 base::TimeDelta::FromMilliseconds(kWorkspaceSwitchTimeMS)); | |
567 launcher_animation_setter.SetTweenType(ui::Tween::EASE_OUT); | |
568 GetLayer(launcher_widget())->SetBounds( | |
569 target_bounds.launcher_bounds_in_root); | |
570 GetLayer(launcher_widget())->SetOpacity(target_bounds.opacity); | |
571 } | |
572 ui::ScopedLayerAnimationSettings status_animation_setter( | |
573 GetLayer(status_area_widget_)->GetAnimator()); | |
574 status_animation_setter.SetTransitionDuration( | |
575 base::TimeDelta::FromMilliseconds(kWorkspaceSwitchTimeMS)); | |
576 status_animation_setter.SetTweenType(ui::Tween::EASE_OUT); | |
577 | |
578 // Delay updating the background when going from SHELF_AUTO_HIDE_SHOWN to | |
579 // SHELF_AUTO_HIDE_HIDDEN until the shelf animates out. Otherwise during the | |
580 // animation you see the background change. | |
581 // Also delay the animation when the shelf was hidden, and has just been made | |
582 // visible (e.g. using a gesture-drag). | |
583 bool delay_shelf_update = | |
584 state.visibility_state == SHELF_AUTO_HIDE && | |
585 state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN && | |
586 old_state.visibility_state == SHELF_AUTO_HIDE; | |
587 | |
588 if (state.visibility_state == SHELF_VISIBLE && | |
589 old_state.visibility_state == SHELF_AUTO_HIDE && | |
590 old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) | |
591 delay_shelf_update = true; | |
592 | |
593 if (delay_shelf_update) { | |
594 if (update_shelf_observer_) | |
595 update_shelf_observer_->Detach(); | |
596 // UpdateShelfBackground deletes itself when the animation is done. | |
597 update_shelf_observer_ = new UpdateShelfObserver(this); | |
598 status_animation_setter.AddObserver(update_shelf_observer_); | |
599 } | |
600 ui::Layer* layer = GetLayer(status_area_widget_); | |
601 layer->SetBounds(target_bounds.status_bounds_in_root); | |
602 layer->SetOpacity(target_bounds.opacity); | |
603 Shell::GetInstance()->SetDisplayWorkAreaInsets( | |
604 root_window_, target_bounds.work_area_insets); | |
605 UpdateHitTestBounds(); | |
606 if (!delay_shelf_update) | |
607 UpdateShelfBackground(change_type); | |
608 } | |
609 | |
610 void ShelfLayoutManager::StopAnimating() { | |
611 ui::Layer* layer = GetLayer(status_area_widget_); | |
612 if (launcher_widget()) | |
613 layer->GetAnimator()->StopAnimating(); | |
614 layer->GetAnimator()->StopAnimating(); | |
615 } | |
616 | |
617 void ShelfLayoutManager::GetShelfSize(int* width, int* height) { | |
618 *width = *height = 0; | |
619 gfx::Size status_size(status_area_widget_->GetWindowBoundsInScreen().size()); | |
620 gfx::Size launcher_size = launcher_ ? | |
621 launcher_widget()->GetContentsView()->GetPreferredSize() : gfx::Size(); | |
622 if (IsHorizontalAlignment()) | |
623 *height = std::max(launcher_size.height(), status_size.height()); | |
624 else | |
625 *width = std::max(launcher_size.width(), status_size.width()); | |
626 } | |
627 | |
628 void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset, | |
629 gfx::Rect* bounds) const { | |
630 bounds->Inset(SelectValueForShelfAlignment( | |
631 gfx::Insets(0, 0, inset, 0), | |
632 gfx::Insets(0, inset, 0, 0), | |
633 gfx::Insets(0, 0, 0, inset), | |
634 gfx::Insets(inset, 0, 0, 0))); | |
635 } | |
636 | |
637 void ShelfLayoutManager::CalculateTargetBounds( | |
638 const State& state, | |
639 TargetBounds* target_bounds) { | |
640 const gfx::Rect& available_bounds(root_window_->bounds()); | |
641 gfx::Rect status_size(status_area_widget_->GetWindowBoundsInScreen().size()); | |
642 gfx::Size launcher_size = launcher_ ? | |
643 launcher_widget()->GetContentsView()->GetPreferredSize() : gfx::Size(); | |
644 int shelf_size = 0; | |
645 int shelf_width = 0, shelf_height = 0; | |
646 GetShelfSize(&shelf_width, &shelf_height); | |
647 if (state.visibility_state == SHELF_VISIBLE || | |
648 (state.visibility_state == SHELF_AUTO_HIDE && | |
649 state.auto_hide_state == SHELF_AUTO_HIDE_SHOWN)) { | |
650 shelf_size = std::max(shelf_width, shelf_height); | |
651 launcher_size.set_width(std::max(shelf_width,launcher_size.width())); | |
652 launcher_size.set_height(std::max(shelf_height,launcher_size.height())); | |
653 } else if (state.visibility_state == SHELF_AUTO_HIDE && | |
654 state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) { | |
655 shelf_size = kAutoHideSize; | |
656 // Keep the launcher to its full height when dragging is in progress. | |
657 if (gesture_drag_status_ == GESTURE_DRAG_NONE) { | |
658 if (IsHorizontalAlignment()) | |
659 launcher_size.set_height(kAutoHideSize); | |
660 else | |
661 launcher_size.set_width(kAutoHideSize); | |
662 } | |
663 } | |
664 switch(alignment_) { | |
665 case SHELF_ALIGNMENT_BOTTOM: | |
666 // The status widget should extend to the bottom and right edges. | |
667 target_bounds->status_bounds_in_root = gfx::Rect( | |
668 base::i18n::IsRTL() ? available_bounds.x() : | |
669 available_bounds.right() - status_size.width(), | |
670 available_bounds.bottom() - shelf_size + shelf_height - | |
671 status_size.height(), | |
672 status_size.width(), status_size.height()); | |
673 if (launcher_widget()) | |
674 target_bounds->launcher_bounds_in_root = gfx::Rect( | |
675 available_bounds.x(), | |
676 available_bounds.bottom() - shelf_size, | |
677 available_bounds.width(), | |
678 launcher_size.height()); | |
679 target_bounds->work_area_insets.Set( | |
680 0, 0, GetWorkAreaSize(state, shelf_height), 0); | |
681 break; | |
682 case SHELF_ALIGNMENT_LEFT: | |
683 target_bounds->status_bounds_in_root = gfx::Rect( | |
684 available_bounds.x() + launcher_size.width() - status_size.width(), | |
685 available_bounds.bottom() - status_size.height(), | |
686 shelf_width, status_size.height()); | |
687 if (launcher_widget()) | |
688 target_bounds->launcher_bounds_in_root = gfx::Rect( | |
689 available_bounds.x(), available_bounds.y(), | |
690 launcher_size.width(), available_bounds.height()); | |
691 target_bounds->work_area_insets.Set( | |
692 0, GetWorkAreaSize(state, launcher_size.width()), 0, 0); | |
693 break; | |
694 case SHELF_ALIGNMENT_RIGHT: | |
695 target_bounds->status_bounds_in_root = gfx::Rect( | |
696 available_bounds.right()- status_size.width() - | |
697 shelf_size + shelf_width, | |
698 available_bounds.bottom() - status_size.height(), | |
699 shelf_width, status_size.height()); | |
700 if (launcher_widget()) | |
701 target_bounds->launcher_bounds_in_root = gfx::Rect( | |
702 available_bounds.right() - launcher_size.width(), | |
703 available_bounds.y(), | |
704 launcher_size.width(), available_bounds.height()); | |
705 target_bounds->work_area_insets.Set( | |
706 0, 0, 0, GetWorkAreaSize(state, launcher_size.width())); | |
707 break; | |
708 case SHELF_ALIGNMENT_TOP: | |
709 target_bounds->status_bounds_in_root = gfx::Rect( | |
710 base::i18n::IsRTL() ? available_bounds.x() : | |
711 available_bounds.right() - status_size.width(), | |
712 available_bounds.y() + launcher_size.height() - status_size.height(), | |
713 status_size.width(), status_size.height()); | |
714 if (launcher_widget()) | |
715 target_bounds->launcher_bounds_in_root = gfx::Rect( | |
716 available_bounds.x(), | |
717 available_bounds.y(), | |
718 available_bounds.width(), | |
719 launcher_size.height()); | |
720 target_bounds->work_area_insets.Set( | |
721 GetWorkAreaSize(state, shelf_height), 0, 0, 0); | |
722 break; | |
723 } | |
724 target_bounds->opacity = | |
725 (gesture_drag_status_ != GESTURE_DRAG_NONE || | |
726 state.visibility_state == SHELF_VISIBLE || | |
727 state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f; | |
728 if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS) | |
729 UpdateTargetBoundsForGesture(target_bounds); | |
730 } | |
731 | |
732 void ShelfLayoutManager::UpdateTargetBoundsForGesture( | |
733 TargetBounds* target_bounds) const { | |
734 CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_); | |
735 bool horizontal = IsHorizontalAlignment(); | |
736 int resistance_free_region = 0; | |
737 | |
738 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && | |
739 visibility_state() == SHELF_AUTO_HIDE && | |
740 auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) { | |
741 // If the shelf was hidden when the drag started (and the state hasn't | |
742 // changed since then, e.g. because the tray-menu was shown because of the | |
743 // drag), then allow the drag some resistance-free region at first to make | |
744 // sure the shelf sticks with the finger until the shelf is visible. | |
745 resistance_free_region += horizontal ? | |
746 target_bounds->launcher_bounds_in_root.height() : | |
747 target_bounds->launcher_bounds_in_root.width(); | |
748 resistance_free_region -= kAutoHideSize; | |
749 } | |
750 | |
751 bool resist = SelectValueForShelfAlignment( | |
752 gesture_drag_amount_ < -resistance_free_region, | |
753 gesture_drag_amount_ > resistance_free_region, | |
754 gesture_drag_amount_ < -resistance_free_region, | |
755 gesture_drag_amount_ > resistance_free_region); | |
756 | |
757 float translate = 0.f; | |
758 if (resist) { | |
759 float diff = fabsf(gesture_drag_amount_) - resistance_free_region; | |
760 diff = std::min(diff, sqrtf(diff)); | |
761 if (gesture_drag_amount_ < 0) | |
762 translate = -resistance_free_region - diff; | |
763 else | |
764 translate = resistance_free_region + diff; | |
765 } else { | |
766 translate = gesture_drag_amount_; | |
767 } | |
768 | |
769 if (horizontal) { | |
770 // Move the launcher with the gesture. | |
771 target_bounds->launcher_bounds_in_root.Offset(0, translate); | |
772 target_bounds->status_bounds_in_root.Offset(0, translate); | |
773 | |
774 if (translate < 0) { | |
775 // When dragging up, the launcher height should increase. | |
776 float move = std::max(translate, | |
777 -static_cast<float>(resistance_free_region)); | |
778 target_bounds->launcher_bounds_in_root.set_height( | |
779 target_bounds->launcher_bounds_in_root.height() + move - translate); | |
780 } | |
781 } else { | |
782 // Move the launcher with the gesture. | |
783 if (alignment_ == SHELF_ALIGNMENT_RIGHT) | |
784 target_bounds->launcher_bounds_in_root.Offset(translate, 0); | |
785 | |
786 if ((translate > 0 && alignment_ == SHELF_ALIGNMENT_RIGHT) || | |
787 (translate < 0 && alignment_ == SHELF_ALIGNMENT_LEFT)) { | |
788 // When dragging towards the edge, the statusbar should move. | |
789 target_bounds->status_bounds_in_root.Offset(translate, 0); | |
790 } else { | |
791 // When dragging away from the edge, the launcher width should increase. | |
792 float move = alignment_ == SHELF_ALIGNMENT_RIGHT ? | |
793 std::max(translate, -static_cast<float>(resistance_free_region)) : | |
794 std::min(translate, static_cast<float>(resistance_free_region)); | |
795 | |
796 if (alignment_ == SHELF_ALIGNMENT_RIGHT) { | |
797 target_bounds->launcher_bounds_in_root.set_width( | |
798 target_bounds->launcher_bounds_in_root.width() + move - translate); | |
799 } else { | |
800 target_bounds->launcher_bounds_in_root.set_width( | |
801 target_bounds->launcher_bounds_in_root.width() - move + translate); | |
802 } | |
803 | |
804 // The statusbar should be in the center. | |
805 gfx::Rect status_x = target_bounds->launcher_bounds_in_root; | |
806 status_x.ClampToCenteredSize( | |
807 target_bounds->status_bounds_in_root.size()); | |
808 target_bounds->status_bounds_in_root.set_x(status_x.x()); | |
809 } | |
810 } | |
811 } | |
812 | |
813 void ShelfLayoutManager::UpdateShelfBackground( | |
814 BackgroundAnimator::ChangeType type) { | |
815 bool launcher_paints = GetLauncherPaintsBackground(); | |
816 if (launcher_) | |
817 launcher_->SetPaintsBackground(launcher_paints, type); | |
818 // The status area normally draws a background, but we don't want it to draw a | |
819 // background when the launcher does or when we're at login/lock screen. | |
820 ShellDelegate* delegate = Shell::GetInstance()->delegate(); | |
821 bool delegate_allows_tray_bg = | |
822 delegate->IsUserLoggedIn() && !delegate->IsScreenLocked(); | |
823 bool status_area_paints = !launcher_paints && delegate_allows_tray_bg; | |
824 status_area_widget_->SetPaintsBackground(status_area_paints, type); | |
825 } | |
826 | |
827 bool ShelfLayoutManager::GetLauncherPaintsBackground() const { | |
828 return gesture_drag_status_ != GESTURE_DRAG_NONE || | |
829 (!state_.is_screen_locked && window_overlaps_shelf_) || | |
830 (state_.visibility_state == SHELF_AUTO_HIDE) ; | |
831 } | |
832 | |
833 void ShelfLayoutManager::UpdateAutoHideStateNow() { | |
834 SetState(state_.visibility_state); | |
835 } | |
836 | |
837 ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState( | |
838 ShelfVisibilityState visibility_state) const { | |
839 if (visibility_state != SHELF_AUTO_HIDE || !launcher_widget()) | |
840 return SHELF_AUTO_HIDE_HIDDEN; | |
841 | |
842 if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS) | |
843 return gesture_drag_auto_hide_state_; | |
844 | |
845 Shell* shell = Shell::GetInstance(); | |
846 if (shell->GetAppListTargetVisibility()) | |
847 return SHELF_AUTO_HIDE_SHOWN; | |
848 | |
849 if (status_area_widget_ && status_area_widget_->ShouldShowLauncher()) | |
850 return SHELF_AUTO_HIDE_SHOWN; | |
851 | |
852 if (launcher_ && launcher_->IsShowingMenu()) | |
853 return SHELF_AUTO_HIDE_SHOWN; | |
854 | |
855 if (launcher_ && launcher_->IsShowingOverflowBubble()) | |
856 return SHELF_AUTO_HIDE_SHOWN; | |
857 | |
858 if (launcher_widget()->IsActive() || status_area_widget_->IsActive()) | |
859 return SHELF_AUTO_HIDE_SHOWN; | |
860 | |
861 // Don't show if the user is dragging the mouse. | |
862 if (event_filter_.get() && event_filter_->in_mouse_drag()) | |
863 return SHELF_AUTO_HIDE_HIDDEN; | |
864 | |
865 gfx::Rect shelf_region = launcher_widget()->GetWindowBoundsInScreen(); | |
866 if (status_area_widget_ && | |
867 status_area_widget_->IsMessageBubbleShown() && | |
868 IsVisible()) { | |
869 // Increase the the hit test area to prevent the shelf from disappearing | |
870 // when the mouse is over the bubble gap. | |
871 shelf_region.Inset(alignment_ == SHELF_ALIGNMENT_RIGHT ? | |
872 -kNotificationBubbleGapHeight : 0, | |
873 alignment_ == SHELF_ALIGNMENT_BOTTOM ? | |
874 -kNotificationBubbleGapHeight : 0, | |
875 alignment_ == SHELF_ALIGNMENT_LEFT ? | |
876 -kNotificationBubbleGapHeight : 0, | |
877 alignment_ == SHELF_ALIGNMENT_TOP ? | |
878 -kNotificationBubbleGapHeight : 0); | |
879 } | |
880 | |
881 if (shelf_region.Contains(Shell::GetScreen()->GetCursorScreenPoint())) | |
882 return SHELF_AUTO_HIDE_SHOWN; | |
883 | |
884 const std::vector<aura::Window*> windows = | |
885 ash::WindowCycleController::BuildWindowList(NULL); | |
886 | |
887 // Process the window list and check if there are any visible windows. | |
888 for (size_t i = 0; i < windows.size(); ++i) { | |
889 if (windows[i] && windows[i]->IsVisible() && | |
890 !ash::wm::IsWindowMinimized(windows[i])) | |
891 return SHELF_AUTO_HIDE_HIDDEN; | |
892 } | |
893 | |
894 // If there are no visible windows do not hide the shelf. | |
895 return SHELF_AUTO_HIDE_SHOWN; | |
896 } | |
897 | |
898 void ShelfLayoutManager::UpdateHitTestBounds() { | |
899 gfx::Insets insets; | |
900 // Only modify the hit test when the shelf is visible, so we don't mess with | |
901 // hover hit testing in the auto-hide state. | |
902 if (state_.visibility_state == SHELF_VISIBLE) { | |
903 // Let clicks at the very top of the launcher through so windows can be | |
904 // resized with the bottom-right corner and bottom edge. | |
905 switch (alignment_) { | |
906 case SHELF_ALIGNMENT_BOTTOM: | |
907 insets.Set(kWorkspaceAreaBottomInset, 0, 0, 0); | |
908 break; | |
909 case SHELF_ALIGNMENT_LEFT: | |
910 insets.Set(0, 0, 0, kWorkspaceAreaBottomInset); | |
911 break; | |
912 case SHELF_ALIGNMENT_RIGHT: | |
913 insets.Set(0, kWorkspaceAreaBottomInset, 0, 0); | |
914 break; | |
915 case SHELF_ALIGNMENT_TOP: | |
916 insets.Set(0, 0, kWorkspaceAreaBottomInset, 0); | |
917 break; | |
918 } | |
919 } | |
920 if (launcher_widget() && launcher_widget()->GetNativeWindow()) { | |
921 launcher_widget()->GetNativeWindow()->SetHitTestBoundsOverrideOuter( | |
922 insets, 1); | |
923 } | |
924 status_area_widget_->GetNativeWindow()-> | |
925 SetHitTestBoundsOverrideOuter(insets, 1); | |
926 } | |
927 | |
928 bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) { | |
929 if (!window) | |
930 return false; | |
931 return (launcher_widget() && | |
932 launcher_widget()->GetNativeWindow()->Contains(window)) || | |
933 (status_area_widget_->GetNativeWindow()->Contains(window)); | |
934 } | |
935 | |
936 int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const { | |
937 if (state.visibility_state == SHELF_VISIBLE) | |
938 return size; | |
939 if (state.visibility_state == SHELF_AUTO_HIDE) | |
940 return kAutoHideSize; | |
941 return 0; | |
942 } | |
943 | |
944 } // namespace internal | |
945 } // namespace ash | |
OLD | NEW |