gpu: Tweak Android WebGL test expectations
[chromium-blink-merge.git] / ui / app_list / cocoa / apps_grid_view_item.mm
blob34fe975ebcac513a775a51f9719d5694ac154945
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.
5 #import "ui/app_list/cocoa/apps_grid_view_item.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/mac_util.h"
9 #include "base/mac/scoped_nsobject.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "skia/ext/skia_utils_mac.h"
12 #include "ui/app_list/app_list_constants.h"
13 #include "ui/app_list/app_list_item_model.h"
14 #include "ui/app_list/app_list_item_model_observer.h"
15 #import "ui/app_list/cocoa/apps_grid_controller.h"
16 #import "ui/base/cocoa/menu_controller.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/gfx/font.h"
19 #include "ui/gfx/image/image_skia_operations.h"
20 #include "ui/gfx/image/image_skia_util_mac.h"
21 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
23 namespace {
25 // Padding from the top of the tile to the top of the app icon.
26 const CGFloat kTileTopPadding = 10;
28 const CGFloat kIconSize = 48;
30 const CGFloat kProgressBarHorizontalPadding = 8;
31 const CGFloat kProgressBarVerticalPadding = 13;
33 // On Mac, fonts of the same enum from ResourceBundle are larger. The smallest
34 // enum is already used, so it needs to be reduced further to match Windows.
35 const int kMacFontSizeDelta = -1;
37 }  // namespace
39 @class AppsGridItemBackgroundView;
41 @interface AppsGridViewItem ()
43 // Typed accessor for the root view.
44 - (AppsGridItemBackgroundView*)itemBackgroundView;
46 // Bridged methods from app_list::AppListItemModelObserver:
47 // Update the title, correctly setting the color if the button is highlighted.
48 - (void)updateButtonTitle;
50 // Update the button image after ensuring its dimensions are |kIconSize|.
51 - (void)updateButtonImage;
53 // Ensure the page this item is on is the visible page in the grid.
54 - (void)ensureVisible;
56 // Add or remove a progress bar from the view.
57 - (void)setItemIsInstalling:(BOOL)isInstalling;
59 // Update the progress bar to represent |percent|, or make it indeterminate if
60 // |percent| is -1, when unpacking begins.
61 - (void)setPercentDownloaded:(int)percent;
63 @end
65 namespace app_list {
67 class ItemModelObserverBridge : public app_list::AppListItemModelObserver {
68  public:
69   ItemModelObserverBridge(AppsGridViewItem* parent, AppListItemModel* model);
70   virtual ~ItemModelObserverBridge();
72   AppListItemModel* model() { return model_; }
73   NSMenu* GetContextMenu();
75   virtual void ItemIconChanged() OVERRIDE;
76   virtual void ItemTitleChanged() OVERRIDE;
77   virtual void ItemHighlightedChanged() OVERRIDE;
78   virtual void ItemIsInstallingChanged() OVERRIDE;
79   virtual void ItemPercentDownloadedChanged() OVERRIDE;
81  private:
82   AppsGridViewItem* parent_;  // Weak. Owns us.
83   AppListItemModel* model_;  // Weak. Owned by AppListModel::Apps.
84   base::scoped_nsobject<MenuController> context_menu_controller_;
86   DISALLOW_COPY_AND_ASSIGN(ItemModelObserverBridge);
89 ItemModelObserverBridge::ItemModelObserverBridge(AppsGridViewItem* parent,
90                                                  AppListItemModel* model)
91     : parent_(parent),
92       model_(model) {
93   model_->AddObserver(this);
96 ItemModelObserverBridge::~ItemModelObserverBridge() {
97   model_->RemoveObserver(this);
100 NSMenu* ItemModelObserverBridge::GetContextMenu() {
101   if (!context_menu_controller_) {
102     context_menu_controller_.reset(
103         [[MenuController alloc] initWithModel:model_->GetContextMenuModel()
104                        useWithPopUpButtonCell:NO]);
105   }
106   return [context_menu_controller_ menu];
109 void ItemModelObserverBridge::ItemIconChanged() {
110   [parent_ updateButtonImage];
113 void ItemModelObserverBridge::ItemTitleChanged() {
114   [parent_ updateButtonTitle];
117 void ItemModelObserverBridge::ItemHighlightedChanged() {
118   if (model_->highlighted())
119     [parent_ ensureVisible];
122 void ItemModelObserverBridge::ItemIsInstallingChanged() {
123   [parent_ setItemIsInstalling:model_->is_installing()];
126 void ItemModelObserverBridge::ItemPercentDownloadedChanged() {
127   [parent_ setPercentDownloaded:model_->percent_downloaded()];
130 }  // namespace app_list
132 // Container for an NSButton to allow proper alignment of the icon in the apps
133 // grid, and to draw with a highlight when selected.
134 @interface AppsGridItemBackgroundView : NSView {
135  @private
136   BOOL selected_;
139 - (NSButton*)button;
141 - (void)setSelected:(BOOL)flag;
143 @end
145 @interface AppsGridItemButtonCell : NSButtonCell {
146  @private
147   BOOL hasShadow_;
150 @property(assign, nonatomic) BOOL hasShadow;
152 @end
154 @interface AppsGridItemButton : NSButton;
155 @end
157 @implementation AppsGridItemBackgroundView
159 - (NSButton*)button {
160   // These views are part of a prototype NSCollectionViewItem, copied with an
161   // NSCoder. Rather than encoding additional members, the following relies on
162   // the button always being the first item added to AppsGridItemBackgroundView.
163   return base::mac::ObjCCastStrict<NSButton>([[self subviews] objectAtIndex:0]);
166 - (void)setSelected:(BOOL)flag {
167   DCHECK(selected_ != flag);
168   selected_ = flag;
169   [self setNeedsDisplay:YES];
172 // Ignore all hit tests. The grid controller needs to be the owner of any drags.
173 - (NSView*)hitTest:(NSPoint)aPoint {
174   return nil;
177 - (void)drawRect:(NSRect)dirtyRect {
178   if (!selected_)
179     return;
181   [gfx::SkColorToSRGBNSColor(app_list::kSelectedColor) set];
182   NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
185 - (void)mouseDown:(NSEvent*)theEvent {
186   [[[self button] cell] setHighlighted:YES];
189 - (void)mouseDragged:(NSEvent*)theEvent {
190   NSPoint pointInView = [self convertPoint:[theEvent locationInWindow]
191                                   fromView:nil];
192   BOOL isInView = [self mouse:pointInView inRect:[self bounds]];
193   [[[self button] cell] setHighlighted:isInView];
196 - (void)mouseUp:(NSEvent*)theEvent {
197   NSPoint pointInView = [self convertPoint:[theEvent locationInWindow]
198                                   fromView:nil];
199   if (![self mouse:pointInView inRect:[self bounds]])
200     return;
202   [[self button] performClick:self];
205 @end
207 @implementation AppsGridViewItem
209 - (id)initWithSize:(NSSize)tileSize {
210   if ((self = [super init])) {
211     base::scoped_nsobject<AppsGridItemButton> prototypeButton(
212         [[AppsGridItemButton alloc] initWithFrame:NSMakeRect(
213             0, 0, tileSize.width, tileSize.height - kTileTopPadding)]);
215     // This NSButton style always positions the icon at the very top of the
216     // button frame. AppsGridViewItem uses an enclosing view so that it is
217     // visually correct.
218     [prototypeButton setImagePosition:NSImageAbove];
219     [prototypeButton setButtonType:NSMomentaryChangeButton];
220     [prototypeButton setBordered:NO];
222     base::scoped_nsobject<AppsGridItemBackgroundView> prototypeButtonBackground(
223         [[AppsGridItemBackgroundView alloc]
224             initWithFrame:NSMakeRect(0, 0, tileSize.width, tileSize.height)]);
225     [prototypeButtonBackground addSubview:prototypeButton];
226     [self setView:prototypeButtonBackground];
227   }
228   return self;
231 - (NSProgressIndicator*)progressIndicator {
232   return progressIndicator_;
235 - (void)updateButtonTitle {
236   if (progressIndicator_)
237     return;
239   base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
240       [[NSMutableParagraphStyle alloc] init]);
241   [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
242   [paragraphStyle setAlignment:NSCenterTextAlignment];
243   NSDictionary* titleAttributes = @{
244     NSParagraphStyleAttributeName : paragraphStyle,
245     NSFontAttributeName : ui::ResourceBundle::GetSharedInstance()
246         .GetFont(app_list::kItemTextFontStyle)
247         .DeriveFont(kMacFontSizeDelta)
248         .GetNativeFont(),
249     NSForegroundColorAttributeName : [self isSelected] ?
250         gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor) :
251         gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor)
252   };
253   NSString* buttonTitle = base::SysUTF8ToNSString([self model]->title());
254   base::scoped_nsobject<NSAttributedString> attributedTitle(
255       [[NSAttributedString alloc] initWithString:buttonTitle
256                                       attributes:titleAttributes]);
257   [[self button] setAttributedTitle:attributedTitle];
260 - (void)updateButtonImage {
261   const gfx::Size iconSize = gfx::Size(kIconSize, kIconSize);
262   gfx::ImageSkia icon = [self model]->icon();
263   if (icon.size() != iconSize) {
264     icon = gfx::ImageSkiaOperations::CreateResizedImage(
265         icon, skia::ImageOperations::RESIZE_BEST, iconSize);
266   }
267   NSImage* buttonImage = gfx::NSImageFromImageSkiaWithColorSpace(
268       icon, base::mac::GetSRGBColorSpace());
269   [[self button] setImage:buttonImage];
270   [[[self button] cell] setHasShadow:[self model]->has_shadow()];
273 - (void)setModel:(app_list::AppListItemModel*)itemModel {
274   if (!itemModel) {
275     observerBridge_.reset();
276     return;
277   }
279   observerBridge_.reset(new app_list::ItemModelObserverBridge(self, itemModel));
280   [self updateButtonTitle];
281   [self updateButtonImage];
283   if (trackingArea_.get())
284     [[self view] removeTrackingArea:trackingArea_.get()];
286   trackingArea_.reset(
287       [[CrTrackingArea alloc] initWithRect:NSZeroRect
288                                    options:NSTrackingInVisibleRect |
289                                            NSTrackingMouseEnteredAndExited |
290                                            NSTrackingActiveInKeyWindow
291                                      owner:self
292                                   userInfo:nil]);
293   [[self view] addTrackingArea:trackingArea_.get()];
296 - (app_list::AppListItemModel*)model {
297   return observerBridge_->model();
300 - (NSButton*)button {
301   return [[self itemBackgroundView] button];
304 - (NSMenu*)contextMenu {
305   [self setSelected:YES];
306   return observerBridge_->GetContextMenu();
309 - (NSBitmapImageRep*)dragRepresentationForRestore:(BOOL)isRestore {
310   NSButton* button = [self button];
311   NSView* itemView = [self view];
313   // The snapshot is never drawn as if it was selected. Also remove the cell
314   // highlight on the button image, added when it was clicked.
315   [button setHidden:NO];
316   [[button cell] setHighlighted:NO];
317   [self setSelected:NO];
318   [progressIndicator_ setHidden:YES];
319   if (isRestore)
320     [self updateButtonTitle];
321   else
322     [button setTitle:@""];
324   NSBitmapImageRep* imageRep =
325       [itemView bitmapImageRepForCachingDisplayInRect:[itemView visibleRect]];
326   [itemView cacheDisplayInRect:[itemView visibleRect]
327               toBitmapImageRep:imageRep];
329   if (isRestore) {
330     [progressIndicator_ setHidden:NO];
331     [self setSelected:YES];
332   }
333   // Button is always hidden until the drag animation completes.
334   [button setHidden:YES];
335   return imageRep;
338 - (void)onInitialModelBuilt {
339   if ([self model]->highlighted()) {
340     [self ensureVisible];
341     if (![self model]->is_installing())
342       [self setSelected:YES];
343   }
346 - (void)ensureVisible {
347   NSCollectionView* collectionView = [self collectionView];
348   AppsGridController* gridController =
349       base::mac::ObjCCastStrict<AppsGridController>([collectionView delegate]);
350   size_t pageIndex = [gridController pageIndexForCollectionView:collectionView];
351   [gridController scrollToPage:pageIndex];
354 - (void)setItemIsInstalling:(BOOL)isInstalling {
355   if (!isInstalling == !progressIndicator_)
356     return;
358   [self ensureVisible];
359   if (!isInstalling) {
360     [progressIndicator_ removeFromSuperview];
361     progressIndicator_.reset();
362     [self updateButtonTitle];
363     [self setSelected:YES];
364     return;
365   }
367   NSRect rect = NSMakeRect(
368       kProgressBarHorizontalPadding,
369       kProgressBarVerticalPadding,
370       NSWidth([[self view] bounds]) - 2 * kProgressBarHorizontalPadding,
371       NSProgressIndicatorPreferredAquaThickness);
372   [[self button] setTitle:@""];
373   progressIndicator_.reset([[NSProgressIndicator alloc] initWithFrame:rect]);
374   [progressIndicator_ setIndeterminate:NO];
375   [progressIndicator_ setControlSize:NSSmallControlSize];
376   [[self view] addSubview:progressIndicator_];
379 - (void)setPercentDownloaded:(int)percent {
380   // In a corner case, items can be installing when they are first added. For
381   // those, the icon will start desaturated. Wait for a progress update before
382   // showing the progress bar.
383   [self setItemIsInstalling:YES];
384   if (percent != -1) {
385     [progressIndicator_ setDoubleValue:percent];
386     return;
387   }
389   // Otherwise, fully downloaded and waiting for install to complete.
390   [progressIndicator_ setIndeterminate:YES];
391   [progressIndicator_ startAnimation:self];
394 - (AppsGridItemBackgroundView*)itemBackgroundView {
395   return base::mac::ObjCCastStrict<AppsGridItemBackgroundView>([self view]);
398 - (void)mouseEntered:(NSEvent*)theEvent {
399   [self setSelected:YES];
402 - (void)mouseExited:(NSEvent*)theEvent {
403   [self setSelected:NO];
406 - (void)setSelected:(BOOL)flag {
407   if ([self isSelected] == flag)
408     return;
410   [[self itemBackgroundView] setSelected:flag];
411   [super setSelected:flag];
412   [self updateButtonTitle];
415 @end
417 @implementation AppsGridItemButton
419 + (Class)cellClass {
420   return [AppsGridItemButtonCell class];
423 @end
425 @implementation AppsGridItemButtonCell
427 @synthesize hasShadow = hasShadow_;
429 - (void)drawImage:(NSImage*)image
430         withFrame:(NSRect)frame
431            inView:(NSView*)controlView {
432   if (!hasShadow_) {
433     [super drawImage:image
434            withFrame:frame
435               inView:controlView];
436     return;
437   }
439   base::scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
440   gfx::ScopedNSGraphicsContextSaveGState context;
441   [shadow setShadowOffset:NSMakeSize(0, -2)];
442   [shadow setShadowBlurRadius:2.0];
443   [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0
444                                                      alpha:0.14]];
445   [shadow set];
447   [super drawImage:image
448          withFrame:frame
449             inView:controlView];
452 @end