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 |