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