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

Side by Side Diff: chrome/browser/ui/cocoa/location_bar/verbose_state_bubble_decoration.mm

Issue 2119033002: [Material][Mac] Implement Omnibox Verbose State Chips (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: nit Created 4 years, 4 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 2016 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/location_bar/verbose_state_bubble_decoration.h"
6
7 #import "base/mac/mac_util.h"
8 #include "base/strings/sys_string_conversions.h"
9 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
10 #import "chrome/browser/ui/cocoa/location_bar/location_icon_decoration.h"
11 #import "chrome/browser/ui/cocoa/themed_window.h"
12 #include "grit/theme_resources.h"
13 #include "skia/ext/skia_utils_mac.h"
14 #import "ui/base/cocoa/nsview_additions.h"
15 #include "ui/base/material_design/material_design_controller.h"
16 #include "ui/gfx/animation/tween.h"
17 #include "ui/gfx/color_palette.h"
18 #include "ui/gfx/font_list.h"
19 #include "ui/gfx/image/image_skia_util_mac.h"
20 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
21 #include "ui/gfx/text_elider.h"
22
23 namespace {
24
25 // This is used to increase the right margin of this decoration.
26 const CGFloat kRightSideMargin = 1.0;
27
28 // Padding between the icon and label.
29 CGFloat kIconLabelPadding = 4.0;
30
31 // Inset for the background.
32 const CGFloat kBackgroundYInset = 4.0;
33
34 // The offset of the text's baseline on a retina screen.
35 const CGFloat kRetinaBaselineOffset = 0.5;
36
37 // TODO(shess): In general, decorations that don't fit in the
38 // available space are omitted. This one never goes to omitted, it
39 // sticks at 150px, which AFAICT follows the Windows code. Since the
40 // Layout() code doesn't take this into account, it's possible the
41 // control could end up with display artifacts, though things still
42 // work (and don't crash).
43 // http://crbug.com/49822
Robert Sesek 2016/08/16 22:25:32 This bug was archived. Does it still apply?
spqchan 2016/08/17 00:41:18 It still does, the star icon should be omitted but
44
45 // The info-bubble point should look like it points to the bottom of the lock
46 // icon. Determined with Pixie.app.
47 const CGFloat kPageInfoBubblePointYOffset = 6.0;
48
49 // Minimum acceptable width for the ev bubble.
50 const CGFloat kMinElidedBubbleWidth = 150.0;
51
52 // Maximum amount of available space to make the bubble, subject to
53 // |kMinElidedBubbleWidth|.
54 const float kMaxBubbleFraction = 0.5;
55
56 // The base text color used for non-MD. The color tuples are stolen from
57 // location_bar_view_gtk.cc.
58 const SkColor kBaseTextColor = SkColorSetRGB(0x07, 0x95, 0x00);
59
60 // Duration of animation in ms.
61 const NSTimeInterval kInAnimationDuration = 0.330;
62 const NSTimeInterval kOutAnimationDuration = 0.250;
63
64 // Interval of the animation timer, 60Hz.
65 const NSTimeInterval kAnimationInterval = 1.0 / 60.0;
66
67 // Transformation values at the beginning of the animation.
68 const CGFloat kStartScale = 0.25;
69 const CGFloat kStartXOffset = -15.0;
70
71 // Different states of the animation. In |kAnimationIn|, the verbose state
72 // animates in. In |kAnimationOut|, the verbose state animates out.
73 enum AnimationState {
74 kAnimationIn,
75 kAnimationOut,
76 };
77
78 } // namespace
79
80 @interface VerboseAnimation : NSObject {
81 @private
82 VerboseStateBubbleDecoration* owner_; // Weak, owns this.
83
84 // Counter, [0..1], with aninmation progress.
85 double progress_;
86
87 // Animation timer. Owns this, owned by the run loop.
88 NSTimer* timer_;
89
90 // The timestamp of the last time timerFired: was called.
91 NSTimeInterval frameTimestamp_;
92 }
93
94 @property(readonly, nonatomic) AnimationState state;
95
96 // Designated initializer. |owner| must not be nil. Animation timer will start
97 // as soon as the object is created.
98 - (id)initWithOwner:(VerboseStateBubbleDecoration*)owner
Robert Sesek 2016/08/16 22:25:32 instancetype preferred in new code
spqchan 2016/08/17 00:41:18 Done.
99 state:(AnimationState)state;
100
101 // Call when |owner| is going away or the animation needs to be stopped.
102 // Ensures that any dangling references are cleared. Can be called multiple
103 // times.
104 - (void)stopAnimation;
105
106 // Returns the current progress of the animation with the curve applied to it.
107 // A value in the range of [0, 1].
108 - (double)animationProgress;
109
110 // Returns true if the animation is running.
111 - (BOOL)isRunning;
112
113 @end
114
115 @implementation VerboseAnimation
116
117 @synthesize state = state_;
118
119 - (id)initWithOwner:(VerboseStateBubbleDecoration*)owner
120 state:(AnimationState)state {
121 DCHECK(ui::MaterialDesignController::IsModeMaterial());
122
123 if ((self = [super init])) {
124 owner_ = owner;
125 frameTimestamp_ = CACurrentMediaTime();
126 timer_ = [NSTimer scheduledTimerWithTimeInterval:kAnimationInterval
127 target:self
128 selector:@selector(timerFired:)
129 userInfo:nil
130 repeats:YES];
131 [[NSRunLoop mainRunLoop] addTimer:timer_ forMode:NSRunLoopCommonModes];
Robert Sesek 2016/08/16 22:25:32 Why do you need CommonModes here?
spqchan 2016/08/17 00:41:18 We want the timer to run when the user is performi
Robert Sesek 2016/08/18 14:43:23 Add a comment then?
spqchan 2016/08/18 18:37:18 Done.
132
133 state_ = state;
134 }
135
136 return self;
137 }
138
139 - (void)dealloc {
140 DCHECK(!timer_);
141 [super dealloc];
142 }
143
144 // Clear weak references and stop the timer.
145 - (void)stopAnimation {
146 [timer_ invalidate];
147 timer_ = nil;
148 }
149
150 - (BOOL)isRunning {
151 return timer_ != nil;
152 }
153
154 - (void)timerFired:(NSTimer*)timer {
155 CGFloat currentTime = CACurrentMediaTime();
156 CGFloat elapsedTime = currentTime - frameTimestamp_;
157 frameTimestamp_ = currentTime;
158
159 // Increment animation progress, normalized to [0..1].
160 CGFloat duration =
161 state_ == kAnimationIn ? kInAnimationDuration : kOutAnimationDuration;
162 progress_ += elapsedTime / duration;
163 progress_ = std::min(progress_, 1.0);
164
165 // Stop timer if it has reached the end of its life.
166 if (progress_ >= 1.0)
167 [self stopAnimation];
168
169 owner_->OnAnimationProgressed();
170 }
171
172 - (double)animationProgress {
173 if (state_ == kAnimationIn)
174 return gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN, progress_);
175
176 return 1 - gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN_EXPO,
177 progress_);
178 }
179
180 @end
181
182 //////////////////////////////////////////////////////////////////
183 // VerboseStateBubbleDecoration, public:
184
185 VerboseStateBubbleDecoration::VerboseStateBubbleDecoration(
186 LocationIconDecoration* location_icon,
187 LocationBarViewMac* owner)
188 : location_icon_(location_icon),
189 label_color_(gfx::kGoogleGreen700),
190 image_fade_(true),
191 owner_(owner) {
192 if (ui::MaterialDesignController::IsModeMaterial()) {
193 // On Retina the text label is 1px above the Omnibox textfield's text
194 // baseline. If the Omnibox textfield also drew the label the baselines
195 // would align.
196 SetRetinaBaselineOffset(kRetinaBaselineOffset);
197 } else {
198 SetTextColor(skia::SkColorToSRGBNSColor(kBaseTextColor));
199 }
200 }
201
202 VerboseStateBubbleDecoration::~VerboseStateBubbleDecoration() {
203 // Just in case the timer is still holding onto the animation object, force
204 // cleanup so it can't get back to |this|.
205 [animation_ stopAnimation];
206 animation_.reset();
207 }
208
209 void VerboseStateBubbleDecoration::SetFullLabel(NSString* label) {
210 full_label_.reset([label retain]);
211 SetLabel(full_label_);
212 }
213
214 void VerboseStateBubbleDecoration::SetLabelColor(SkColor color) {
215 label_color_ = color;
216 }
217
218 void VerboseStateBubbleDecoration::OnAnimationProgressed() {
219 owner_->Layout();
220 }
221
222 void VerboseStateBubbleDecoration::AnimateIn(bool image_fade) {
223 image_fade_ = image_fade;
224 animation_.reset(
225 [[VerboseAnimation alloc] initWithOwner:this state:kAnimationIn]);
226 }
227
228 void VerboseStateBubbleDecoration::AnimateOut() {
229 if (!HasAnimatedIn())
230 return;
231
232 animation_.reset(
233 [[VerboseAnimation alloc] initWithOwner:this state:kAnimationOut]);
234 }
235
236 bool VerboseStateBubbleDecoration::HasAnimatedIn() const {
237 return animation_.get() && [animation_ state] == kAnimationIn &&
238 ![animation_ isRunning];
239 }
240
241 bool VerboseStateBubbleDecoration::HasAnimatedOut() const {
242 if (!animation_.get())
243 return true;
244
245 return animation_.get() && [animation_ state] == kAnimationOut &&
246 ![animation_ isRunning];
247 }
248
249 bool VerboseStateBubbleDecoration::AnimatingOut() const {
250 return animation_.get() && [animation_ state] == kAnimationOut &&
251 [animation_ isRunning];
252 }
253
254 void VerboseStateBubbleDecoration::ResetAndHide() {
255 SetVisible(false);
256 if (animation_.get() && [animation_ state] == kAnimationIn)
257 animation_.reset();
258 }
259
260 //////////////////////////////////////////////////////////////////
261 // VerboseStateBubbleDecoration::LocationBarDecoration:
262
263 CGFloat VerboseStateBubbleDecoration::GetWidthForSpace(CGFloat width) {
264 if (!ui::MaterialDesignController::IsModeMaterial())
265 return GetWidthForText(width);
266
267 CGFloat locationIconWidth = location_icon_->GetWidthForSpace(width);
268 CGFloat textWidth = GetWidthForText(width) - locationIconWidth;
269 return (textWidth * GetAnimationProgress()) + locationIconWidth;
270 }
271
272 void VerboseStateBubbleDecoration::DrawInFrame(NSRect frame,
273 NSView* control_view) {
274 const NSRect decoration_frame = NSInsetRect(frame, 0.0, kBackgroundYInset);
275 CGFloat textOffset = NSMinX(decoration_frame);
Robert Sesek 2016/08/16 22:25:32 naming: under_scores, and other locals in this met
spqchan 2016/08/17 00:41:18 Done.
276 if (image_) {
277 // The image should fade in if we're animating in.
278 CGFloat image_alpha =
279 image_fade_ && animation_.get() && [animation_ state] == kAnimationIn
280 ? GetAnimationProgress()
281 : 1.0;
282
283 // Center the image vertically.
284 const NSSize imageSize = [image_ size];
285 NSRect imageRect = decoration_frame;
286 imageRect.origin.y +=
287 std::floor((NSHeight(decoration_frame) - imageSize.height) / 2.0);
288 imageRect.size = imageSize;
289 [image_ drawInRect:imageRect
290 fromRect:NSZeroRect // Entire image
291 operation:NSCompositeSourceOver
292 fraction:image_alpha
293 respectFlipped:YES
294 hints:nil];
295 textOffset = NSMaxX(imageRect) + kIconLabelPadding;
296 }
297
298 // Set the text color and draw the text.
299 if (full_label_) {
300 bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
301 NSColor* text_color =
302 in_dark_mode ? skia::SkColorToSRGBNSColor(kMaterialDarkModeTextColor)
303 : GetBackgroundBorderColor();
304 SetTextColor(text_color);
305
306 // Transform the coordinate system to adjust the baseline on Retina.
307 // This is the only way to get fractional adjustments.
308 gfx::ScopedNSGraphicsContextSaveGState saveGraphicsState;
309 CGFloat lineWidth = [control_view cr_lineWidth];
310 if (lineWidth < 1) {
311 NSAffineTransform* transform = [NSAffineTransform transform];
312 [transform translateXBy:0 yBy:kRetinaBaselineOffset];
313 [transform concat];
314 }
315
316 base::scoped_nsobject<NSAttributedString> str([[NSAttributedString alloc]
Robert Sesek 2016/08/16 22:25:32 naming: |text|, given that you have text_rect and
spqchan 2016/08/17 00:41:18 Done.
317 initWithString:full_label_
318 attributes:attributes_]);
319
320 // Calculate the text frame based on the text height and offsets.
321 NSRect textRect = frame;
322 CGFloat textHeight = [str size].height;
323
324 textRect.origin.x = textOffset;
325 textRect.origin.y = roundf(NSMidY(textRect) - textHeight / 2.0) - 1;
326 textRect.size.width = NSMaxX(decoration_frame) - NSMinX(textRect);
327 textRect.size.height = textHeight;
328
329 NSAffineTransform* transform = [NSAffineTransform transform];
330 CGFloat progress = GetAnimationProgress();
331
332 // Apply transformations so that the text animation:
333 // - Scales from 0.75 to 1.
334 // - Translates the X position to its origin after it got scaled, and
335 // before moving in a position from from -15 to 0
336 // - Translates the Y position so that the text is centered vertically.
337 double scale = gfx::Tween::DoubleValueBetween(progress, kStartScale, 1.0);
338
339 double xOriginOffset = NSMinX(textRect) * (1 - scale);
340 double yOriginOffset = NSMinY(textRect) * (1 - scale);
341 double xOffset = gfx::Tween::DoubleValueBetween(progress, kStartXOffset, 0);
342 double yOffset = NSHeight(textRect) * (1 - scale) / 2.0;
343
344 [transform translateXBy:xOffset + xOriginOffset
345 yBy:yOffset + yOriginOffset];
346 [transform scaleBy:scale];
347 [transform concat];
348
349 // Draw the label.
350 [str drawInRect:textRect];
351
352 // Draw the divider.
353 NSBezierPath* line = [NSBezierPath bezierPath];
354 [line setLineWidth:1];
355 [line moveToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
356 NSMinY(decoration_frame))];
357 [line lineToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
358 NSMaxY(decoration_frame))];
359
360 NSColor* divider_color = GetDividerColor(in_dark_mode);
361 CGFloat divider_alpha =
362 [divider_color alphaComponent] * GetAnimationProgress();
363 divider_color = [divider_color colorWithAlphaComponent:divider_alpha];
364 [divider_color set];
365 [line stroke];
366 }
367 }
368
369 void VerboseStateBubbleDecoration::DrawWithBackgroundInFrame(
370 NSRect background_frame,
371 NSRect frame,
372 NSView* control_view) {
373 if (!ui::MaterialDesignController::IsModeMaterial()) {
374 BubbleDecoration::DrawWithBackgroundInFrame(background_frame, frame,
375 control_view);
376 return;
377 }
378
379 NSRect rect = NSInsetRect(background_frame, 0, 3);
380 rect.size.width -= kRightSideMargin;
381
382 CGFloat line_width = [control_view cr_lineWidth];
383 bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
384 // Only adjust the path rect by 1/2 the line width if it's going to be
385 // stroked (so that the stroke lines fall along pixel lines).
386 if (!in_dark_mode) {
387 rect = NSInsetRect(rect, line_width / 2., line_width / 2.);
388 }
389
390 DrawInFrame(frame, control_view);
391 }
392
393 // Pass mouse operations through to location icon.
394 bool VerboseStateBubbleDecoration::IsDraggable() {
395 return location_icon_->IsDraggable();
396 }
397
398 NSPasteboard* VerboseStateBubbleDecoration::GetDragPasteboard() {
399 return location_icon_->GetDragPasteboard();
400 }
401
402 NSImage* VerboseStateBubbleDecoration::GetDragImage() {
403 return location_icon_->GetDragImage();
404 }
405
406 NSRect VerboseStateBubbleDecoration::GetDragImageFrame(NSRect frame) {
407 return GetImageRectInFrame(frame);
408 }
409
410 bool VerboseStateBubbleDecoration::OnMousePressed(NSRect frame,
411 NSPoint location) {
412 return location_icon_->OnMousePressed(frame, location);
413 }
414
415 bool VerboseStateBubbleDecoration::AcceptsMousePress() {
416 return true;
417 }
418
419 NSPoint VerboseStateBubbleDecoration::GetBubblePointInFrame(NSRect frame) {
420 NSRect image_rect = GetImageRectInFrame(frame);
421 return NSMakePoint(NSMidX(image_rect),
422 NSMaxY(image_rect) - kPageInfoBubblePointYOffset);
423 }
424
425 //////////////////////////////////////////////////////////////////
426 // VerboseStateBubbleDecoration::BubbleDecoration:
427
428 NSColor* VerboseStateBubbleDecoration::GetBackgroundBorderColor() {
429 return skia::SkColorToSRGBNSColor(
430 SkColorSetA(label_color_, 255.0 * GetAnimationProgress()));
431 }
432
433 ui::NinePartImageIds VerboseStateBubbleDecoration::GetBubbleImageIds() {
434 return IMAGE_GRID(IDR_OMNIBOX_EV_BUBBLE);
435 }
436
437 //////////////////////////////////////////////////////////////////
438 // VerboseStateBubbleDecoration, private:
439
440 CGFloat VerboseStateBubbleDecoration::GetAnimationProgress() const {
441 if (!ui::MaterialDesignController::IsModeMaterial())
442 return 1.0;
443
444 return animation_.get() ? [animation_ animationProgress] : 0.0;
445 }
446
447 CGFloat VerboseStateBubbleDecoration::GetWidthForText(CGFloat width) {
448 // Limit with to not take up too much of the available width, but
449 // also don't let it shrink too much.
450 width = std::max(width * kMaxBubbleFraction, kMinElidedBubbleWidth);
451
452 // Use the full label if it fits.
453 NSImage* image = GetImage();
454 const CGFloat all_width = GetWidthForImageAndLabel(image, full_label_);
455 if (all_width <= width) {
456 SetLabel(full_label_);
457 return all_width;
458 }
459
460 // Width left for laying out the label.
461 const CGFloat width_left = width - GetWidthForImageAndLabel(image, @"");
462
463 // Middle-elide the label to fit |width_left|. This leaves the
464 // prefix and the trailing country code in place.
465 NSString* elided_label = base::SysUTF16ToNSString(gfx::ElideText(
466 base::SysNSStringToUTF16(full_label_),
467 gfx::FontList(gfx::Font(GetFont())), width_left, gfx::ELIDE_MIDDLE));
468
469 // Use the elided label.
470 SetLabel(elided_label);
471 return GetWidthForImageAndLabel(image, elided_label);
472 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698