Home | History | Annotate | Download | only in launcher
      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/launcher/overflow_bubble.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ash/launcher/launcher_types.h"
     10 #include "ash/launcher/launcher_view.h"
     11 #include "ash/root_window_controller.h"
     12 #include "ash/shelf/shelf_layout_manager.h"
     13 #include "ash/shelf/shelf_widget.h"
     14 #include "ash/shell.h"
     15 #include "ash/system/tray/system_tray.h"
     16 #include "ui/aura/root_window.h"
     17 #include "ui/gfx/insets.h"
     18 #include "ui/gfx/screen.h"
     19 #include "ui/views/bubble/bubble_delegate.h"
     20 #include "ui/views/bubble/bubble_frame_view.h"
     21 #include "ui/views/widget/widget.h"
     22 
     23 namespace ash {
     24 namespace internal {
     25 
     26 namespace {
     27 
     28 // Max bubble size to screen size ratio.
     29 const float kMaxBubbleSizeToScreenRatio = 0.5f;
     30 
     31 // Inner padding in pixels for launcher view inside bubble.
     32 const int kPadding = 2;
     33 
     34 // Padding space in pixels between LauncherView's left/top edge to its contents.
     35 const int kLauncherViewLeadingInset = 8;
     36 
     37 ////////////////////////////////////////////////////////////////////////////////
     38 // OverflowBubbleView
     39 // OverflowBubbleView hosts a LauncherView to display overflown items.
     40 
     41 class OverflowBubbleView : public views::BubbleDelegateView {
     42  public:
     43   OverflowBubbleView();
     44   virtual ~OverflowBubbleView();
     45 
     46   void InitOverflowBubble(views::View* anchor, LauncherView* launcher_view);
     47 
     48  private:
     49   bool IsHorizontalAlignment() const {
     50     return GetShelfLayoutManagerForLauncher()->IsHorizontalAlignment();
     51   }
     52 
     53   const gfx::Size GetContentsSize() const {
     54     return static_cast<views::View*>(launcher_view_)->GetPreferredSize();
     55   }
     56 
     57   // Gets arrow location based on shelf alignment.
     58   views::BubbleBorder::Arrow GetBubbleArrow() const {
     59     return GetShelfLayoutManagerForLauncher()->SelectValueForShelfAlignment(
     60         views::BubbleBorder::BOTTOM_LEFT,
     61         views::BubbleBorder::LEFT_TOP,
     62         views::BubbleBorder::RIGHT_TOP,
     63         views::BubbleBorder::TOP_LEFT);
     64   }
     65 
     66   void ScrollByXOffset(int x_offset);
     67   void ScrollByYOffset(int y_offset);
     68 
     69   // views::View overrides:
     70   virtual gfx::Size GetPreferredSize() OVERRIDE;
     71   virtual void Layout() OVERRIDE;
     72   virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
     73   virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE;
     74 
     75   // ui::EventHandler overrides:
     76   virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
     77 
     78   // views::BubbleDelegate overrides:
     79   virtual gfx::Rect GetBubbleBounds() OVERRIDE;
     80 
     81   ShelfLayoutManager* GetShelfLayoutManagerForLauncher() const {
     82     return ShelfLayoutManager::ForLauncher(
     83         anchor_view()->GetWidget()->GetNativeView());
     84   }
     85 
     86   LauncherView* launcher_view_;  // Owned by views hierarchy.
     87   gfx::Vector2d scroll_offset_;
     88 
     89   DISALLOW_COPY_AND_ASSIGN(OverflowBubbleView);
     90 };
     91 
     92 OverflowBubbleView::OverflowBubbleView()
     93     : launcher_view_(NULL) {
     94 }
     95 
     96 OverflowBubbleView::~OverflowBubbleView() {
     97 }
     98 
     99 void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
    100                                             LauncherView* launcher_view) {
    101   // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher
    102   // can be called.
    103   set_anchor_view(anchor);
    104   set_arrow(GetBubbleArrow());
    105   set_background(NULL);
    106   set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0));
    107   set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding));
    108   set_move_with_anchor(true);
    109 
    110   // Makes bubble view has a layer and clip its children layers.
    111   SetPaintToLayer(true);
    112   SetFillsBoundsOpaquely(false);
    113   layer()->SetMasksToBounds(true);
    114 
    115   launcher_view_ = launcher_view;
    116   AddChildView(launcher_view_);
    117 
    118   views::BubbleDelegateView::CreateBubble(this);
    119 }
    120 
    121 void OverflowBubbleView::ScrollByXOffset(int x_offset) {
    122   const gfx::Rect visible_bounds(GetContentsBounds());
    123   const gfx::Size contents_size(GetContentsSize());
    124 
    125   int x = std::min(contents_size.width() - visible_bounds.width(),
    126                    std::max(0, scroll_offset_.x() + x_offset));
    127   scroll_offset_.set_x(x);
    128 }
    129 
    130 void OverflowBubbleView::ScrollByYOffset(int y_offset) {
    131   const gfx::Rect visible_bounds(GetContentsBounds());
    132   const gfx::Size contents_size(GetContentsSize());
    133 
    134   int y = std::min(contents_size.height() - visible_bounds.height(),
    135                    std::max(0, scroll_offset_.y() + y_offset));
    136   scroll_offset_.set_y(y);
    137 }
    138 
    139 gfx::Size OverflowBubbleView::GetPreferredSize() {
    140   gfx::Size preferred_size = GetContentsSize();
    141 
    142   const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
    143       GetAnchorRect().CenterPoint()).work_area();
    144   if (!monitor_rect.IsEmpty()) {
    145     if (IsHorizontalAlignment()) {
    146       preferred_size.set_width(std::min(
    147           preferred_size.width(),
    148           static_cast<int>(monitor_rect.width() *
    149                            kMaxBubbleSizeToScreenRatio)));
    150     } else {
    151       preferred_size.set_height(std::min(
    152           preferred_size.height(),
    153           static_cast<int>(monitor_rect.height() *
    154                            kMaxBubbleSizeToScreenRatio)));
    155     }
    156   }
    157 
    158   return preferred_size;
    159 }
    160 
    161 void OverflowBubbleView::Layout() {
    162   launcher_view_->SetBoundsRect(gfx::Rect(
    163       gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize()));
    164 }
    165 
    166 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) {
    167   // Ensures |launch_view_| is still visible.
    168   ScrollByXOffset(0);
    169   ScrollByYOffset(0);
    170   Layout();
    171 
    172   SizeToContents();
    173 }
    174 
    175 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) {
    176   // The MouseWheelEvent was changed to support both X and Y offsets
    177   // recently, but the behavior of this function was retained to continue
    178   // using Y offsets only. Might be good to simply scroll in both
    179   // directions as in OverflowBubbleView::OnScrollEvent.
    180   if (IsHorizontalAlignment())
    181     ScrollByXOffset(-event.y_offset());
    182   else
    183     ScrollByYOffset(-event.y_offset());
    184   Layout();
    185 
    186   return true;
    187 }
    188 
    189 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) {
    190   ScrollByXOffset(-event->x_offset());
    191   ScrollByYOffset(-event->y_offset());
    192   Layout();
    193   event->SetHandled();
    194 }
    195 
    196 gfx::Rect OverflowBubbleView::GetBubbleBounds() {
    197   views::BubbleBorder* border = GetBubbleFrameView()->bubble_border();
    198   gfx::Insets bubble_insets = border->GetInsets();
    199 
    200   const int border_size =
    201       views::BubbleBorder::is_arrow_on_horizontal(arrow()) ?
    202       bubble_insets.left() : bubble_insets.top();
    203   const int arrow_offset = border_size + kPadding + kLauncherViewLeadingInset +
    204       ShelfLayoutManager::GetPreferredShelfSize() / 2;
    205 
    206   const gfx::Size content_size = GetPreferredSize();
    207   border->set_arrow_offset(arrow_offset);
    208 
    209   const gfx::Rect anchor_rect = GetAnchorRect();
    210   gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds(
    211       anchor_rect,
    212       content_size,
    213       false);
    214 
    215   gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
    216       anchor_rect.CenterPoint()).work_area();
    217 
    218   int offset = 0;
    219   if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) {
    220     if (bubble_rect.x() < monitor_rect.x())
    221       offset = monitor_rect.x() - bubble_rect.x();
    222     else if (bubble_rect.right() > monitor_rect.right())
    223       offset = monitor_rect.right() - bubble_rect.right();
    224 
    225     bubble_rect.Offset(offset, 0);
    226     border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x());
    227   } else {
    228     if (bubble_rect.y() < monitor_rect.y())
    229       offset = monitor_rect.y() - bubble_rect.y();
    230     else if (bubble_rect.bottom() > monitor_rect.bottom())
    231       offset =  monitor_rect.bottom() - bubble_rect.bottom();
    232 
    233     bubble_rect.Offset(0, offset);
    234     border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y());
    235   }
    236 
    237   GetBubbleFrameView()->SchedulePaint();
    238   return bubble_rect;
    239 }
    240 
    241 }  // namespace
    242 
    243 OverflowBubble::OverflowBubble()
    244     : bubble_(NULL),
    245       launcher_view_(NULL) {
    246 }
    247 
    248 OverflowBubble::~OverflowBubble() {
    249   Hide();
    250 }
    251 
    252 void OverflowBubble::Show(views::View* anchor, LauncherView* launcher_view) {
    253   Hide();
    254 
    255   OverflowBubbleView* bubble_view = new OverflowBubbleView();
    256   bubble_view->InitOverflowBubble(anchor, launcher_view);
    257   launcher_view_ = launcher_view;
    258 
    259   bubble_ = bubble_view;
    260   RootWindowController::ForWindow(anchor->GetWidget()->GetNativeView())->
    261       GetSystemTray()->InitializeBubbleAnimations(bubble_->GetWidget());
    262   bubble_->GetWidget()->AddObserver(this);
    263   bubble_->GetWidget()->Show();
    264 }
    265 
    266 void OverflowBubble::Hide() {
    267   if (!IsShowing())
    268     return;
    269 
    270   bubble_->GetWidget()->RemoveObserver(this);
    271   bubble_->GetWidget()->Close();
    272   bubble_ = NULL;
    273   launcher_view_ = NULL;
    274 }
    275 
    276 void OverflowBubble::OnWidgetDestroying(views::Widget* widget) {
    277   DCHECK(widget == bubble_->GetWidget());
    278   bubble_ = NULL;
    279   launcher_view_ = NULL;
    280   ShelfLayoutManager::ForLauncher(
    281       widget->GetNativeView())->shelf_widget()->launcher()->SchedulePaint();
    282 }
    283 
    284 }  // namespace internal
    285 }  // namespace ash
    286