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