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

Side by Side Diff: chrome/browser/ui/cocoa/throbber_view.mm

Issue 6362007: [Mac] Organize all tab/tab strip files into chrome/browser/ui/cocoa/tabs/.... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2009 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 "chrome/browser/ui/cocoa/throbber_view.h"
6
7 #include <set>
8
9 #include "base/logging.h"
10
11 static const float kAnimationIntervalSeconds = 0.03; // 30ms, same as windows
12
13 @interface ThrobberView(PrivateMethods)
14 - (id)initWithFrame:(NSRect)frame delegate:(id<ThrobberDataDelegate>)delegate;
15 - (void)maintainTimer;
16 - (void)animate;
17 @end
18
19 @protocol ThrobberDataDelegate <NSObject>
20 // Is the current frame the last frame of the animation?
21 - (BOOL)animationIsComplete;
22
23 // Draw the current frame into the current graphics context.
24 - (void)drawFrameInRect:(NSRect)rect;
25
26 // Update the frame counter.
27 - (void)advanceFrame;
28 @end
29
30 @interface ThrobberFilmstripDelegate : NSObject
31 <ThrobberDataDelegate> {
32 scoped_nsobject<NSImage> image_;
33 unsigned int numFrames_; // Number of frames in this animation.
34 unsigned int animationFrame_; // Current frame of the animation,
35 // [0..numFrames_)
36 }
37
38 - (id)initWithImage:(NSImage*)image;
39
40 @end
41
42 @implementation ThrobberFilmstripDelegate
43
44 - (id)initWithImage:(NSImage*)image {
45 if ((self = [super init])) {
46 // Reset the animation counter so there's no chance we are off the end.
47 animationFrame_ = 0;
48
49 // Ensure that the height divides evenly into the width. Cache the
50 // number of frames in the animation for later.
51 NSSize imageSize = [image size];
52 DCHECK(imageSize.height && imageSize.width);
53 if (!imageSize.height)
54 return nil;
55 DCHECK((int)imageSize.width % (int)imageSize.height == 0);
56 numFrames_ = (int)imageSize.width / (int)imageSize.height;
57 DCHECK(numFrames_);
58 image_.reset([image retain]);
59 }
60 return self;
61 }
62
63 - (BOOL)animationIsComplete {
64 return NO;
65 }
66
67 - (void)drawFrameInRect:(NSRect)rect {
68 float imageDimension = [image_ size].height;
69 float xOffset = animationFrame_ * imageDimension;
70 NSRect sourceImageRect =
71 NSMakeRect(xOffset, 0, imageDimension, imageDimension);
72 [image_ drawInRect:rect
73 fromRect:sourceImageRect
74 operation:NSCompositeSourceOver
75 fraction:1.0];
76 }
77
78 - (void)advanceFrame {
79 animationFrame_ = ++animationFrame_ % numFrames_;
80 }
81
82 @end
83
84 @interface ThrobberToastDelegate : NSObject
85 <ThrobberDataDelegate> {
86 scoped_nsobject<NSImage> image1_;
87 scoped_nsobject<NSImage> image2_;
88 NSSize image1Size_;
89 NSSize image2Size_;
90 int animationFrame_; // Current frame of the animation,
91 }
92
93 - (id)initWithImage1:(NSImage*)image1 image2:(NSImage*)image2;
94
95 @end
96
97 @implementation ThrobberToastDelegate
98
99 - (id)initWithImage1:(NSImage*)image1 image2:(NSImage*)image2 {
100 if ((self = [super init])) {
101 image1_.reset([image1 retain]);
102 image2_.reset([image2 retain]);
103 image1Size_ = [image1 size];
104 image2Size_ = [image2 size];
105 animationFrame_ = 0;
106 }
107 return self;
108 }
109
110 - (BOOL)animationIsComplete {
111 if (animationFrame_ >= image1Size_.height + image2Size_.height)
112 return YES;
113
114 return NO;
115 }
116
117 // From [0..image1Height) we draw image1, at image1Height we draw nothing, and
118 // from [image1Height+1..image1Hight+image2Height] we draw the second image.
119 - (void)drawFrameInRect:(NSRect)rect {
120 NSImage* image = nil;
121 NSSize srcSize;
122 NSRect destRect;
123
124 if (animationFrame_ < image1Size_.height) {
125 image = image1_.get();
126 srcSize = image1Size_;
127 destRect = NSMakeRect(0, -animationFrame_,
128 image1Size_.width, image1Size_.height);
129 } else if (animationFrame_ == image1Size_.height) {
130 // nothing; intermediate blank frame
131 } else {
132 image = image2_.get();
133 srcSize = image2Size_;
134 destRect = NSMakeRect(0, animationFrame_ -
135 (image1Size_.height + image2Size_.height),
136 image2Size_.width, image2Size_.height);
137 }
138
139 if (image) {
140 NSRect sourceImageRect =
141 NSMakeRect(0, 0, srcSize.width, srcSize.height);
142 [image drawInRect:destRect
143 fromRect:sourceImageRect
144 operation:NSCompositeSourceOver
145 fraction:1.0];
146 }
147 }
148
149 - (void)advanceFrame {
150 ++animationFrame_;
151 }
152
153 @end
154
155 typedef std::set<ThrobberView*> ThrobberSet;
156
157 // ThrobberTimer manages the animation of a set of ThrobberViews. It allows
158 // a single timer instance to be shared among as many ThrobberViews as needed.
159 @interface ThrobberTimer : NSObject {
160 @private
161 // A set of weak references to each ThrobberView that should be notified
162 // whenever the timer fires.
163 ThrobberSet throbbers_;
164
165 // Weak reference to the timer that calls back to this object. The timer
166 // retains this object.
167 NSTimer* timer_;
168
169 // Whether the timer is actively running. To avoid timer construction
170 // and destruction overhead, the timer is not invalidated when it is not
171 // needed, but its next-fire date is set to [NSDate distantFuture].
172 // It is not possible to determine whether the timer has been suspended by
173 // comparing its fireDate to [NSDate distantFuture], though, so a separate
174 // variable is used to track this state.
175 BOOL timerRunning_;
176
177 // The thread that created this object. Used to validate that ThrobberViews
178 // are only added and removed on the same thread that the fire action will
179 // be performed on.
180 NSThread* validThread_;
181 }
182
183 // Returns a shared ThrobberTimer. Everyone is expected to use the same
184 // instance.
185 + (ThrobberTimer*)sharedThrobberTimer;
186
187 // Invalidates the timer, which will cause it to remove itself from the run
188 // loop. This causes the timer to be released, and it should then release
189 // this object.
190 - (void)invalidate;
191
192 // Adds or removes ThrobberView objects from the throbbers_ set.
193 - (void)addThrobber:(ThrobberView*)throbber;
194 - (void)removeThrobber:(ThrobberView*)throbber;
195 @end
196
197 @interface ThrobberTimer(PrivateMethods)
198 // Starts or stops the timer as needed as ThrobberViews are added and removed
199 // from the throbbers_ set.
200 - (void)maintainTimer;
201
202 // Calls animate on each ThrobberView in the throbbers_ set.
203 - (void)fire:(NSTimer*)timer;
204 @end
205
206 @implementation ThrobberTimer
207 - (id)init {
208 if ((self = [super init])) {
209 // Start out with a timer that fires at the appropriate interval, but
210 // prevent it from firing by setting its next-fire date to the distant
211 // future. Once a ThrobberView is added, the timer will be allowed to
212 // start firing.
213 timer_ = [NSTimer scheduledTimerWithTimeInterval:kAnimationIntervalSeconds
214 target:self
215 selector:@selector(fire:)
216 userInfo:nil
217 repeats:YES];
218 [timer_ setFireDate:[NSDate distantFuture]];
219 timerRunning_ = NO;
220
221 validThread_ = [NSThread currentThread];
222 }
223 return self;
224 }
225
226 + (ThrobberTimer*)sharedThrobberTimer {
227 // Leaked. That's OK, it's scoped to the lifetime of the application.
228 static ThrobberTimer* sharedInstance = [[ThrobberTimer alloc] init];
229 return sharedInstance;
230 }
231
232 - (void)invalidate {
233 [timer_ invalidate];
234 }
235
236 - (void)addThrobber:(ThrobberView*)throbber {
237 DCHECK([NSThread currentThread] == validThread_);
238 throbbers_.insert(throbber);
239 [self maintainTimer];
240 }
241
242 - (void)removeThrobber:(ThrobberView*)throbber {
243 DCHECK([NSThread currentThread] == validThread_);
244 throbbers_.erase(throbber);
245 [self maintainTimer];
246 }
247
248 - (void)maintainTimer {
249 BOOL oldRunning = timerRunning_;
250 BOOL newRunning = throbbers_.empty() ? NO : YES;
251
252 if (oldRunning == newRunning)
253 return;
254
255 // To start the timer, set its next-fire date to an appropriate interval from
256 // now. To suspend the timer, set its next-fire date to a preposterous time
257 // in the future.
258 NSDate* fireDate;
259 if (newRunning)
260 fireDate = [NSDate dateWithTimeIntervalSinceNow:kAnimationIntervalSeconds];
261 else
262 fireDate = [NSDate distantFuture];
263
264 [timer_ setFireDate:fireDate];
265 timerRunning_ = newRunning;
266 }
267
268 - (void)fire:(NSTimer*)timer {
269 // The call to [throbber animate] may result in the ThrobberView calling
270 // removeThrobber: if it decides it's done animating. That would invalidate
271 // the iterator, making it impossible to correctly get to the next element
272 // in the set. To prevent that from happening, a second iterator is used
273 // and incremented before calling [throbber animate].
274 ThrobberSet::const_iterator current = throbbers_.begin();
275 ThrobberSet::const_iterator next = current;
276 while (current != throbbers_.end()) {
277 ++next;
278 ThrobberView* throbber = *current;
279 [throbber animate];
280 current = next;
281 }
282 }
283 @end
284
285 @implementation ThrobberView
286
287 + (id)filmstripThrobberViewWithFrame:(NSRect)frame
288 image:(NSImage*)image {
289 ThrobberFilmstripDelegate* delegate =
290 [[[ThrobberFilmstripDelegate alloc] initWithImage:image] autorelease];
291 if (!delegate)
292 return nil;
293
294 return [[[ThrobberView alloc] initWithFrame:frame
295 delegate:delegate] autorelease];
296 }
297
298 + (id)toastThrobberViewWithFrame:(NSRect)frame
299 beforeImage:(NSImage*)beforeImage
300 afterImage:(NSImage*)afterImage {
301 ThrobberToastDelegate* delegate =
302 [[[ThrobberToastDelegate alloc] initWithImage1:beforeImage
303 image2:afterImage] autorelease];
304 if (!delegate)
305 return nil;
306
307 return [[[ThrobberView alloc] initWithFrame:frame
308 delegate:delegate] autorelease];
309 }
310
311 - (id)initWithFrame:(NSRect)frame delegate:(id<ThrobberDataDelegate>)delegate {
312 if ((self = [super initWithFrame:frame])) {
313 dataDelegate_ = [delegate retain];
314 }
315 return self;
316 }
317
318 - (void)dealloc {
319 [dataDelegate_ release];
320 [[ThrobberTimer sharedThrobberTimer] removeThrobber:self];
321
322 [super dealloc];
323 }
324
325 // Manages this ThrobberView's membership in the shared throbber timer set on
326 // the basis of its visibility and whether its animation needs to continue
327 // running.
328 - (void)maintainTimer {
329 ThrobberTimer* throbberTimer = [ThrobberTimer sharedThrobberTimer];
330
331 if ([self window] && ![self isHidden] && ![dataDelegate_ animationIsComplete])
332 [throbberTimer addThrobber:self];
333 else
334 [throbberTimer removeThrobber:self];
335 }
336
337 // A ThrobberView added to a window may need to begin animating; a ThrobberView
338 // removed from a window should stop.
339 - (void)viewDidMoveToWindow {
340 [self maintainTimer];
341 [super viewDidMoveToWindow];
342 }
343
344 // A hidden ThrobberView should stop animating.
345 - (void)viewDidHide {
346 [self maintainTimer];
347 [super viewDidHide];
348 }
349
350 // A visible ThrobberView may need to start animating.
351 - (void)viewDidUnhide {
352 [self maintainTimer];
353 [super viewDidUnhide];
354 }
355
356 // Called when the timer fires. Advance the frame, dirty the display, and remove
357 // the throbber if it's no longer needed.
358 - (void)animate {
359 [dataDelegate_ advanceFrame];
360 [self setNeedsDisplay:YES];
361
362 if ([dataDelegate_ animationIsComplete]) {
363 [[ThrobberTimer sharedThrobberTimer] removeThrobber:self];
364 }
365 }
366
367 // Overridden to draw the appropriate frame in the image strip.
368 - (void)drawRect:(NSRect)rect {
369 [dataDelegate_ drawFrameInRect:[self bounds]];
370 }
371
372 @end
OLDNEW
« no previous file with comments | « chrome/browser/ui/cocoa/throbber_view.h ('k') | chrome/browser/ui/cocoa/throbber_view_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698