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