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() 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() { 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