apps_grid_view_item.mm revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/memory/scoped_nsobject.h" 9#include "base/strings/sys_string_conversions.h" 10#include "skia/ext/skia_utils_mac.h" 11#include "ui/app_list/app_list_constants.h" 12#include "ui/app_list/app_list_item_model.h" 13#include "ui/app_list/app_list_item_model_observer.h" 14#include "ui/base/resource/resource_bundle.h" 15#include "ui/gfx/image/image_skia_util_mac.h" 16#include "ui/gfx/font.h" 17#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 18 19namespace { 20 21// Padding from the top of the tile to the top of the app icon. 22const CGFloat kTileTopPadding = 10; 23 24} // namespace 25 26namespace app_list { 27 28class ItemModelObserverBridge : public app_list::AppListItemModelObserver { 29 public: 30 ItemModelObserverBridge(AppsGridViewItem* parent, AppListItemModel* model); 31 virtual ~ItemModelObserverBridge(); 32 33 AppListItemModel* model() { return model_; } 34 35 virtual void ItemIconChanged() OVERRIDE; 36 virtual void ItemTitleChanged() OVERRIDE; 37 virtual void ItemHighlightedChanged() OVERRIDE; 38 virtual void ItemIsInstallingChanged() OVERRIDE; 39 virtual void ItemPercentDownloadedChanged() OVERRIDE; 40 41 private: 42 AppsGridViewItem* parent_; // Weak. Owns us. 43 AppListItemModel* model_; // Weak. Owned by AppListModel::Apps. 44 45 DISALLOW_COPY_AND_ASSIGN(ItemModelObserverBridge); 46}; 47 48ItemModelObserverBridge::ItemModelObserverBridge(AppsGridViewItem* parent, 49 AppListItemModel* model) 50 : parent_(parent), 51 model_(model) { 52 model_->AddObserver(this); 53} 54 55ItemModelObserverBridge::~ItemModelObserverBridge() { 56 model_->RemoveObserver(this); 57} 58 59void ItemModelObserverBridge::ItemIconChanged() { 60 [[parent_ button] setImage:gfx::NSImageFromImageSkia(model_->icon())]; 61} 62 63void ItemModelObserverBridge::ItemTitleChanged() { 64 [[parent_ button] setTitle:base::SysUTF8ToNSString(model_->title())]; 65} 66 67void ItemModelObserverBridge::ItemHighlightedChanged() { 68 //TODO(tapted): Ensure the item view is visible (requires pagination). 69} 70 71void ItemModelObserverBridge::ItemIsInstallingChanged() { 72 //TODO(tapted): Hide the title while itemModel->is_installing(). 73} 74 75void ItemModelObserverBridge::ItemPercentDownloadedChanged() { 76 //TODO(tapted): Update install progress bar for this item. 77} 78 79} // namespace app_list 80 81// Container for an NSButton to allow proper alignment of the icon in the apps 82// grid, and to draw with a highlight when selected. 83@interface AppsGridItemBackgroundView : NSView { 84 @private 85 BOOL selected_; 86} 87 88- (NSButton*)button; 89 90- (void)setSelected:(BOOL)flag; 91 92@end 93 94@interface AppsGridItemButtonCell : NSButtonCell { 95 @private 96 BOOL hasShadow_; 97} 98 99@property(assign, nonatomic) BOOL hasShadow; 100 101@end 102 103@interface AppsGridItemButton : NSButton; 104@end 105 106@implementation AppsGridItemBackgroundView 107 108- (NSButton*)button { 109 return base::mac::ObjCCastStrict<NSButton>([[self subviews] objectAtIndex:0]); 110} 111 112- (void)setSelected:(BOOL)flag { 113 if (selected_ == flag) 114 return; 115 116 selected_ = flag; 117 [self setNeedsDisplay:YES]; 118} 119 120// Ignore all hit tests. The grid controller needs to be the owner of any drags. 121- (NSView*)hitTest:(NSPoint)aPoint { 122 return nil; 123} 124 125- (void)drawRect:(NSRect)dirtyRect { 126 if (!selected_) 127 return; 128 129 [gfx::SkColorToCalibratedNSColor(app_list::kSelectedColor) set]; 130 NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver); 131} 132 133- (void)mouseDown:(NSEvent*)theEvent { 134 [[[self button] cell] setHighlighted:YES]; 135} 136 137- (void)mouseDragged:(NSEvent*)theEvent { 138 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] 139 fromView:nil]; 140 BOOL isInView = [self mouse:pointInView inRect:[self bounds]]; 141 [[[self button] cell] setHighlighted:isInView]; 142} 143 144- (void)mouseUp:(NSEvent*)theEvent { 145 NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] 146 fromView:nil]; 147 if (![self mouse:pointInView inRect:[self bounds]]) 148 return; 149 150 [[self button] performClick:self]; 151} 152 153@end 154 155@interface AppsGridViewItem () 156 157- (AppsGridItemBackgroundView*)itemBackgroundView; 158 159@end 160 161@implementation AppsGridViewItem 162 163- (id)initWithSize:(NSSize)tileSize { 164 if ((self = [super init])) { 165 scoped_nsobject<AppsGridItemButton> prototypeButton( 166 [[AppsGridItemButton alloc] initWithFrame:NSMakeRect( 167 0, 0, tileSize.width, tileSize.height - kTileTopPadding)]); 168 169 // This NSButton style always positions the icon at the very top of the 170 // button frame. AppsGridViewItem uses an enclosing view so that it is 171 // visually correct. 172 [prototypeButton setImagePosition:NSImageAbove]; 173 [prototypeButton setButtonType:NSMomentaryChangeButton]; 174 [prototypeButton setBordered:NO]; 175 176 [[prototypeButton cell] 177 setFont:ui::ResourceBundle::GetSharedInstance().GetFont( 178 app_list::kItemTextFontStyle).GetNativeFont()]; 179 [[prototypeButton cell] setLineBreakMode:NSLineBreakByTruncatingTail]; 180 181 scoped_nsobject<AppsGridItemBackgroundView> prototypeButtonBackground( 182 [[AppsGridItemBackgroundView alloc] initWithFrame:NSMakeRect( 183 0, 0, tileSize.width, tileSize.height)]); 184 [prototypeButtonBackground addSubview:prototypeButton]; 185 [self setView:prototypeButtonBackground]; 186 } 187 return self; 188} 189 190- (void)setModel:(app_list::AppListItemModel*)itemModel { 191 if (!itemModel) { 192 observerBridge_.reset(); 193 return; 194 } 195 196 NSButton* button = [self button]; 197 [button setTitle:base::SysUTF8ToNSString(itemModel->title())]; 198 [button setImage:gfx::NSImageFromImageSkia(itemModel->icon())]; 199 [[button cell] setHasShadow:itemModel->has_shadow()]; 200 observerBridge_.reset(new app_list::ItemModelObserverBridge(self, itemModel)); 201 202 if (trackingArea_.get()) 203 [[self view] removeTrackingArea:trackingArea_.get()]; 204 205 trackingArea_.reset( 206 [[CrTrackingArea alloc] initWithRect:NSZeroRect 207 options:NSTrackingInVisibleRect | 208 NSTrackingMouseEnteredAndExited | 209 NSTrackingActiveInKeyWindow 210 owner:self 211 userInfo:nil]); 212 [[self view] addTrackingArea:trackingArea_.get()]; 213} 214 215- (app_list::AppListItemModel*)model { 216 return observerBridge_->model(); 217} 218 219- (NSButton*)button { 220 DCHECK_EQ(1u, [[[self view] subviews] count]); 221 return base::mac::ObjCCastStrict<NSButton>( 222 [[[self view] subviews] objectAtIndex:0]); 223} 224 225- (AppsGridItemBackgroundView*)itemBackgroundView { 226 return base::mac::ObjCCastStrict<AppsGridItemBackgroundView>([self view]); 227} 228 229- (void)mouseEntered:(NSEvent*)theEvent { 230 [self setSelected:YES]; 231} 232 233- (void)mouseExited:(NSEvent*)theEvent { 234 [self setSelected:NO]; 235} 236 237- (void)setSelected:(BOOL)flag { 238 [[self itemBackgroundView] setSelected:flag]; 239 [super setSelected:flag]; 240} 241 242@end 243 244@implementation AppsGridItemButton 245 246+ (Class)cellClass { 247 return [AppsGridItemButtonCell class]; 248} 249 250@end 251 252@implementation AppsGridItemButtonCell 253 254@synthesize hasShadow = hasShadow_; 255 256- (void)drawImage:(NSImage*)image 257 withFrame:(NSRect)frame 258 inView:(NSView*)controlView { 259 if (!hasShadow_) { 260 [super drawImage:image 261 withFrame:frame 262 inView:controlView]; 263 return; 264 } 265 266 scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]); 267 gfx::ScopedNSGraphicsContextSaveGState context; 268 [shadow setShadowOffset:NSMakeSize(0, -2)]; 269 [shadow setShadowBlurRadius:2.0]; 270 [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0 271 alpha:0.14]]; 272 [shadow set]; 273 274 [super drawImage:image 275 withFrame:frame 276 inView:controlView]; 277} 278 279@end 280