Home | History | Annotate | Download | only in shelf
      1 // Copyright 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 #include "ash/shelf/overflow_bubble_view.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ash/root_window_controller.h"
     10 #include "ash/shelf/shelf_layout_manager.h"
     11 #include "ash/shelf/shelf_view.h"
     12 #include "ash/shell.h"
     13 #include "ash/shell_window_ids.h"
     14 #include "ui/events/event.h"
     15 #include "ui/gfx/insets.h"
     16 #include "ui/gfx/screen.h"
     17 #include "ui/views/bubble/bubble_frame_view.h"
     18 #include "ui/views/widget/widget.h"
     19 
     20 namespace ash {
     21 namespace internal {
     22 
     23 namespace {
     24 
     25 // Max bubble size to screen size ratio.
     26 const float kMaxBubbleSizeToScreenRatio = 0.5f;
     27 
     28 // Inner padding in pixels for shelf view inside bubble.
     29 const int kPadding = 2;
     30 
     31 // Padding space in pixels between ShelfView's left/top edge to its contents.
     32 const int kShelfViewLeadingInset = 8;
     33 
     34 }  // namespace
     35 
     36 OverflowBubbleView::OverflowBubbleView()
     37     : shelf_view_(NULL) {
     38 }
     39 
     40 OverflowBubbleView::~OverflowBubbleView() {
     41 }
     42 
     43 void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
     44                                             ShelfView* shelf_view) {
     45   // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher
     46   // can be called.
     47   SetAnchorView(anchor);
     48   set_arrow(GetBubbleArrow());
     49   set_background(NULL);
     50   set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0));
     51   set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding));
     52   set_move_with_anchor(true);
     53   // Overflow bubble should not get focus. If it get focus when it is shown,
     54   // active state item is changed to running state.
     55   set_use_focusless(true);
     56 
     57   // Makes bubble view has a layer and clip its children layers.
     58   SetPaintToLayer(true);
     59   SetFillsBoundsOpaquely(false);
     60   layer()->SetMasksToBounds(true);
     61 
     62   shelf_view_ = shelf_view;
     63   AddChildView(shelf_view_);
     64 
     65   set_parent_window(Shell::GetContainer(
     66       anchor->GetWidget()->GetNativeWindow()->GetRootWindow(),
     67       internal::kShellWindowId_ShelfBubbleContainer));
     68   views::BubbleDelegateView::CreateBubble(this);
     69 }
     70 
     71 bool OverflowBubbleView::IsHorizontalAlignment() const {
     72   ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManagerForLauncher();
     73   return shelf_layout_manager ?
     74       shelf_layout_manager->IsHorizontalAlignment() :
     75       false;
     76 }
     77 
     78 const gfx::Size OverflowBubbleView::GetContentsSize() const {
     79   return static_cast<views::View*>(shelf_view_)->GetPreferredSize();
     80 }
     81 
     82 // Gets arrow location based on shelf alignment.
     83 views::BubbleBorder::Arrow OverflowBubbleView::GetBubbleArrow() const {
     84   ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManagerForLauncher();
     85   return shelf_layout_manager ?
     86       shelf_layout_manager->SelectValueForShelfAlignment(
     87           views::BubbleBorder::BOTTOM_LEFT,
     88           views::BubbleBorder::LEFT_TOP,
     89           views::BubbleBorder::RIGHT_TOP,
     90           views::BubbleBorder::TOP_LEFT) :
     91       views::BubbleBorder::NONE;
     92 }
     93 
     94 void OverflowBubbleView::ScrollByXOffset(int x_offset) {
     95   const gfx::Rect visible_bounds(GetContentsBounds());
     96   const gfx::Size contents_size(GetContentsSize());
     97 
     98   DCHECK_GE(contents_size.width(), visible_bounds.width());
     99   int x = std::min(contents_size.width() - visible_bounds.width(),
    100                    std::max(0, scroll_offset_.x() + x_offset));
    101   scroll_offset_.set_x(x);
    102 }
    103 
    104 void OverflowBubbleView::ScrollByYOffset(int y_offset) {
    105   const gfx::Rect visible_bounds(GetContentsBounds());
    106   const gfx::Size contents_size(GetContentsSize());
    107 
    108   DCHECK_GE(contents_size.width(), visible_bounds.width());
    109   int y = std::min(contents_size.height() - visible_bounds.height(),
    110                    std::max(0, scroll_offset_.y() + y_offset));
    111   scroll_offset_.set_y(y);
    112 }
    113 
    114 gfx::Size OverflowBubbleView::GetPreferredSize() {
    115   gfx::Size preferred_size = GetContentsSize();
    116 
    117   const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
    118       GetAnchorRect().CenterPoint()).work_area();
    119   if (!monitor_rect.IsEmpty()) {
    120     if (IsHorizontalAlignment()) {
    121       preferred_size.set_width(std::min(
    122           preferred_size.width(),
    123           static_cast<int>(monitor_rect.width() *
    124                            kMaxBubbleSizeToScreenRatio)));
    125     } else {
    126       preferred_size.set_height(std::min(
    127           preferred_size.height(),
    128           static_cast<int>(monitor_rect.height() *
    129                            kMaxBubbleSizeToScreenRatio)));
    130     }
    131   }
    132 
    133   return preferred_size;
    134 }
    135 
    136 void OverflowBubbleView::Layout() {
    137   shelf_view_->SetBoundsRect(gfx::Rect(
    138       gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize()));
    139 }
    140 
    141 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) {
    142   // When contents size is changed, ContentsBounds should be updated before
    143   // calculating scroll offset.
    144   SizeToContents();
    145 
    146   // Ensures |shelf_view_| is still visible.
    147   if (IsHorizontalAlignment())
    148     ScrollByXOffset(0);
    149   else
    150     ScrollByYOffset(0);
    151   Layout();
    152 }
    153 
    154 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) {
    155   // The MouseWheelEvent was changed to support both X and Y offsets
    156   // recently, but the behavior of this function was retained to continue
    157   // using Y offsets only. Might be good to simply scroll in both
    158   // directions as in OverflowBubbleView::OnScrollEvent.
    159   if (IsHorizontalAlignment())
    160     ScrollByXOffset(-event.y_offset());
    161   else
    162     ScrollByYOffset(-event.y_offset());
    163   Layout();
    164 
    165   return true;
    166 }
    167 
    168 ShelfLayoutManager*
    169 OverflowBubbleView::GetShelfLayoutManagerForLauncher() const {
    170   return GetAnchorView() ?
    171       ShelfLayoutManager::ForLauncher(
    172           GetAnchorView()->GetWidget()->GetNativeView()) :
    173       NULL;
    174 }
    175 
    176 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) {
    177   ScrollByXOffset(-event->x_offset());
    178   ScrollByYOffset(-event->y_offset());
    179   Layout();
    180   event->SetHandled();
    181 }
    182 
    183 gfx::Rect OverflowBubbleView::GetBubbleBounds() {
    184   views::BubbleBorder* border = GetBubbleFrameView()->bubble_border();
    185   gfx::Insets bubble_insets = border->GetInsets();
    186 
    187   const int border_size =
    188       views::BubbleBorder::is_arrow_on_horizontal(arrow()) ?
    189       bubble_insets.left() : bubble_insets.top();
    190   const int arrow_offset = border_size + kPadding + kShelfViewLeadingInset +
    191       ShelfLayoutManager::GetPreferredShelfSize() / 2;
    192 
    193   const gfx::Size content_size = GetPreferredSize();
    194   border->set_arrow_offset(arrow_offset);
    195 
    196   const gfx::Rect anchor_rect = GetAnchorRect();
    197   gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds(
    198       anchor_rect,
    199       content_size,
    200       false);
    201 
    202   gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
    203       anchor_rect.CenterPoint()).work_area();
    204 
    205   int offset = 0;
    206   if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) {
    207     if (bubble_rect.x() < monitor_rect.x())
    208       offset = monitor_rect.x() - bubble_rect.x();
    209     else if (bubble_rect.right() > monitor_rect.right())
    210       offset = monitor_rect.right() - bubble_rect.right();
    211 
    212     bubble_rect.Offset(offset, 0);
    213     border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x());
    214   } else {
    215     if (bubble_rect.y() < monitor_rect.y())
    216       offset = monitor_rect.y() - bubble_rect.y();
    217     else if (bubble_rect.bottom() > monitor_rect.bottom())
    218       offset =  monitor_rect.bottom() - bubble_rect.bottom();
    219 
    220     bubble_rect.Offset(0, offset);
    221     border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y());
    222   }
    223 
    224   GetBubbleFrameView()->SchedulePaint();
    225   return bubble_rect;
    226 }
    227 
    228 }  // namespace internal
    229 }  // namespace ash
    230