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