Home | History | Annotate | Download | only in views
      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 "ui/app_list/views/page_switcher.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "third_party/skia/include/core/SkPath.h"
     10 #include "ui/app_list/app_list_constants.h"
     11 #include "ui/app_list/pagination_model.h"
     12 #include "ui/base/animation/throb_animation.h"
     13 #include "ui/gfx/canvas.h"
     14 #include "ui/gfx/skia_util.h"
     15 #include "ui/views/controls/button/custom_button.h"
     16 #include "ui/views/layout/box_layout.h"
     17 
     18 namespace {
     19 
     20 const int kPreferredHeight = 57;
     21 
     22 const int kMaxButtonSpacing = 18;
     23 const int kMinButtonSpacing = 4;
     24 const int kMaxButtonWidth = 68;
     25 const int kMinButtonWidth = 28;
     26 const int kButtonHeight = 6;
     27 const int kButtonCornerRadius = 2;
     28 const int kButtonStripPadding = 20;
     29 
     30 class PageSwitcherButton : public views::CustomButton {
     31  public:
     32   explicit PageSwitcherButton(views::ButtonListener* listener)
     33       : views::CustomButton(listener),
     34         button_width_(kMaxButtonWidth),
     35         selected_range_(0) {
     36   }
     37   virtual ~PageSwitcherButton() {}
     38 
     39   void SetSelectedRange(double selected_range) {
     40     if (selected_range_ == selected_range)
     41       return;
     42 
     43     selected_range_ = selected_range;
     44     SchedulePaint();
     45   }
     46 
     47   void set_button_width(int button_width) { button_width_ = button_width; }
     48 
     49   // Overridden from views::View:
     50   virtual gfx::Size GetPreferredSize() OVERRIDE {
     51     return gfx::Size(button_width_, kButtonHeight);
     52   }
     53 
     54   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
     55     if (state() == STATE_HOVERED) {
     56       PaintButton(canvas, app_list::kPagerHoverColor);
     57     } else {
     58       PaintButton(canvas, app_list::kPagerNormalColor);
     59     }
     60   }
     61 
     62  private:
     63   // Paints a button that has two rounded corner at bottom.
     64   void PaintButton(gfx::Canvas* canvas, SkColor base_color) {
     65     gfx::Rect rect(GetContentsBounds());
     66     rect.ClampToCenteredSize(gfx::Size(button_width_, kButtonHeight));
     67 
     68     SkPath path;
     69     path.addRoundRect(gfx::RectToSkRect(rect),
     70                       SkIntToScalar(kButtonCornerRadius),
     71                       SkIntToScalar(kButtonCornerRadius));
     72 
     73     SkPaint paint;
     74     paint.setAntiAlias(true);
     75     paint.setStyle(SkPaint::kFill_Style);
     76     paint.setColor(base_color);
     77     canvas->DrawPath(path, paint);
     78 
     79     int selected_start_x = 0;
     80     int selected_width = 0;
     81     if (selected_range_ > 0) {
     82       selected_width = selected_range_ * rect.width();
     83     } else if (selected_range_ < 0) {
     84       selected_width =  -selected_range_ * rect.width();
     85       selected_start_x = rect.right() - selected_width;
     86     }
     87 
     88     if (selected_width) {
     89       gfx::Rect selected_rect(rect);
     90       selected_rect.set_x(selected_start_x);
     91       selected_rect.set_width(selected_width);
     92 
     93       SkPath selected_path;
     94       selected_path.addRoundRect(gfx::RectToSkRect(selected_rect),
     95                                  SkIntToScalar(kButtonCornerRadius),
     96                                  SkIntToScalar(kButtonCornerRadius));
     97       paint.setColor(app_list::kPagerSelectedColor);
     98       canvas->DrawPath(selected_path, paint);
     99     }
    100   }
    101 
    102   int button_width_;
    103 
    104   // [-1, 1] range that represents the portion of the button that should be
    105   // painted with kSelectedColor. Positive range starts from left side and
    106   // negative range starts from the right side.
    107   double selected_range_;
    108 
    109   DISALLOW_COPY_AND_ASSIGN(PageSwitcherButton);
    110 };
    111 
    112 // Gets PageSwitcherButton at |index| in |buttons|.
    113 PageSwitcherButton* GetButtonByIndex(views::View* buttons, int index) {
    114   return static_cast<PageSwitcherButton*>(buttons->child_at(index));
    115 }
    116 
    117 }  // namespace
    118 
    119 namespace app_list {
    120 
    121 PageSwitcher::PageSwitcher(PaginationModel* model)
    122     : model_(model),
    123       buttons_(new views::View) {
    124   AddChildView(buttons_);
    125 
    126   TotalPagesChanged();
    127   SelectedPageChanged(-1, model->selected_page());
    128   model_->AddObserver(this);
    129 }
    130 
    131 PageSwitcher::~PageSwitcher() {
    132   model_->RemoveObserver(this);
    133 }
    134 
    135 int PageSwitcher::GetPageForPoint(const gfx::Point& point) const {
    136   if (!buttons_->bounds().Contains(point))
    137     return -1;
    138 
    139   gfx::Point buttons_point(point);
    140   views::View::ConvertPointToTarget(this, buttons_, &buttons_point);
    141 
    142   for (int i = 0; i < buttons_->child_count(); ++i) {
    143     const views::View* button = buttons_->child_at(i);
    144     if (button->bounds().Contains(buttons_point))
    145       return i;
    146   }
    147 
    148   return -1;
    149 }
    150 
    151 void PageSwitcher::UpdateUIForDragPoint(const gfx::Point& point) {
    152   int page = GetPageForPoint(point);
    153 
    154   const int button_count = buttons_->child_count();
    155   if (page >= 0 && page < button_count) {
    156     PageSwitcherButton* button =
    157         static_cast<PageSwitcherButton*>(buttons_->child_at(page));
    158     button->SetState(views::CustomButton::STATE_HOVERED);
    159     return;
    160   }
    161 
    162   for (int i = 0; i < button_count; ++i) {
    163     PageSwitcherButton* button =
    164         static_cast<PageSwitcherButton*>(buttons_->child_at(i));
    165     button->SetState(views::CustomButton::STATE_NORMAL);
    166   }
    167 }
    168 
    169 gfx::Size PageSwitcher::GetPreferredSize() {
    170   // Always return a size with correct height so that container resize is not
    171   // needed when more pages are added.
    172   return gfx::Size(buttons_->GetPreferredSize().width(),
    173                    kPreferredHeight);
    174 }
    175 
    176 void PageSwitcher::Layout() {
    177   gfx::Rect rect(GetContentsBounds());
    178 
    179   CalculateButtonWidthAndSpacing(rect.width());
    180 
    181   // Makes |buttons_| horizontally center and vertically fill.
    182   gfx::Size buttons_size(buttons_->GetPreferredSize());
    183   gfx::Rect buttons_bounds(rect.CenterPoint().x() - buttons_size.width() / 2,
    184                            rect.y(),
    185                            buttons_size.width(),
    186                            rect.height());
    187   buttons_->SetBoundsRect(gfx::IntersectRects(rect, buttons_bounds));
    188 }
    189 
    190 void PageSwitcher::CalculateButtonWidthAndSpacing(int contents_width) {
    191   const int button_count = buttons_->child_count();
    192   if (!button_count)
    193     return;
    194 
    195   contents_width -= 2 * kButtonStripPadding;
    196 
    197   int button_width = kMinButtonWidth;
    198   int button_spacing = kMinButtonSpacing;
    199   if (button_count > 1) {
    200     button_spacing = (contents_width - button_width * button_count) /
    201         (button_count - 1);
    202     button_spacing = std::min(kMaxButtonSpacing,
    203                               std::max(kMinButtonSpacing, button_spacing));
    204   }
    205 
    206   button_width = (contents_width - (button_count - 1) * button_spacing) /
    207       button_count;
    208   button_width = std::min(kMaxButtonWidth,
    209                           std::max(kMinButtonWidth, button_width));
    210 
    211   buttons_->SetLayoutManager(new views::BoxLayout(
    212       views::BoxLayout::kHorizontal, kButtonStripPadding, 0, button_spacing));
    213   for (int i = 0; i < button_count; ++i) {
    214     PageSwitcherButton* button =
    215         static_cast<PageSwitcherButton*>(buttons_->child_at(i));
    216     button->set_button_width(button_width);
    217   }
    218 }
    219 
    220 void PageSwitcher::ButtonPressed(views::Button* sender,
    221                                  const ui::Event& event) {
    222   for (int i = 0; i < buttons_->child_count(); ++i) {
    223     if (sender == static_cast<views::Button*>(buttons_->child_at(i))) {
    224       model_->SelectPage(i, true /* animate */);
    225       break;
    226     }
    227   }
    228 }
    229 
    230 void PageSwitcher::TotalPagesChanged() {
    231   buttons_->RemoveAllChildViews(true);
    232   for (int i = 0; i < model_->total_pages(); ++i) {
    233     PageSwitcherButton* button = new PageSwitcherButton(this);
    234     button->SetSelectedRange(i == model_->selected_page() ? 1 : 0);
    235     buttons_->AddChildView(button);
    236   }
    237   buttons_->SetVisible(model_->total_pages() > 1);
    238   Layout();
    239 }
    240 
    241 void PageSwitcher::SelectedPageChanged(int old_selected, int new_selected) {
    242   if (old_selected >= 0 && old_selected < buttons_->child_count())
    243     GetButtonByIndex(buttons_, old_selected)->SetSelectedRange(0);
    244   if (new_selected >= 0 && new_selected < buttons_->child_count())
    245     GetButtonByIndex(buttons_, new_selected)->SetSelectedRange(1);
    246 }
    247 
    248 void PageSwitcher::TransitionStarted() {
    249 }
    250 
    251 void PageSwitcher::TransitionChanged() {
    252   const int current_page = model_->selected_page();
    253   const int target_page = model_->transition().target_page;
    254 
    255   double progress = model_->transition().progress;
    256   double remaining = progress - 1;
    257 
    258   if (current_page > target_page) {
    259     remaining = -remaining;
    260     progress = -progress;
    261   }
    262 
    263   GetButtonByIndex(buttons_, current_page)->SetSelectedRange(remaining);
    264   if (model_->is_valid_page(target_page))
    265     GetButtonByIndex(buttons_, target_page)->SetSelectedRange(progress);
    266 }
    267 
    268 }  // namespace app_list
    269