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
18namespace app_list {
19
20namespace {
21
22const int kPreferredHeight = 57;
23
24const int kMaxButtonSpacing = 18;
25const int kMinButtonSpacing = 4;
26const int kMaxButtonWidth = 68;
27const int kMinButtonWidth = 28;
28const int kButtonHeight = 6;
29const int kButtonCornerRadius = 2;
30const int kButtonStripPadding = 20;
31
32class 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|.
114PageSwitcherButton* GetButtonByIndex(views::View* buttons, int index) {
115  return static_cast<PageSwitcherButton*>(buttons->child_at(index));
116}
117
118}  // namespace
119
120PageSwitcher::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
130PageSwitcher::~PageSwitcher() {
131  model_->RemoveObserver(this);
132}
133
134int 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
150void 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
168gfx::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
175void 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
189void 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
219void 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
229void 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
240void 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
247void PageSwitcher::TransitionStarted() {
248}
249
250void 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