| 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 "ash/app_list/app_list_item_view.h" | |
| 6 | |
| 7 #include "ash/app_list/app_list.h" | |
| 8 #include "ash/app_list/app_list_item_model.h" | |
| 9 #include "ash/app_list/app_list_model_view.h" | |
| 10 #include "ash/app_list/drop_shadow_label.h" | |
| 11 #include "ash/app_list/icon_cache.h" | |
| 12 #include "base/bind.h" | |
| 13 #include "base/message_loop.h" | |
| 14 #include "base/synchronization/cancellation_flag.h" | |
| 15 #include "base/threading/worker_pool.h" | |
| 16 #include "base/utf_string_conversions.h" | |
| 17 #include "ui/base/accessibility/accessible_view_state.h" | |
| 18 #include "ui/base/animation/throb_animation.h" | |
| 19 #include "ui/base/resource/resource_bundle.h" | |
| 20 #include "ui/gfx/canvas.h" | |
| 21 #include "ui/gfx/font.h" | |
| 22 #include "ui/gfx/shadow_value.h" | |
| 23 #include "ui/gfx/skbitmap_operations.h" | |
| 24 #include "ui/views/controls/image_view.h" | |
| 25 #include "ui/views/controls/menu/menu_item_view.h" | |
| 26 #include "ui/views/controls/menu/menu_model_adapter.h" | |
| 27 #include "ui/views/controls/menu/menu_runner.h" | |
| 28 | |
| 29 namespace ash { | |
| 30 | |
| 31 namespace { | |
| 32 | |
| 33 const int kTopBottomPadding = 10; | |
| 34 const int kIconTitleSpacing = 10; | |
| 35 | |
| 36 const SkColor kTitleColor = SK_ColorWHITE; | |
| 37 const SkColor kTitleColorV2 = SkColorSetARGB(0xFF, 0x88, 0x88, 0x88); | |
| 38 | |
| 39 // 0.33 black | |
| 40 const SkColor kHoverAndPushedColor = SkColorSetARGB(0x55, 0x00, 0x00, 0x00); | |
| 41 | |
| 42 // 0.16 black | |
| 43 const SkColor kSelectedColor = SkColorSetARGB(0x2A, 0x00, 0x00, 0x00); | |
| 44 | |
| 45 const SkColor kHighlightedColor = kHoverAndPushedColor; | |
| 46 | |
| 47 // FontSize/IconSize ratio = 24 / 128, which means we should get 24 font size | |
| 48 // when icon size is 128. | |
| 49 const float kFontSizeToIconSizeRatio = 0.1875f; | |
| 50 | |
| 51 // Font smaller than kBoldFontSize needs to be bold. | |
| 52 const int kBoldFontSize = 14; | |
| 53 | |
| 54 const int kMinFontSize = 12; | |
| 55 | |
| 56 const int kMinTitleChars = 15; | |
| 57 | |
| 58 const int kLeftRightPaddingChars = 1; | |
| 59 | |
| 60 const gfx::Font& GetTitleFontForIconSize(const gfx::Size& size) { | |
| 61 static int icon_height; | |
| 62 static gfx::Font* font = NULL; | |
| 63 | |
| 64 if (font && icon_height == size.height()) | |
| 65 return *font; | |
| 66 | |
| 67 delete font; | |
| 68 | |
| 69 icon_height = size.height(); | |
| 70 int font_size = std::max( | |
| 71 static_cast<int>(icon_height * kFontSizeToIconSizeRatio), | |
| 72 kMinFontSize); | |
| 73 | |
| 74 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 75 gfx::Font title_font(rb.GetFont(ui::ResourceBundle::BaseFont).GetFontName(), | |
| 76 font_size); | |
| 77 if (font_size <= kBoldFontSize) | |
| 78 title_font = title_font.DeriveFont(0, gfx::Font::BOLD); | |
| 79 font = new gfx::Font(title_font); | |
| 80 return *font; | |
| 81 } | |
| 82 | |
| 83 // An image view that is not interactive. | |
| 84 class StaticImageView : public views::ImageView { | |
| 85 public: | |
| 86 StaticImageView() : ImageView() { | |
| 87 } | |
| 88 | |
| 89 private: | |
| 90 // views::View overrides: | |
| 91 virtual bool HitTest(const gfx::Point& l) const OVERRIDE { | |
| 92 return false; | |
| 93 } | |
| 94 | |
| 95 DISALLOW_COPY_AND_ASSIGN(StaticImageView); | |
| 96 }; | |
| 97 | |
| 98 // A minimum title width set by test to override the default logic that derives | |
| 99 // the min width from font. | |
| 100 int g_min_title_width = 0; | |
| 101 | |
| 102 } // namespace | |
| 103 | |
| 104 // static | |
| 105 const char AppListItemView::kViewClassName[] = "ash/app_list/AppListItemView"; | |
| 106 | |
| 107 // AppListItemView::IconOperation wraps background icon processing. | |
| 108 class AppListItemView::IconOperation | |
| 109 : public base::RefCountedThreadSafe<AppListItemView::IconOperation> { | |
| 110 public: | |
| 111 IconOperation(const SkBitmap& bitmap, const gfx::Size& size) | |
| 112 : bitmap_(bitmap), | |
| 113 size_(size) { | |
| 114 } | |
| 115 | |
| 116 static void Run(scoped_refptr<IconOperation> op) { | |
| 117 op->ResizeAndGenerateShadow(); | |
| 118 } | |
| 119 | |
| 120 // Padding space around icon to contain its shadow. Note it should be at least | |
| 121 // the max size of shadow radius + shadow offset in shadow generation code. | |
| 122 static const int kShadowPadding = 15; | |
| 123 | |
| 124 void ResizeAndGenerateShadow() { | |
| 125 // If you change shadow radius and shadow offset, please also update | |
| 126 // kShadowPaddingAbove. | |
| 127 const SkColor kShadowColor[] = { | |
| 128 SkColorSetARGB(0xCC, 0, 0, 0), | |
| 129 SkColorSetARGB(0x33, 0, 0, 0), | |
| 130 SkColorSetARGB(0x4C, 0, 0, 0), | |
| 131 }; | |
| 132 const gfx::Point kShadowOffset[] = { | |
| 133 gfx::Point(0, 0), | |
| 134 gfx::Point(0, 4), | |
| 135 gfx::Point(0, 5), | |
| 136 }; | |
| 137 const SkScalar kShadowRadius[] = { | |
| 138 SkIntToScalar(2), | |
| 139 SkIntToScalar(4), | |
| 140 SkIntToScalar(10), | |
| 141 }; | |
| 142 | |
| 143 if (cancel_flag_.IsSet()) | |
| 144 return; | |
| 145 | |
| 146 if (size_ != gfx::Size(bitmap_.width(), bitmap_.height())) | |
| 147 bitmap_ = SkBitmapOperations::CreateResizedBitmap(bitmap_, size_); | |
| 148 | |
| 149 if (cancel_flag_.IsSet()) | |
| 150 return; | |
| 151 | |
| 152 bitmap_ = SkBitmapOperations::CreateDropShadow( | |
| 153 bitmap_, | |
| 154 arraysize(kShadowColor), | |
| 155 kShadowColor, | |
| 156 kShadowOffset, | |
| 157 kShadowRadius); | |
| 158 } | |
| 159 | |
| 160 void Cancel() { | |
| 161 cancel_flag_.Set(); | |
| 162 } | |
| 163 | |
| 164 const SkBitmap& bitmap() const { | |
| 165 return bitmap_; | |
| 166 } | |
| 167 | |
| 168 private: | |
| 169 friend class base::RefCountedThreadSafe<AppListItemView::IconOperation>; | |
| 170 | |
| 171 base::CancellationFlag cancel_flag_; | |
| 172 | |
| 173 SkBitmap bitmap_; | |
| 174 const gfx::Size size_; | |
| 175 | |
| 176 DISALLOW_COPY_AND_ASSIGN(IconOperation); | |
| 177 }; | |
| 178 | |
| 179 AppListItemView::AppListItemView(AppListModelView* list_model_view, | |
| 180 AppListItemModel* model, | |
| 181 views::ButtonListener* listener) | |
| 182 : CustomButton(listener), | |
| 183 model_(model), | |
| 184 list_model_view_(list_model_view), | |
| 185 icon_(new StaticImageView), | |
| 186 title_(new DropShadowLabel), | |
| 187 selected_(false), | |
| 188 ALLOW_THIS_IN_INITIALIZER_LIST(apply_shadow_factory_(this)) { | |
| 189 title_->SetBackgroundColor(0); | |
| 190 | |
| 191 if (internal::AppList::UseAppListV2()) { | |
| 192 title_->SetEnabledColor(kTitleColorV2); | |
| 193 } else { | |
| 194 title_->SetEnabledColor(kTitleColor); | |
| 195 const gfx::ShadowValue kTitleShadows[] = { | |
| 196 gfx::ShadowValue(gfx::Point(0, 0), 1, SkColorSetARGB(0x66, 0, 0, 0)), | |
| 197 gfx::ShadowValue(gfx::Point(0, 0), 10, SkColorSetARGB(0x66, 0, 0, 0)), | |
| 198 gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x66, 0, 0, 0)), | |
| 199 gfx::ShadowValue(gfx::Point(0, 2), 4, SkColorSetARGB(0x66, 0, 0, 0)), | |
| 200 }; | |
| 201 title_->SetTextShadows(arraysize(kTitleShadows), kTitleShadows); | |
| 202 } | |
| 203 | |
| 204 AddChildView(icon_); | |
| 205 AddChildView(title_); | |
| 206 | |
| 207 ItemIconChanged(); | |
| 208 ItemTitleChanged(); | |
| 209 model_->AddObserver(this); | |
| 210 | |
| 211 set_context_menu_controller(this); | |
| 212 set_request_focus_on_press(false); | |
| 213 set_focusable(true); | |
| 214 } | |
| 215 | |
| 216 AppListItemView::~AppListItemView() { | |
| 217 model_->RemoveObserver(this); | |
| 218 CancelPendingIconOperation(); | |
| 219 } | |
| 220 | |
| 221 // static | |
| 222 gfx::Size AppListItemView::GetPreferredSizeForIconSize( | |
| 223 const gfx::Size& icon_size) { | |
| 224 int min_title_width = g_min_title_width; | |
| 225 // Fixed 20px is used for left/right padding before switching to padding | |
| 226 // based on number of chars. It is also a number used for test case | |
| 227 // AppList.ModelViewCalculateLayout. | |
| 228 int left_right_padding = 20; | |
| 229 if (min_title_width == 0) { | |
| 230 const gfx::Font& title_font = GetTitleFontForIconSize(icon_size); | |
| 231 // Use big char such as 'G' to calculate min title width. | |
| 232 min_title_width = kMinTitleChars * | |
| 233 title_font.GetStringWidth(ASCIIToUTF16("G")); | |
| 234 left_right_padding = kLeftRightPaddingChars * | |
| 235 title_font.GetAverageCharacterWidth(); | |
| 236 } | |
| 237 | |
| 238 int dimension = std::max(icon_size.width() * 2, min_title_width); | |
| 239 gfx::Size size(dimension, dimension); | |
| 240 size.Enlarge(left_right_padding, kTopBottomPadding); | |
| 241 return size; | |
| 242 } | |
| 243 | |
| 244 // static | |
| 245 void AppListItemView::SetMinTitleWidth(int width) { | |
| 246 g_min_title_width = width; | |
| 247 } | |
| 248 | |
| 249 void AppListItemView::SetIconSize(const gfx::Size& size) { | |
| 250 if (icon_size_ == size) | |
| 251 return; | |
| 252 | |
| 253 icon_size_ = size; | |
| 254 title_->SetFont(GetTitleFontForIconSize(size)); | |
| 255 UpdateIcon(); | |
| 256 } | |
| 257 | |
| 258 void AppListItemView::SetSelected(bool selected) { | |
| 259 if (selected == selected_) | |
| 260 return; | |
| 261 | |
| 262 RequestFocus(); | |
| 263 selected_ = selected; | |
| 264 SchedulePaint(); | |
| 265 } | |
| 266 | |
| 267 void AppListItemView::UpdateIcon() { | |
| 268 // Skip if |icon_size_| has not been determined. | |
| 269 if (icon_size_.IsEmpty()) | |
| 270 return; | |
| 271 | |
| 272 SkBitmap icon = model_->icon(); | |
| 273 // Clear icon and bail out if model icon is empty. | |
| 274 if (icon.empty()) { | |
| 275 icon_->SetImage(NULL); | |
| 276 return; | |
| 277 } | |
| 278 | |
| 279 CancelPendingIconOperation(); | |
| 280 | |
| 281 SkBitmap shadow; | |
| 282 if (IconCache::GetInstance()->Get(icon, icon_size_, &shadow)) { | |
| 283 icon_->SetImage(shadow); | |
| 284 } else { | |
| 285 // Schedule resize and shadow generation. | |
| 286 icon_op_ = new IconOperation(icon, icon_size_); | |
| 287 base::WorkerPool::PostTaskAndReply( | |
| 288 FROM_HERE, | |
| 289 base::Bind(&IconOperation::Run, icon_op_), | |
| 290 base::Bind(&AppListItemView::ApplyShadow, | |
| 291 apply_shadow_factory_.GetWeakPtr(), | |
| 292 icon_op_), | |
| 293 true /* task_is_slow */); | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 void AppListItemView::CancelPendingIconOperation() { | |
| 298 // Set canceled flag of previous request to skip unneeded processing. | |
| 299 if (icon_op_.get()) | |
| 300 icon_op_->Cancel(); | |
| 301 | |
| 302 // Cancel reply callback for previous request. | |
| 303 apply_shadow_factory_.InvalidateWeakPtrs(); | |
| 304 } | |
| 305 | |
| 306 void AppListItemView::ApplyShadow(scoped_refptr<IconOperation> op) { | |
| 307 icon_->SetImage(op->bitmap()); | |
| 308 IconCache::GetInstance()->Put(model_->icon(), icon_size_, op->bitmap()); | |
| 309 | |
| 310 DCHECK(op.get() == icon_op_.get()); | |
| 311 icon_op_ = NULL; | |
| 312 } | |
| 313 | |
| 314 void AppListItemView::ItemIconChanged() { | |
| 315 UpdateIcon(); | |
| 316 } | |
| 317 | |
| 318 void AppListItemView::ItemTitleChanged() { | |
| 319 title_->SetText(UTF8ToUTF16(model_->title())); | |
| 320 } | |
| 321 | |
| 322 void AppListItemView::ItemHighlightedChanged() { | |
| 323 SchedulePaint(); | |
| 324 } | |
| 325 | |
| 326 std::string AppListItemView::GetClassName() const { | |
| 327 return kViewClassName; | |
| 328 } | |
| 329 | |
| 330 gfx::Size AppListItemView::GetPreferredSize() { | |
| 331 return GetPreferredSizeForIconSize(icon_size_); | |
| 332 } | |
| 333 | |
| 334 void AppListItemView::Layout() { | |
| 335 gfx::Rect rect(GetContentsBounds()); | |
| 336 | |
| 337 int left_right_padding = kLeftRightPaddingChars * | |
| 338 title_->font().GetAverageCharacterWidth(); | |
| 339 rect.Inset(left_right_padding, kTopBottomPadding); | |
| 340 | |
| 341 gfx::Size title_size = title_->GetPreferredSize(); | |
| 342 int height = icon_size_.height() + kIconTitleSpacing + | |
| 343 title_size.height(); | |
| 344 int y = rect.y() + (rect.height() - height) / 2; | |
| 345 | |
| 346 gfx::Rect icon_bounds(rect.x(), y, rect.width(), icon_size_.height()); | |
| 347 icon_bounds.Inset(0, -IconOperation::kShadowPadding); | |
| 348 icon_->SetBoundsRect(icon_bounds); | |
| 349 | |
| 350 title_->SetBounds(rect.x(), | |
| 351 y + icon_size_.height() + kIconTitleSpacing, | |
| 352 rect.width(), | |
| 353 title_size.height()); | |
| 354 } | |
| 355 | |
| 356 void AppListItemView::OnPaint(gfx::Canvas* canvas) { | |
| 357 gfx::Rect rect(GetContentsBounds()); | |
| 358 | |
| 359 if (model_->highlighted()) { | |
| 360 canvas->FillRect(rect, kHighlightedColor); | |
| 361 } else if (hover_animation_->is_animating()) { | |
| 362 int alpha = SkColorGetA(kHoverAndPushedColor) * | |
| 363 hover_animation_->GetCurrentValue(); | |
| 364 canvas->FillRect(rect, SkColorSetA(kHoverAndPushedColor, alpha)); | |
| 365 } else if (state() == BS_HOT || state() == BS_PUSHED) { | |
| 366 canvas->FillRect(rect, kHoverAndPushedColor); | |
| 367 } else if (selected_) { | |
| 368 canvas->FillRect(rect, kSelectedColor); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 void AppListItemView::GetAccessibleState(ui::AccessibleViewState* state) { | |
| 373 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON; | |
| 374 state->name = UTF8ToUTF16(model_->title()); | |
| 375 } | |
| 376 | |
| 377 void AppListItemView::ShowContextMenuForView(views::View* source, | |
| 378 const gfx::Point& point) { | |
| 379 ui::MenuModel* menu_model = model_->GetContextMenuModel(); | |
| 380 if (!menu_model) | |
| 381 return; | |
| 382 | |
| 383 views::MenuModelAdapter menu_adapter(menu_model); | |
| 384 context_menu_runner_.reset( | |
| 385 new views::MenuRunner(new views::MenuItemView(&menu_adapter))); | |
| 386 menu_adapter.BuildMenu(context_menu_runner_->GetMenu()); | |
| 387 if (context_menu_runner_->RunMenuAt( | |
| 388 GetWidget(), NULL, gfx::Rect(point, gfx::Size()), | |
| 389 views::MenuItemView::TOPLEFT, views::MenuRunner::HAS_MNEMONICS) == | |
| 390 views::MenuRunner::MENU_DELETED) | |
| 391 return; | |
| 392 } | |
| 393 | |
| 394 void AppListItemView::StateChanged() { | |
| 395 if (state() == BS_HOT || state() == BS_PUSHED) { | |
| 396 list_model_view_->SetSelectedItem(this); | |
| 397 } else { | |
| 398 list_model_view_->ClearSelectedItem(this); | |
| 399 model_->SetHighlighted(false); | |
| 400 } | |
| 401 } | |
| 402 | |
| 403 } // namespace ash | |
| OLD | NEW |