| OLD | NEW |
| (Empty) |
| 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/app_list_model_view.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "ui/app_list/app_list_item_view.h" | |
| 10 #include "ui/app_list/app_list_model.h" | |
| 11 #include "ui/app_list/pagination_model.h" | |
| 12 #include "ui/views/border.h" | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // Padding space in pixels for fixed layout. | |
| 17 const int kTopLeftRightPadding = 15; | |
| 18 const int kBottomPadding = 30; | |
| 19 | |
| 20 // Preferred tile size when showing in fixed layout. | |
| 21 const int kPreferredTileWidth = 80; | |
| 22 const int kPreferredTileHeight = 88; | |
| 23 | |
| 24 } // namespace | |
| 25 | |
| 26 namespace app_list { | |
| 27 | |
| 28 AppListModelView::AppListModelView(views::ButtonListener* listener, | |
| 29 PaginationModel* pagination_model) | |
| 30 : model_(NULL), | |
| 31 listener_(listener), | |
| 32 pagination_model_(pagination_model), | |
| 33 fixed_layout_(false), | |
| 34 cols_(0), | |
| 35 rows_per_page_(0), | |
| 36 selected_item_index_(-1) { | |
| 37 set_focusable(true); | |
| 38 pagination_model_->AddObserver(this); | |
| 39 } | |
| 40 | |
| 41 AppListModelView::~AppListModelView() { | |
| 42 if (model_) | |
| 43 model_->RemoveObserver(this); | |
| 44 pagination_model_->RemoveObserver(this); | |
| 45 } | |
| 46 | |
| 47 void AppListModelView::CalculateLayout(const gfx::Size& content_size, | |
| 48 int num_of_tiles, | |
| 49 gfx::Size* icon_size, | |
| 50 int* rows, | |
| 51 int* cols) { | |
| 52 DCHECK(!content_size.IsEmpty() && num_of_tiles); | |
| 53 | |
| 54 // Icon sizes to try. | |
| 55 const int kIconSizes[] = { 128, 96, 64, 48, 32 }; | |
| 56 | |
| 57 double aspect = static_cast<double>(content_size.width()) / | |
| 58 content_size.height(); | |
| 59 | |
| 60 // Chooses the biggest icon size that could fit all tiles. | |
| 61 gfx::Size tile_size; | |
| 62 for (size_t i = 0; i < arraysize(kIconSizes); ++i) { | |
| 63 icon_size->SetSize(kIconSizes[i], kIconSizes[i]); | |
| 64 tile_size = AppListItemView::GetPreferredSizeForIconSize( | |
| 65 *icon_size); | |
| 66 | |
| 67 int max_cols = content_size.width() / tile_size.width(); | |
| 68 int max_rows = content_size.height() / tile_size.height(); | |
| 69 | |
| 70 // Skip if |tile_size| could not fit into |content_size|. | |
| 71 if (max_cols * max_rows < num_of_tiles) | |
| 72 continue; | |
| 73 | |
| 74 // Find a rows/cols pair that has a aspect ratio closest to |aspect|. | |
| 75 double min_aspect_diff = 1e5; | |
| 76 for (int c = std::max(max_cols / 2, 1); c <= max_cols; ++c) { | |
| 77 int r = std::min((num_of_tiles - 1) / c + 1, max_rows); | |
| 78 if (c * r < num_of_tiles) | |
| 79 continue; | |
| 80 | |
| 81 double aspect_diff = fabs(static_cast<double>(c) / r - aspect); | |
| 82 if (aspect_diff < min_aspect_diff) { | |
| 83 *cols = c; | |
| 84 *rows = r; | |
| 85 min_aspect_diff = aspect_diff; | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 DCHECK((*rows) * (*cols) >= num_of_tiles); | |
| 90 return; | |
| 91 } | |
| 92 | |
| 93 // No icon size that could fit all tiles. | |
| 94 *cols = std::max(content_size.width() / tile_size.width(), 1); | |
| 95 *rows = (num_of_tiles - 1) / (*cols) + 1; | |
| 96 } | |
| 97 | |
| 98 void AppListModelView::SetLayout(int icon_size, int cols, int rows_per_page) { | |
| 99 fixed_layout_ = true; | |
| 100 | |
| 101 icon_size_.SetSize(icon_size, icon_size); | |
| 102 cols_ = cols; | |
| 103 rows_per_page_ = rows_per_page; | |
| 104 | |
| 105 set_border(views::Border::CreateEmptyBorder(kTopLeftRightPadding, | |
| 106 kTopLeftRightPadding, | |
| 107 kBottomPadding, | |
| 108 kTopLeftRightPadding)); | |
| 109 } | |
| 110 | |
| 111 void AppListModelView::SetModel(AppListModel* model) { | |
| 112 if (model_) | |
| 113 model_->RemoveObserver(this); | |
| 114 | |
| 115 model_ = model; | |
| 116 if (model_) | |
| 117 model_->AddObserver(this); | |
| 118 Update(); | |
| 119 } | |
| 120 | |
| 121 void AppListModelView::SetSelectedItem(AppListItemView* item) { | |
| 122 int index = GetIndexOf(item); | |
| 123 if (index >= 0) | |
| 124 SetSelectedItemByIndex(index); | |
| 125 } | |
| 126 | |
| 127 void AppListModelView::ClearSelectedItem(AppListItemView* item) { | |
| 128 int index = GetIndexOf(item); | |
| 129 if (index == selected_item_index_) | |
| 130 SetSelectedItemByIndex(-1); | |
| 131 } | |
| 132 | |
| 133 void AppListModelView::Update() { | |
| 134 selected_item_index_ = -1; | |
| 135 RemoveAllChildViews(true); | |
| 136 if (!model_ || model_->item_count() == 0) | |
| 137 return; | |
| 138 | |
| 139 for (size_t i = 0; i < model_->item_count(); ++i) | |
| 140 AddChildView(new AppListItemView(this, model_->GetItemAt(i), listener_)); | |
| 141 | |
| 142 Layout(); | |
| 143 SchedulePaint(); | |
| 144 } | |
| 145 | |
| 146 AppListItemView* AppListModelView::GetItemViewAtIndex(int index) { | |
| 147 return static_cast<AppListItemView*>(child_at(index)); | |
| 148 } | |
| 149 | |
| 150 void AppListModelView::SetSelectedItemByIndex(int index) { | |
| 151 if (selected_item_index_ == index) | |
| 152 return; | |
| 153 | |
| 154 if (selected_item_index_ >= 0) | |
| 155 GetItemViewAtIndex(selected_item_index_)->SetSelected(false); | |
| 156 | |
| 157 if (index < 0 || index >= child_count()) { | |
| 158 selected_item_index_ = -1; | |
| 159 } else { | |
| 160 selected_item_index_ = index; | |
| 161 GetItemViewAtIndex(selected_item_index_)->SetSelected(true); | |
| 162 | |
| 163 if (tiles_per_page()) | |
| 164 pagination_model_->SelectPage(selected_item_index_ / tiles_per_page()); | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 gfx::Size AppListModelView::GetPreferredSize() { | |
| 169 if (!fixed_layout_) | |
| 170 return gfx::Size(); | |
| 171 | |
| 172 gfx::Insets insets(GetInsets()); | |
| 173 gfx::Size tile_size = gfx::Size(kPreferredTileWidth, kPreferredTileHeight); | |
| 174 return gfx::Size(tile_size.width() * cols_ + insets.width(), | |
| 175 tile_size.height() * rows_per_page_ + insets.height()); | |
| 176 } | |
| 177 | |
| 178 void AppListModelView::Layout() { | |
| 179 gfx::Rect rect(GetContentsBounds()); | |
| 180 if (rect.IsEmpty() || child_count() == 0) | |
| 181 return; | |
| 182 | |
| 183 gfx::Size tile_size; | |
| 184 if (fixed_layout_) { | |
| 185 tile_size = gfx::Size(kPreferredTileWidth, kPreferredTileHeight); | |
| 186 } else { | |
| 187 int rows = 0; | |
| 188 CalculateLayout(rect.size(), child_count(), &icon_size_, &rows, &cols_); | |
| 189 | |
| 190 tile_size = AppListItemView::GetPreferredSizeForIconSize( | |
| 191 icon_size_); | |
| 192 rows_per_page_ = tile_size.height() ? | |
| 193 std::max(rect.height() / tile_size.height(), 1) : 1; | |
| 194 | |
| 195 tile_size.set_width(std::max(rect.width() / (cols_ + 1), | |
| 196 tile_size.width())); | |
| 197 tile_size.set_height(std::max(rect.height() / (rows_per_page_ + 1), | |
| 198 tile_size.height())); | |
| 199 } | |
| 200 | |
| 201 if (!tiles_per_page()) | |
| 202 return; | |
| 203 | |
| 204 pagination_model_->SetTotalPages((child_count() - 1) / tiles_per_page() + 1); | |
| 205 if (pagination_model_->selected_page() < 0) | |
| 206 pagination_model_->SelectPage(0); | |
| 207 | |
| 208 gfx::Rect grid_rect = rect.Center( | |
| 209 gfx::Size(tile_size.width() * cols_, | |
| 210 tile_size.height() * rows_per_page_)); | |
| 211 grid_rect = grid_rect.Intersect(rect); | |
| 212 | |
| 213 // Layouts items. | |
| 214 const int page = pagination_model_->selected_page(); | |
| 215 const int first_visible_index = page * tiles_per_page(); | |
| 216 const int last_visible_index = (page + 1) * tiles_per_page() - 1; | |
| 217 gfx::Rect current_tile(grid_rect.origin(), tile_size); | |
| 218 for (int i = 0; i < child_count(); ++i) { | |
| 219 views::View* view = child_at(i); | |
| 220 static_cast<AppListItemView*>(view)->SetIconSize(icon_size_); | |
| 221 | |
| 222 if (i < first_visible_index || i > last_visible_index) { | |
| 223 view->SetVisible(false); | |
| 224 continue; | |
| 225 } | |
| 226 | |
| 227 view->SetBoundsRect(current_tile); | |
| 228 view->SetVisible(rect.Contains(current_tile)); | |
| 229 | |
| 230 current_tile.Offset(tile_size.width(), 0); | |
| 231 if ((i + 1) % cols_ == 0) { | |
| 232 current_tile.set_x(grid_rect.x()); | |
| 233 current_tile.set_y(current_tile.y() + tile_size.height()); | |
| 234 } | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 bool AppListModelView::OnKeyPressed(const views::KeyEvent& event) { | |
| 239 bool handled = false; | |
| 240 if (selected_item_index_ >= 0) | |
| 241 handled = GetItemViewAtIndex(selected_item_index_)->OnKeyPressed(event); | |
| 242 | |
| 243 if (!handled) { | |
| 244 switch (event.key_code()) { | |
| 245 case ui::VKEY_LEFT: | |
| 246 SetSelectedItemByIndex(std::max(selected_item_index_ - 1, 0)); | |
| 247 return true; | |
| 248 case ui::VKEY_RIGHT: | |
| 249 SetSelectedItemByIndex(std::min(selected_item_index_ + 1, | |
| 250 child_count() - 1)); | |
| 251 return true; | |
| 252 case ui::VKEY_UP: | |
| 253 SetSelectedItemByIndex(std::max(selected_item_index_ - cols_, | |
| 254 0)); | |
| 255 return true; | |
| 256 case ui::VKEY_DOWN: | |
| 257 if (selected_item_index_ < 0) { | |
| 258 SetSelectedItemByIndex(0); | |
| 259 } else { | |
| 260 SetSelectedItemByIndex(std::min(selected_item_index_ + cols_, | |
| 261 child_count() - 1)); | |
| 262 } | |
| 263 return true; | |
| 264 case ui::VKEY_PRIOR: { | |
| 265 SetSelectedItemByIndex( | |
| 266 std::max(selected_item_index_ - tiles_per_page(), | |
| 267 0)); | |
| 268 return true; | |
| 269 } | |
| 270 case ui::VKEY_NEXT: { | |
| 271 if (selected_item_index_ < 0) { | |
| 272 SetSelectedItemByIndex(0); | |
| 273 } else { | |
| 274 SetSelectedItemByIndex( | |
| 275 std::min(selected_item_index_ + tiles_per_page(), | |
| 276 child_count() - 1)); | |
| 277 } | |
| 278 } | |
| 279 default: | |
| 280 break; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 return handled; | |
| 285 } | |
| 286 | |
| 287 bool AppListModelView::OnKeyReleased(const views::KeyEvent& event) { | |
| 288 bool handled = false; | |
| 289 if (selected_item_index_ >= 0) | |
| 290 handled = GetItemViewAtIndex(selected_item_index_)->OnKeyReleased(event); | |
| 291 | |
| 292 return handled; | |
| 293 } | |
| 294 | |
| 295 void AppListModelView::OnPaintFocusBorder(gfx::Canvas* canvas) { | |
| 296 // Override to not paint focus frame. | |
| 297 } | |
| 298 | |
| 299 void AppListModelView::ListItemsAdded(size_t start, size_t count) { | |
| 300 for (size_t i = start; i < start + count; ++i) { | |
| 301 AddChildViewAt(new AppListItemView(this, model_->GetItemAt(i), listener_), | |
| 302 i); | |
| 303 } | |
| 304 Layout(); | |
| 305 SchedulePaint(); | |
| 306 } | |
| 307 | |
| 308 void AppListModelView::ListItemsRemoved(size_t start, size_t count) { | |
| 309 for (size_t i = 0; i < count; ++i) | |
| 310 delete child_at(start); | |
| 311 | |
| 312 Layout(); | |
| 313 SchedulePaint(); | |
| 314 } | |
| 315 | |
| 316 void AppListModelView::ListItemsChanged(size_t start, size_t count) { | |
| 317 NOTREACHED(); | |
| 318 } | |
| 319 | |
| 320 void AppListModelView::TotalPagesChanged() { | |
| 321 } | |
| 322 | |
| 323 void AppListModelView::SelectedPageChanged(int old_selected, int new_selected) { | |
| 324 Layout(); | |
| 325 } | |
| 326 | |
| 327 } // namespace app_list | |
| OLD | NEW |