Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / panels / docked_panel_collection.cc
blob44b554e0a3521f0602ddc20ebb7b10869de332c7
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.
5 #include "chrome/browser/ui/panels/docked_panel_collection.h"
7 #include <math.h>
9 #include <algorithm>
10 #include <vector>
12 #include "base/auto_reset.h"
13 #include "base/bind.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_loop.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/ui/panels/panel_drag_controller.h"
18 #include "chrome/browser/ui/panels/panel_manager.h"
19 #include "chrome/browser/ui/panels/panel_mouse_watcher.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/notification_source.h"
23 namespace {
24 // Width of spacing around panel collection and the left/right edges of the
25 // screen.
26 const int kPanelCollectionLeftMargin = 6;
27 const int kPanelCollectionRightMargin = 24;
29 // Occasionally some system, like Windows, might not bring up or down the bottom
30 // bar when the mouse enters or leaves the bottom screen area. This is the
31 // maximum time we will wait for the bottom bar visibility change notification.
32 // After the time expires, we bring up/down the titlebars as planned.
33 const int kMaxDelayWaitForBottomBarVisibilityChangeMs = 1000;
35 // After focus changed, one panel lost active status, another got it,
36 // we refresh layout with a delay.
37 const int kRefreshLayoutAfterActivePanelChangeDelayMs = 600; // arbitrary
39 // As we refresh panel positions, some or all panels may move. We make sure
40 // we do not animate too many panels at once as this tends to perform poorly.
41 const int kNumPanelsToAnimateSimultaneously = 3;
43 } // namespace
45 DockedPanelCollection::DockedPanelCollection(PanelManager* panel_manager)
46 : PanelCollection(PanelCollection::DOCKED),
47 panel_manager_(panel_manager),
48 minimized_panel_count_(0),
49 are_titlebars_up_(false),
50 minimizing_all_(false),
51 delayed_titlebar_action_(NO_ACTION),
52 titlebar_action_factory_(this),
53 refresh_action_factory_(this) {
54 panel_manager_->display_settings_provider()->AddDesktopBarObserver(this);
55 OnDisplayChanged();
58 DockedPanelCollection::~DockedPanelCollection() {
59 DCHECK(panels_.empty());
60 DCHECK_EQ(0, minimized_panel_count_);
61 panel_manager_->display_settings_provider()->RemoveDesktopBarObserver(this);
64 void DockedPanelCollection::OnDisplayChanged() {
65 work_area_ =
66 panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
67 work_area_.set_x(work_area_.x() + kPanelCollectionLeftMargin);
68 work_area_.set_width(work_area_.width() -
69 kPanelCollectionLeftMargin - kPanelCollectionRightMargin);
71 if (panels_.empty())
72 return;
74 for (Panels::const_iterator iter = panels_.begin();
75 iter != panels_.end(); ++iter) {
76 (*iter)->LimitSizeToWorkArea(work_area_);
79 RefreshLayout();
82 void DockedPanelCollection::AddPanel(Panel* panel,
83 PositioningMask positioning_mask) {
84 // This method does not handle minimized panels.
85 DCHECK_EQ(Panel::EXPANDED, panel->expansion_state());
87 DCHECK(panel->initialized());
88 DCHECK_NE(this, panel->collection());
89 panel->set_collection(this);
91 bool default_position = (positioning_mask & KNOWN_POSITION) == 0;
92 bool update_bounds = (positioning_mask & DO_NOT_UPDATE_BOUNDS) == 0;
94 if (default_position) {
95 gfx::Size full_size = panel->full_size();
96 gfx::Point pt = GetDefaultPositionForPanel(full_size);
97 panel->SetPanelBounds(gfx::Rect(pt, full_size));
98 panels_.push_back(panel);
99 } else {
100 DCHECK(update_bounds);
101 int x = panel->GetBounds().x();
102 Panels::iterator iter = panels_.begin();
103 for (; iter != panels_.end(); ++iter)
104 if (x > (*iter)->GetBounds().x())
105 break;
106 panels_.insert(iter, panel);
109 if (update_bounds) {
110 if ((positioning_mask & DELAY_LAYOUT_REFRESH) != 0)
111 ScheduleLayoutRefresh();
112 else
113 RefreshLayout();
117 gfx::Point DockedPanelCollection::GetDefaultPositionForPanel(
118 const gfx::Size& full_size) const {
119 int x = 0;
120 if (!panels_.empty() &&
121 panels_.back()->GetBounds().x() < work_area_.x()) {
122 // Panels go off screen. Make sure the default position will place
123 // the panel in view.
124 Panels::const_reverse_iterator iter = panels_.rbegin();
125 for (; iter != panels_.rend(); ++iter) {
126 if ((*iter)->GetBounds().x() >= work_area_.x()) {
127 x = (*iter)->GetBounds().x();
128 break;
131 // At least one panel should fit on the screen.
132 DCHECK(x > work_area_.x());
133 } else {
134 x = std::max(GetRightMostAvailablePosition() - full_size.width(),
135 work_area_.x());
137 return gfx::Point(x, work_area_.bottom() - full_size.height());
140 int DockedPanelCollection::StartingRightPosition() const {
141 return work_area_.right();
144 int DockedPanelCollection::GetRightMostAvailablePosition() const {
145 return panels_.empty() ? StartingRightPosition() :
146 (panels_.back()->GetBounds().x() - kPanelsHorizontalSpacing);
149 void DockedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) {
150 DCHECK_EQ(this, panel->collection());
151 panel->set_collection(NULL);
153 // Optimize for the common case of removing the last panel.
154 DCHECK(!panels_.empty());
155 if (panels_.back() == panel) {
156 panels_.pop_back();
158 // Update the saved panel placement if needed. This is because
159 // we might remove |saved_panel_placement_.left_panel|.
160 if (saved_panel_placement_.panel &&
161 saved_panel_placement_.left_panel == panel)
162 saved_panel_placement_.left_panel = NULL;
164 } else {
165 Panels::iterator iter = find(panels_.begin(), panels_.end(), panel);
166 DCHECK(iter != panels_.end());
167 iter = panels_.erase(iter);
169 // Update the saved panel placement if needed. This is because
170 // we might remove |saved_panel_placement_.left_panel|.
171 if (saved_panel_placement_.panel &&
172 saved_panel_placement_.left_panel == panel)
173 saved_panel_placement_.left_panel = *iter;
176 if (panel->expansion_state() != Panel::EXPANDED)
177 UpdateMinimizedPanelCount();
179 RefreshLayout();
182 void DockedPanelCollection::SavePanelPlacement(Panel* panel) {
183 DCHECK(!saved_panel_placement_.panel);
185 saved_panel_placement_.panel = panel;
187 // To recover panel to its original placement, we only need to track the panel
188 // that is placed after it.
189 Panels::iterator iter = find(panels_.begin(), panels_.end(), panel);
190 DCHECK(iter != panels_.end());
191 ++iter;
192 saved_panel_placement_.left_panel = (iter == panels_.end()) ? NULL : *iter;
195 void DockedPanelCollection::RestorePanelToSavedPlacement() {
196 DCHECK(saved_panel_placement_.panel);
198 Panel* panel = saved_panel_placement_.panel;
200 // Find next panel after this panel.
201 Panels::iterator iter = std::find(panels_.begin(), panels_.end(), panel);
202 DCHECK(iter != panels_.end());
203 Panels::iterator next_iter = iter;
204 next_iter++;
205 Panel* next_panel = (next_iter == panels_.end()) ? NULL : *iter;
207 // Restoring is only needed when this panel is not in the right position.
208 if (next_panel != saved_panel_placement_.left_panel) {
209 // Remove this panel from its current position.
210 panels_.erase(iter);
212 // Insert this panel into its previous position.
213 if (saved_panel_placement_.left_panel) {
214 Panels::iterator iter_to_insert_before = std::find(panels_.begin(),
215 panels_.end(), saved_panel_placement_.left_panel);
216 DCHECK(iter_to_insert_before != panels_.end());
217 panels_.insert(iter_to_insert_before, panel);
218 } else {
219 panels_.push_back(panel);
223 RefreshLayout();
225 DiscardSavedPanelPlacement();
228 void DockedPanelCollection::DiscardSavedPanelPlacement() {
229 DCHECK(saved_panel_placement_.panel);
230 saved_panel_placement_.panel = NULL;
231 saved_panel_placement_.left_panel = NULL;
234 panel::Resizability DockedPanelCollection::GetPanelResizability(
235 const Panel* panel) const {
236 return (panel->expansion_state() == Panel::EXPANDED) ?
237 panel::RESIZABLE_EXCEPT_BOTTOM : panel::NOT_RESIZABLE;
240 void DockedPanelCollection::OnPanelResizedByMouse(Panel* panel,
241 const gfx::Rect& new_bounds) {
242 DCHECK_EQ(this, panel->collection());
243 panel->set_full_size(new_bounds.size());
246 void DockedPanelCollection::OnPanelExpansionStateChanged(Panel* panel) {
247 gfx::Rect panel_bounds = panel->GetBounds();
248 AdjustPanelBoundsPerExpansionState(panel, &panel_bounds);
249 panel->SetPanelBounds(panel_bounds);
251 UpdateMinimizedPanelCount();
253 // Ensure minimized panel does not get the focus. If minimizing all,
254 // the active panel will be deactivated once when all panels are minimized
255 // rather than per minimized panel.
256 if (panel->expansion_state() != Panel::EXPANDED && !minimizing_all_ &&
257 panel->IsActive()) {
258 panel->Deactivate();
259 // The layout will refresh itself in response
260 // to (de)activation notification.
264 void DockedPanelCollection::AdjustPanelBoundsPerExpansionState(Panel* panel,
265 gfx::Rect* bounds) {
266 Panel::ExpansionState expansion_state = panel->expansion_state();
267 switch (expansion_state) {
268 case Panel::EXPANDED:
269 bounds->set_height(panel->full_size().height());
271 break;
272 case Panel::TITLE_ONLY:
273 bounds->set_height(panel->TitleOnlyHeight());
275 break;
276 case Panel::MINIMIZED:
277 bounds->set_height(panel::kMinimizedPanelHeight);
279 break;
280 default:
281 NOTREACHED();
282 break;
285 int bottom = GetBottomPositionForExpansionState(expansion_state);
286 bounds->set_y(bottom - bounds->height());
289 void DockedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) {
290 DCHECK_EQ(this, panel->collection());
291 Panel::ExpansionState state = panel->expansion_state();
292 if (panel->IsDrawingAttention()) {
293 // Bring up the titlebar to get user's attention.
294 if (state == Panel::MINIMIZED)
295 panel->SetExpansionState(Panel::TITLE_ONLY);
296 return;
299 // Panel is no longer drawing attention, but leave the panel in
300 // title-only mode if all titlebars are currently up.
301 if (state != Panel::TITLE_ONLY || are_titlebars_up_)
302 return;
304 // Leave titlebar up if panel is being dragged.
305 if (panel_manager_->drag_controller()->dragging_panel() == panel)
306 return;
308 // Leave titlebar up if mouse is in/below the panel.
309 const gfx::Point mouse_position =
310 panel_manager_->mouse_watcher()->GetMousePosition();
311 gfx::Rect bounds = panel->GetBounds();
312 if (bounds.x() <= mouse_position.x() &&
313 mouse_position.x() <= bounds.right() &&
314 mouse_position.y() >= bounds.y())
315 return;
317 // Bring down the titlebar now that panel is not drawing attention.
318 panel->SetExpansionState(Panel::MINIMIZED);
321 void DockedPanelCollection::OnPanelTitlebarClicked(Panel* panel,
322 panel::ClickModifier modifier) {
323 DCHECK_EQ(this, panel->collection());
324 if (!IsPanelMinimized(panel))
325 return;
327 if (modifier == panel::APPLY_TO_ALL)
328 RestoreAll();
329 else
330 RestorePanel(panel);
333 void DockedPanelCollection::ActivatePanel(Panel* panel) {
334 DCHECK_EQ(this, panel->collection());
336 // Make sure the panel is expanded when activated so the user input
337 // does not go into a collapsed window.
338 panel->SetExpansionState(Panel::EXPANDED);
340 // If the layout needs to be refreshed, it will happen in response to
341 // the activation notification (and with a slight delay to let things settle).
344 void DockedPanelCollection::MinimizePanel(Panel* panel) {
345 DCHECK_EQ(this, panel->collection());
347 if (panel->expansion_state() != Panel::EXPANDED)
348 return;
350 panel->SetExpansionState(panel->IsDrawingAttention() ?
351 Panel::TITLE_ONLY : Panel::MINIMIZED);
354 void DockedPanelCollection::RestorePanel(Panel* panel) {
355 DCHECK_EQ(this, panel->collection());
356 panel->SetExpansionState(Panel::EXPANDED);
359 void DockedPanelCollection::MinimizeAll() {
360 // Set minimizing_all_ to prevent deactivation of each panel when it
361 // is minimized. See comments in OnPanelExpansionStateChanged.
362 base::AutoReset<bool> pin(&minimizing_all_, true);
363 Panel* minimized_active_panel = NULL;
364 for (Panels::const_iterator iter = panels_.begin();
365 iter != panels_.end(); ++iter) {
366 if ((*iter)->IsActive())
367 minimized_active_panel = *iter;
368 MinimizePanel(*iter);
371 // When a single panel is minimized, it is deactivated to ensure that
372 // a minimized panel does not have focus. However, when minimizing all,
373 // the deactivation is only done once after all panels are minimized,
374 // rather than per minimized panel, both for efficiency and to avoid
375 // temporary activations of random not-yet-minimized panels.
376 if (minimized_active_panel) {
377 minimized_active_panel->Deactivate();
378 // Layout will be refreshed in response to (de)activation notification.
382 void DockedPanelCollection::RestoreAll() {
383 for (Panels::const_iterator iter = panels_.begin();
384 iter != panels_.end(); ++iter) {
385 RestorePanel(*iter);
389 void DockedPanelCollection::OnMinimizeButtonClicked(
390 Panel* panel, panel::ClickModifier modifier) {
391 if (modifier == panel::APPLY_TO_ALL)
392 MinimizeAll();
393 else
394 MinimizePanel(panel);
397 void DockedPanelCollection::OnRestoreButtonClicked(
398 Panel* panel, panel::ClickModifier modifier) {
399 if (modifier == panel::APPLY_TO_ALL)
400 RestoreAll();
401 else
402 RestorePanel(panel);
405 bool DockedPanelCollection::CanShowMinimizeButton(const Panel* panel) const {
406 return !IsPanelMinimized(panel);
409 bool DockedPanelCollection::CanShowRestoreButton(const Panel* panel) const {
410 return IsPanelMinimized(panel);
413 bool DockedPanelCollection::IsPanelMinimized(const Panel* panel) const {
414 return panel->expansion_state() != Panel::EXPANDED;
417 bool DockedPanelCollection::UsesAlwaysOnTopPanels() const {
418 return true;
421 void DockedPanelCollection::UpdateMinimizedPanelCount() {
422 int prev_minimized_panel_count = minimized_panel_count_;
423 minimized_panel_count_ = 0;
424 for (Panels::const_iterator panel_iter = panels_.begin();
425 panel_iter != panels_.end(); ++panel_iter) {
426 if ((*panel_iter)->expansion_state() != Panel::EXPANDED)
427 minimized_panel_count_++;
430 if (prev_minimized_panel_count == 0 && minimized_panel_count_ > 0)
431 panel_manager_->mouse_watcher()->AddObserver(this);
432 else if (prev_minimized_panel_count > 0 && minimized_panel_count_ == 0)
433 panel_manager_->mouse_watcher()->RemoveObserver(this);
435 DCHECK_LE(minimized_panel_count_, num_panels());
438 void DockedPanelCollection::ResizePanelWindow(
439 Panel* panel,
440 const gfx::Size& preferred_window_size) {
441 DCHECK_EQ(this, panel->collection());
442 // Make sure the new size does not violate panel's size restrictions.
443 gfx::Size new_size(preferred_window_size.width(),
444 preferred_window_size.height());
445 new_size = panel->ClampSize(new_size);
447 if (new_size == panel->full_size())
448 return;
450 panel->set_full_size(new_size);
452 RefreshLayout();
455 bool DockedPanelCollection::ShouldBringUpTitlebars(int mouse_x,
456 int mouse_y) const {
457 // We should always bring up the titlebar if the mouse is over the
458 // visible auto-hiding bottom bar.
459 DisplaySettingsProvider* provider =
460 panel_manager_->display_settings_provider();
461 if (provider->IsAutoHidingDesktopBarEnabled(
462 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) &&
463 provider->GetDesktopBarVisibility(
464 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) ==
465 DisplaySettingsProvider::DESKTOP_BAR_VISIBLE) {
466 int bottom_bar_bottom = work_area_.bottom();
467 int bottom_bar_y = bottom_bar_bottom - provider->GetDesktopBarThickness(
468 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
469 if (bottom_bar_y <= mouse_y && mouse_y <= bottom_bar_bottom)
470 return true;
473 // Bring up titlebars if any panel needs the titlebar up.
474 Panel* dragging_panel = panel_manager_->drag_controller()->dragging_panel();
475 if (dragging_panel &&
476 dragging_panel->collection()->type() != PanelCollection::DOCKED)
477 dragging_panel = NULL;
478 for (Panels::const_iterator iter = panels_.begin();
479 iter != panels_.end(); ++iter) {
480 Panel* panel = *iter;
481 Panel::ExpansionState state = panel->expansion_state();
482 // Skip the expanded panel.
483 if (state == Panel::EXPANDED)
484 continue;
486 // If the panel is showing titlebar only, we want to keep it up when it is
487 // being dragged.
488 if (state == Panel::TITLE_ONLY && panel == dragging_panel)
489 return true;
491 // We do not want to bring up other minimized panels if the mouse is over
492 // the panel that pops up the titlebar to attract attention.
493 if (panel->IsDrawingAttention())
494 continue;
496 gfx::Rect bounds = panel->GetBounds();
497 if (bounds.x() <= mouse_x && mouse_x <= bounds.right() &&
498 mouse_y >= bounds.y())
499 return true;
501 return false;
504 void DockedPanelCollection::BringUpOrDownTitlebars(bool bring_up) {
505 if (are_titlebars_up_ == bring_up)
506 return;
508 are_titlebars_up_ = bring_up;
509 int task_delay_ms = 0;
511 // If the auto-hiding bottom bar exists, delay the action until the bottom
512 // bar is fully visible or hidden. We do not want both bottom bar and panel
513 // titlebar to move at the same time but with different speeds.
514 DisplaySettingsProvider* provider =
515 panel_manager_->display_settings_provider();
516 if (provider->IsAutoHidingDesktopBarEnabled(
517 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) {
518 DisplaySettingsProvider::DesktopBarVisibility visibility =
519 provider->GetDesktopBarVisibility(
520 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
521 if (visibility !=
522 (bring_up ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE
523 : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN)) {
524 // Occasionally some system, like Windows, might not bring up or down the
525 // bottom bar when the mouse enters or leaves the bottom screen area.
526 // Thus, we schedule a delayed task to do the work if we do not receive
527 // the bottom bar visibility change notification within a certain period
528 // of time.
529 task_delay_ms = kMaxDelayWaitForBottomBarVisibilityChangeMs;
533 // OnAutoHidingDesktopBarVisibilityChanged will handle this.
534 delayed_titlebar_action_ = bring_up ? BRING_UP : BRING_DOWN;
536 // If user moves the mouse in and out of mouse tracking area, we might have
537 // previously posted but not yet dispatched task in the queue. New action
538 // should always 'reset' the delays so cancel any tasks that haven't run yet
539 // and post a new one.
540 titlebar_action_factory_.InvalidateWeakPtrs();
541 base::MessageLoop::current()->PostDelayedTask(
542 FROM_HERE,
543 base::Bind(&DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck,
544 titlebar_action_factory_.GetWeakPtr()),
545 base::TimeDelta::FromMilliseconds(
546 PanelManager::AdjustTimeInterval(task_delay_ms)));
549 void DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck() {
550 // Task was already processed or cancelled - bail out.
551 if (delayed_titlebar_action_ == NO_ACTION)
552 return;
554 bool need_to_bring_up_titlebars = (delayed_titlebar_action_ == BRING_UP);
556 delayed_titlebar_action_ = NO_ACTION;
558 // Check if the action is still needed based on the latest mouse position. The
559 // user could move the mouse into the tracking area and then quickly move it
560 // out of the area. In case of this, cancel the action.
561 if (are_titlebars_up_ != need_to_bring_up_titlebars)
562 return;
564 DoBringUpOrDownTitlebars(need_to_bring_up_titlebars);
567 void DockedPanelCollection::DoBringUpOrDownTitlebars(bool bring_up) {
568 for (Panels::const_iterator iter = panels_.begin();
569 iter != panels_.end(); ++iter) {
570 Panel* panel = *iter;
572 // Skip any panel that is drawing the attention.
573 if (panel->IsDrawingAttention())
574 continue;
576 if (bring_up) {
577 if (panel->expansion_state() == Panel::MINIMIZED)
578 panel->SetExpansionState(Panel::TITLE_ONLY);
579 } else {
580 if (panel->expansion_state() == Panel::TITLE_ONLY)
581 panel->SetExpansionState(Panel::MINIMIZED);
586 int DockedPanelCollection::GetBottomPositionForExpansionState(
587 Panel::ExpansionState expansion_state) const {
588 int bottom = work_area_.bottom();
589 // If there is an auto-hiding desktop bar aligned to the bottom edge, we need
590 // to move the title-only panel above the auto-hiding desktop bar.
591 DisplaySettingsProvider* provider =
592 panel_manager_->display_settings_provider();
593 if (expansion_state == Panel::TITLE_ONLY &&
594 provider->IsAutoHidingDesktopBarEnabled(
595 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) {
596 bottom -= provider->GetDesktopBarThickness(
597 DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
600 return bottom;
603 void DockedPanelCollection::OnMouseMove(const gfx::Point& mouse_position) {
604 bool bring_up_titlebars = ShouldBringUpTitlebars(mouse_position.x(),
605 mouse_position.y());
606 BringUpOrDownTitlebars(bring_up_titlebars);
609 void DockedPanelCollection::OnAutoHidingDesktopBarVisibilityChanged(
610 DisplaySettingsProvider::DesktopBarAlignment alignment,
611 DisplaySettingsProvider::DesktopBarVisibility visibility) {
612 if (delayed_titlebar_action_ == NO_ACTION)
613 return;
615 DisplaySettingsProvider::DesktopBarVisibility expected_visibility =
616 delayed_titlebar_action_ == BRING_UP
617 ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE
618 : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN;
619 if (visibility != expected_visibility)
620 return;
622 DoBringUpOrDownTitlebars(delayed_titlebar_action_ == BRING_UP);
623 delayed_titlebar_action_ = NO_ACTION;
626 void DockedPanelCollection::OnAutoHidingDesktopBarThicknessChanged(
627 DisplaySettingsProvider::DesktopBarAlignment alignment, int thickness) {
628 RefreshLayout();
631 void DockedPanelCollection::RefreshLayout() {
632 int total_active_width = 0;
633 int total_inactive_width = 0;
635 for (Panels::const_iterator panel_iter = panels_.begin();
636 panel_iter != panels_.end(); ++panel_iter) {
637 Panel* panel = *panel_iter;
638 if (panel->IsActive())
639 total_active_width += panel->full_size().width();
640 else
641 total_inactive_width += panel->full_size().width();
644 double display_width_for_inactive_panels =
645 work_area_.width() - total_active_width -
646 kPanelsHorizontalSpacing * panels_.size();
647 double overflow_squeeze_factor = (total_inactive_width > 0) ?
648 std::min(display_width_for_inactive_panels / total_inactive_width, 1.0) :
649 1.0;
651 // We want to calculate all bounds first, then apply them in a specific order.
652 typedef std::pair<Panel*, gfx::Rect> PanelBoundsInfo;
653 // The next pair of variables will hold panels that move, respectively,
654 // to the right and to the left. We want to process them from the center
655 // outwards, so one is a stack and another is a queue.
656 std::vector<PanelBoundsInfo> moving_right;
657 std::queue<PanelBoundsInfo> moving_left;
659 int rightmost_position = StartingRightPosition();
660 for (Panels::const_iterator panel_iter = panels_.begin();
661 panel_iter != panels_.end(); ++panel_iter) {
662 Panel* panel = *panel_iter;
663 gfx::Rect old_bounds = panel->GetBounds();
664 gfx::Rect new_bounds = old_bounds;
665 AdjustPanelBoundsPerExpansionState(panel, &new_bounds);
667 new_bounds.set_width(
668 WidthToDisplayPanelInCollection(panel->IsActive(),
669 overflow_squeeze_factor,
670 panel->full_size().width()));
671 int x = rightmost_position - new_bounds.width();
672 new_bounds.set_x(x);
674 if (x < old_bounds.x() ||
675 (x == old_bounds.x() && new_bounds.width() <= old_bounds.width()))
676 moving_left.push(std::make_pair(panel, new_bounds));
677 else
678 moving_right.push_back(std::make_pair(panel, new_bounds));
680 rightmost_position = x - kPanelsHorizontalSpacing;
683 // Update panels going in both directions.
684 // This is important on Mac where bounds changes are slow and you see a
685 // "wave" instead of a smooth sliding effect.
686 int num_animated = 0;
687 bool going_right = true;
688 while (!moving_right.empty() || !moving_left.empty()) {
689 PanelBoundsInfo bounds_info;
690 // Alternate between processing the panels that moving left and right,
691 // starting from the center.
692 going_right = !going_right;
693 bool take_panel_on_right =
694 (going_right && !moving_right.empty()) ||
695 moving_left.empty();
696 if (take_panel_on_right) {
697 bounds_info = moving_right.back();
698 moving_right.pop_back();
699 } else {
700 bounds_info = moving_left.front();
701 moving_left.pop();
704 // Don't update the docked panel that is in preview mode.
705 Panel* panel = bounds_info.first;
706 gfx::Rect bounds = bounds_info.second;
707 if (!panel->in_preview_mode() && bounds != panel->GetBounds()) {
708 // We animate a limited number of panels, starting with the
709 // "most important" ones, that is, ones that are close to the center
710 // of the action. Other panels are moved instantly to improve performance.
711 if (num_animated < kNumPanelsToAnimateSimultaneously) {
712 panel->SetPanelBounds(bounds); // Animates.
713 ++num_animated;
714 } else {
715 panel->SetPanelBoundsInstantly(bounds);
720 content::NotificationService::current()->Notify(
721 chrome::NOTIFICATION_PANEL_COLLECTION_UPDATED,
722 content::Source<PanelCollection>(this),
723 content::NotificationService::NoDetails());
726 int DockedPanelCollection::WidthToDisplayPanelInCollection(
727 bool is_for_active_panel, double squeeze_factor, int full_width) const {
728 return is_for_active_panel ? full_width :
729 std::max(panel::kPanelMinWidth,
730 static_cast<int>(floor(full_width * squeeze_factor)));
733 void DockedPanelCollection::CloseAll() {
734 // This should only be called at the end of tests to clean up.
736 // Make a copy of the iterator as closing panels can modify the vector.
737 Panels panels_copy = panels_;
739 // Start from the bottom to avoid reshuffling.
740 for (Panels::reverse_iterator iter = panels_copy.rbegin();
741 iter != panels_copy.rend(); ++iter)
742 (*iter)->Close();
745 void DockedPanelCollection::UpdatePanelOnCollectionChange(Panel* panel) {
746 panel->set_attention_mode(Panel::USE_PANEL_ATTENTION);
747 panel->ShowShadow(true);
748 panel->UpdateMinimizeRestoreButtonVisibility();
749 panel->SetWindowCornerStyle(panel::TOP_ROUNDED);
752 void DockedPanelCollection::ScheduleLayoutRefresh() {
753 refresh_action_factory_.InvalidateWeakPtrs();
754 base::MessageLoop::current()->PostDelayedTask(
755 FROM_HERE,
756 base::Bind(&DockedPanelCollection::RefreshLayout,
757 refresh_action_factory_.GetWeakPtr()),
758 base::TimeDelta::FromMilliseconds(PanelManager::AdjustTimeInterval(
759 kRefreshLayoutAfterActivePanelChangeDelayMs)));
762 void DockedPanelCollection::OnPanelActiveStateChanged(Panel* panel) {
763 // Refresh layout, but wait till active states settle.
764 // This lets us avoid refreshing too many times when one panel loses
765 // focus and another gains it.
766 ScheduleLayoutRefresh();
769 gfx::Rect DockedPanelCollection::GetInitialPanelBounds(
770 const gfx::Rect& requested_bounds) const {
771 gfx::Rect initial_bounds = requested_bounds;
772 initial_bounds.set_origin(
773 GetDefaultPositionForPanel(requested_bounds.size()));
774 return initial_bounds;
777 bool DockedPanelCollection::HasPanel(Panel* panel) const {
778 return find(panels_.begin(), panels_.end(), panel) != panels_.end();