Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: ui/app_list/cocoa/apps_grid_view_item.mm

Issue 2131463002: Purge the App Launcher code from Mac (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Zap mac-specific icon assets Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 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 #import "ui/app_list/cocoa/apps_grid_view_item.h"
6
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/mac_util.h"
9 #include "base/mac/scoped_nsobject.h"
10 #include "base/macros.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "skia/ext/skia_utils_mac.h"
13 #include "ui/app_list/app_list_constants.h"
14 #include "ui/app_list/app_list_item.h"
15 #include "ui/app_list/app_list_item_observer.h"
16 #import "ui/app_list/cocoa/apps_grid_controller.h"
17 #import "ui/base/cocoa/menu_controller.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/font_list.h"
20 #include "ui/gfx/image/image_skia_operations.h"
21 #include "ui/gfx/image/image_skia_util_mac.h"
22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
23
24 namespace {
25
26 // Padding from the top of the tile to the top of the app icon.
27 const CGFloat kTileTopPadding = 10;
28
29 const CGFloat kIconSize = 48;
30
31 const CGFloat kProgressBarHorizontalPadding = 8;
32 const CGFloat kProgressBarVerticalPadding = 13;
33
34 // On Mac, fonts of the same enum from ResourceBundle are larger. The smallest
35 // enum is already used, so it needs to be reduced further to match Windows.
36 const int kMacFontSizeDelta = -1;
37
38 } // namespace
39
40 @class AppsGridItemBackgroundView;
41
42 @interface AppsGridViewItem ()
43
44 // Typed accessor for the root view.
45 - (AppsGridItemBackgroundView*)itemBackgroundView;
46
47 // Bridged methods from app_list::AppListItemObserver:
48 // Update the title, correctly setting the color if the button is highlighted.
49 - (void)updateButtonTitle;
50
51 // Update the button image after ensuring its dimensions are |kIconSize|.
52 - (void)updateButtonImage;
53
54 // Add or remove a progress bar from the view.
55 - (void)setItemIsInstalling:(BOOL)isInstalling;
56
57 // Update the progress bar to represent |percent|, or make it indeterminate if
58 // |percent| is -1, when unpacking begins.
59 - (void)setPercentDownloaded:(int)percent;
60
61 @end
62
63 namespace app_list {
64
65 class ItemModelObserverBridge : public app_list::AppListItemObserver {
66 public:
67 ItemModelObserverBridge(AppsGridViewItem* parent, AppListItem* model);
68 ~ItemModelObserverBridge() override;
69
70 AppListItem* model() { return model_; }
71 NSMenu* GetContextMenu();
72
73 void ItemIconChanged() override;
74 void ItemNameChanged() override;
75 void ItemIsInstallingChanged() override;
76 void ItemPercentDownloadedChanged() override;
77
78 private:
79 AppsGridViewItem* parent_; // Weak. Owns us.
80 AppListItem* model_; // Weak. Owned by AppListModel.
81 base::scoped_nsobject<MenuController> context_menu_controller_;
82
83 DISALLOW_COPY_AND_ASSIGN(ItemModelObserverBridge);
84 };
85
86 ItemModelObserverBridge::ItemModelObserverBridge(AppsGridViewItem* parent,
87 AppListItem* model)
88 : parent_(parent),
89 model_(model) {
90 model_->AddObserver(this);
91 }
92
93 ItemModelObserverBridge::~ItemModelObserverBridge() {
94 model_->RemoveObserver(this);
95 }
96
97 NSMenu* ItemModelObserverBridge::GetContextMenu() {
98 if (!context_menu_controller_) {
99 ui::MenuModel* menu_model = model_->GetContextMenuModel();
100 if (!menu_model)
101 return nil;
102
103 context_menu_controller_.reset(
104 [[MenuController alloc] initWithModel:menu_model
105 useWithPopUpButtonCell:NO]);
106 }
107 return [context_menu_controller_ menu];
108 }
109
110 void ItemModelObserverBridge::ItemIconChanged() {
111 [parent_ updateButtonImage];
112 }
113
114 void ItemModelObserverBridge::ItemNameChanged() {
115 [parent_ updateButtonTitle];
116 }
117
118 void ItemModelObserverBridge::ItemIsInstallingChanged() {
119 [parent_ setItemIsInstalling:model_->is_installing()];
120 }
121
122 void ItemModelObserverBridge::ItemPercentDownloadedChanged() {
123 [parent_ setPercentDownloaded:model_->percent_downloaded()];
124 }
125
126 } // namespace app_list
127
128 // Container for an NSButton to allow proper alignment of the icon in the apps
129 // grid, and to draw with a highlight when selected.
130 @interface AppsGridItemBackgroundView : NSView {
131 @private
132 BOOL selected_;
133 }
134
135 - (NSButton*)button;
136
137 - (void)setSelected:(BOOL)flag;
138
139 @end
140
141 @interface AppsGridItemButtonCell : NSButtonCell {
142 @private
143 BOOL hasShadow_;
144 }
145
146 @property(assign, nonatomic) BOOL hasShadow;
147
148 @end
149
150 @interface AppsGridItemButton : NSButton;
151 @end
152
153 @implementation AppsGridItemBackgroundView
154
155 - (NSButton*)button {
156 // These views are part of a prototype NSCollectionViewItem, copied with an
157 // NSCoder. Rather than encoding additional members, the following relies on
158 // the button always being the first item added to AppsGridItemBackgroundView.
159 return base::mac::ObjCCastStrict<NSButton>([[self subviews] objectAtIndex:0]);
160 }
161
162 - (void)setSelected:(BOOL)flag {
163 DCHECK(selected_ != flag);
164 selected_ = flag;
165 [self setNeedsDisplay:YES];
166 }
167
168 // Ignore all hit tests. The grid controller needs to be the owner of any drags.
169 - (NSView*)hitTest:(NSPoint)aPoint {
170 return nil;
171 }
172
173 - (void)drawRect:(NSRect)dirtyRect {
174 if (!selected_)
175 return;
176
177 [skia::SkColorToSRGBNSColor(app_list::kSelectedColor) set];
178 NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
179 }
180
181 - (void)mouseDown:(NSEvent*)theEvent {
182 [[[self button] cell] setHighlighted:YES];
183 }
184
185 - (void)mouseDragged:(NSEvent*)theEvent {
186 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow]
187 fromView:nil];
188 BOOL isInView = [self mouse:pointInView inRect:[self bounds]];
189 [[[self button] cell] setHighlighted:isInView];
190 }
191
192 - (void)mouseUp:(NSEvent*)theEvent {
193 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow]
194 fromView:nil];
195 if (![self mouse:pointInView inRect:[self bounds]])
196 return;
197
198 [[self button] performClick:self];
199 }
200
201 @end
202
203 @implementation AppsGridViewItem
204
205 - (id)initWithSize:(NSSize)tileSize {
206 if ((self = [super init])) {
207 base::scoped_nsobject<AppsGridItemButton> prototypeButton(
208 [[AppsGridItemButton alloc] initWithFrame:NSMakeRect(
209 0, 0, tileSize.width, tileSize.height - kTileTopPadding)]);
210
211 // This NSButton style always positions the icon at the very top of the
212 // button frame. AppsGridViewItem uses an enclosing view so that it is
213 // visually correct.
214 [prototypeButton setImagePosition:NSImageAbove];
215 [prototypeButton setButtonType:NSMomentaryChangeButton];
216 [prototypeButton setBordered:NO];
217
218 base::scoped_nsobject<AppsGridItemBackgroundView> prototypeButtonBackground(
219 [[AppsGridItemBackgroundView alloc]
220 initWithFrame:NSMakeRect(0, 0, tileSize.width, tileSize.height)]);
221 [prototypeButtonBackground addSubview:prototypeButton];
222 [self setView:prototypeButtonBackground];
223 }
224 return self;
225 }
226
227 - (NSProgressIndicator*)progressIndicator {
228 return progressIndicator_;
229 }
230
231 - (void)updateButtonTitle {
232 if (progressIndicator_)
233 return;
234
235 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
236 [[NSMutableParagraphStyle alloc] init]);
237 [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
238 [paragraphStyle setAlignment:NSCenterTextAlignment];
239 NSDictionary* titleAttributes = @{
240 NSParagraphStyleAttributeName : paragraphStyle,
241 NSFontAttributeName : ui::ResourceBundle::GetSharedInstance()
242 .GetFontList(app_list::kItemTextFontStyle)
243 .DeriveWithSizeDelta(kMacFontSizeDelta)
244 .GetPrimaryFont()
245 .GetNativeFont(),
246 NSForegroundColorAttributeName :
247 skia::SkColorToSRGBNSColor(app_list::kGridTitleColor)
248 };
249 NSString* buttonTitle =
250 base::SysUTF8ToNSString([self model]->GetDisplayName());
251 base::scoped_nsobject<NSAttributedString> attributedTitle(
252 [[NSAttributedString alloc] initWithString:buttonTitle
253 attributes:titleAttributes]);
254 [[self button] setAttributedTitle:attributedTitle];
255
256 // If the display name would be truncated in the NSButton, or if the display
257 // name differs from the full name, add a tooltip showing the full name.
258 NSRect titleRect =
259 [[[self button] cell] titleRectForBounds:[[self button] bounds]];
260 if ([self model]->name() == [self model]->GetDisplayName() &&
261 [attributedTitle size].width < NSWidth(titleRect)) {
262 [[self view] removeAllToolTips];
263 } else {
264 [[self view] setToolTip:base::SysUTF8ToNSString([self model]->name())];
265 }
266 }
267
268 - (void)updateButtonImage {
269 const gfx::Size iconSize = gfx::Size(kIconSize, kIconSize);
270 gfx::ImageSkia icon = [self model]->icon();
271 if (icon.size() != iconSize) {
272 icon = gfx::ImageSkiaOperations::CreateResizedImage(
273 icon, skia::ImageOperations::RESIZE_BEST, iconSize);
274 }
275 NSImage* buttonImage = gfx::NSImageFromImageSkiaWithColorSpace(
276 icon, base::mac::GetSRGBColorSpace());
277 [[self button] setImage:buttonImage];
278 [[[self button] cell] setHasShadow:true];
279 }
280
281 - (void)setModel:(app_list::AppListItem*)itemModel {
282 [trackingArea_.get() clearOwner];
283 if (!itemModel) {
284 observerBridge_.reset();
285 return;
286 }
287
288 observerBridge_.reset(new app_list::ItemModelObserverBridge(self, itemModel));
289 [self updateButtonTitle];
290 [self updateButtonImage];
291
292 if (trackingArea_.get())
293 [[self view] removeTrackingArea:trackingArea_.get()];
294
295 trackingArea_.reset(
296 [[CrTrackingArea alloc] initWithRect:NSZeroRect
297 options:NSTrackingInVisibleRect |
298 NSTrackingMouseEnteredAndExited |
299 NSTrackingActiveInKeyWindow
300 owner:self
301 userInfo:nil]);
302 [[self view] addTrackingArea:trackingArea_.get()];
303 }
304
305 - (app_list::AppListItem*)model {
306 return observerBridge_->model();
307 }
308
309 - (NSButton*)button {
310 return [[self itemBackgroundView] button];
311 }
312
313 - (NSMenu*)contextMenu {
314 // Don't show the menu if button is already held down, e.g. with a left-click.
315 if ([[[self button] cell] isHighlighted])
316 return nil;
317
318 [self setSelected:YES];
319 return observerBridge_->GetContextMenu();
320 }
321
322 - (NSBitmapImageRep*)dragRepresentationForRestore:(BOOL)isRestore {
323 NSButton* button = [self button];
324 NSView* itemView = [self view];
325
326 // The snapshot is never drawn as if it was selected. Also remove the cell
327 // highlight on the button image, added when it was clicked.
328 [button setHidden:NO];
329 [[button cell] setHighlighted:NO];
330 [self setSelected:NO];
331 [progressIndicator_ setHidden:YES];
332 if (isRestore)
333 [self updateButtonTitle];
334 else
335 [button setTitle:@""];
336
337 NSBitmapImageRep* imageRep =
338 [itemView bitmapImageRepForCachingDisplayInRect:[itemView visibleRect]];
339 [itemView cacheDisplayInRect:[itemView visibleRect]
340 toBitmapImageRep:imageRep];
341
342 if (isRestore) {
343 [progressIndicator_ setHidden:NO];
344 [self setSelected:YES];
345 }
346 // Button is always hidden until the drag animation completes.
347 [button setHidden:YES];
348 return imageRep;
349 }
350
351 - (void)setItemIsInstalling:(BOOL)isInstalling {
352 if (!isInstalling == !progressIndicator_)
353 return;
354
355 if (!isInstalling) {
356 [progressIndicator_ removeFromSuperview];
357 progressIndicator_.reset();
358 [self updateButtonTitle];
359 [self setSelected:YES];
360 return;
361 }
362
363 NSRect rect = NSMakeRect(
364 kProgressBarHorizontalPadding,
365 kProgressBarVerticalPadding,
366 NSWidth([[self view] bounds]) - 2 * kProgressBarHorizontalPadding,
367 NSProgressIndicatorPreferredAquaThickness);
368 [[self button] setTitle:@""];
369 progressIndicator_.reset([[NSProgressIndicator alloc] initWithFrame:rect]);
370 [progressIndicator_ setIndeterminate:NO];
371 [progressIndicator_ setControlSize:NSSmallControlSize];
372 [[self view] addSubview:progressIndicator_];
373 }
374
375 - (void)setPercentDownloaded:(int)percent {
376 // In a corner case, items can be installing when they are first added. For
377 // those, the icon will start desaturated. Wait for a progress update before
378 // showing the progress bar.
379 [self setItemIsInstalling:YES];
380 if (percent != -1) {
381 [progressIndicator_ setDoubleValue:percent];
382 return;
383 }
384
385 // Otherwise, fully downloaded and waiting for install to complete.
386 [progressIndicator_ setIndeterminate:YES];
387 [progressIndicator_ startAnimation:self];
388 }
389
390 - (AppsGridItemBackgroundView*)itemBackgroundView {
391 return base::mac::ObjCCastStrict<AppsGridItemBackgroundView>([self view]);
392 }
393
394 - (void)mouseEntered:(NSEvent*)theEvent {
395 [self setSelected:YES];
396 }
397
398 - (void)mouseExited:(NSEvent*)theEvent {
399 [self setSelected:NO];
400 }
401
402 - (void)setSelected:(BOOL)flag {
403 if ([self isSelected] == flag)
404 return;
405
406 [[self itemBackgroundView] setSelected:flag];
407 [super setSelected:flag];
408 [self updateButtonTitle];
409 }
410
411 @end
412
413 @implementation AppsGridItemButton
414
415 + (Class)cellClass {
416 return [AppsGridItemButtonCell class];
417 }
418
419 @end
420
421 @implementation AppsGridItemButtonCell
422
423 @synthesize hasShadow = hasShadow_;
424
425 - (void)drawImage:(NSImage*)image
426 withFrame:(NSRect)frame
427 inView:(NSView*)controlView {
428 if (!hasShadow_) {
429 [super drawImage:image
430 withFrame:frame
431 inView:controlView];
432 return;
433 }
434
435 base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
436 gfx::ScopedNSGraphicsContextSaveGState context;
437 [shadow setShadowOffset:NSMakeSize(0, -2)];
438 [shadow setShadowBlurRadius:2.0];
439 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0
440 alpha:0.14]];
441 [shadow set];
442
443 [super drawImage:image
444 withFrame:frame
445 inView:controlView];
446 }
447
448 // Workaround for http://crbug.com/324365: AppKit in Mavericks tries to call
449 // - [NSButtonCell item] when inspecting accessibility. Without this, an
450 // unrecognized selector exception is thrown inside AppKit, crashing Chrome.
451 - (id)item {
452 return nil;
453 }
454
455 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698