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
18namespace {
19
20const int kPreferredHeight = 57;
21
22const int kMaxButtonSpacing = 18;
23const int kMinButtonSpacing = 4;
24const int kMaxButtonWidth = 68;
25const int kMinButtonWidth = 28;
26const int kButtonHeight = 6;
27const int kButtonCornerRadius = 2;
28const int kButtonStripPadding = 20;
29
30class 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|.
113PageSwitcherButton* GetButtonByIndex(views::View* buttons, int index) {
114  return static_cast<PageSwitcherButton*>(buttons->child_at(index));
115}
116
117}  // namespace
118
119namespace app_list {
120
121PageSwitcher::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
131PageSwitcher::~PageSwitcher() {
132  model_->RemoveObserver(this);
133}
134
135int 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
151void 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
169gfx::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
176void 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
190void 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
220void 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
230void 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
241void 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
248void PageSwitcher::TransitionStarted() {
249}
250
251void 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