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

Side by Side Diff: chrome/browser/ui/cocoa/location_bar/security_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: Fix for rsesek 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/security_state_bubble_decoration.h "
6
7 #include <cmath>
8
9 #import "base/mac/mac_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
12 #import "chrome/browser/ui/cocoa/location_bar/location_icon_decoration.h"
13 #import "chrome/browser/ui/cocoa/themed_window.h"
14 #include "grit/theme_resources.h"
15 #include "skia/ext/skia_utils_mac.h"
16 #import "ui/base/cocoa/nsview_additions.h"
17 #include "ui/base/material_design/material_design_controller.h"
18 #include "ui/gfx/animation/tween.h"
19 #include "ui/gfx/color_palette.h"
20 #include "ui/gfx/font_list.h"
21 #include "ui/gfx/image/image_skia_util_mac.h"
22 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
23 #include "ui/gfx/text_elider.h"
24
25 // TODO(spqchan): Decorations that don't fit in the available space are
26 // omitted. See crbug.com/638427.
27
28 namespace {
29
30 // This is used to increase the right margin of this decoration.
31 const CGFloat kRightSideMargin = 1.0;
32
33 // Padding between the icon and label.
34 CGFloat kIconLabelPadding = 4.0;
35
36 // Inset for the background.
37 const CGFloat kBackgroundYInset = 4.0;
38
39 // The offset of the text's baseline on a retina screen.
40 const CGFloat kRetinaBaselineOffset = 0.5;
41
42 // The info-bubble point should look like it points to the bottom of the lock
43 // icon. Determined with Pixie.app.
44 const CGFloat kPageInfoBubblePointYOffset = 6.0;
45
46 // Minimum acceptable width for the ev bubble.
47 const CGFloat kMinElidedBubbleWidth = 150.0;
48
49 // Maximum amount of available space to make the bubble, subject to
50 // |kMinElidedBubbleWidth|.
51 const float kMaxBubbleFraction = 0.5;
52
53 // The base text color used for non-MD. The color tuples are stolen from
54 // location_bar_view_gtk.cc.
55 const SkColor kBaseTextColor = SkColorSetRGB(0x07, 0x95, 0x00);
56
57 // Duration of animation in ms.
58 const NSTimeInterval kInAnimationDuration = 0.330;
59 const NSTimeInterval kOutAnimationDuration = 0.250;
60
61 // Interval of the animation timer, 60Hz.
62 const NSTimeInterval kAnimationInterval = 1.0 / 60.0;
63
64 // Transformation values at the beginning of the animation.
65 const CGFloat kStartScale = 0.25;
66 const CGFloat kStartx_offset = -15.0;
67
68 // Different states of the animation. In |kAnimationIn|, the verbose state
69 // animates in. In |kAnimationOut|, the verbose state animates out.
70 enum AnimationState {
71 kAnimationIn,
72 kAnimationOut,
73 };
74
75 } // namespace
76
77 @interface SecurityStateAnimation : NSObject {
78 @private
79 SecurityStateBubbleDecoration* owner_; // Weak, owns this.
80
81 // Counter, [0..1], with aninmation progress.
82 double progress_;
83
84 // Animation timer. Owns this, owned by the run loop.
85 NSTimer* timer_;
86
87 // The timestamp of the last time timerFired: was called.
88 NSTimeInterval frameTimestamp_;
89 }
90
91 @property(readonly, nonatomic) AnimationState state;
92
93 // Designated initializer. |owner| must not be nil. Animation timer will start
94 // as soon as the object is created.
95 - (instancetype)initWithOwner:(SecurityStateBubbleDecoration*)owner
96 state:(AnimationState)state;
97
98 // Call when |owner| is going away or the animation needs to be stopped.
99 // Ensures that any dangling references are cleared. Can be called multiple
100 // times.
101 - (void)stopAnimation;
102
103 // Returns the current progress of the animation with the curve applied to it.
104 // A value in the range of [0, 1].
105 - (double)animationProgress;
106
107 // Returns true if the animation is running.
108 - (BOOL)isRunning;
109
110 @end
111
112 @implementation SecurityStateAnimation
113
114 @synthesize state = state_;
115
116 - (instancetype)initWithOwner:(SecurityStateBubbleDecoration*)owner
117 state:(AnimationState)state {
118 DCHECK(ui::MaterialDesignController::IsModeMaterial());
119
120 if ((self = [super init])) {
121 owner_ = owner;
122 frameTimestamp_ = CACurrentMediaTime();
123 timer_ = [NSTimer scheduledTimerWithTimeInterval:kAnimationInterval
124 target:self
125 selector:@selector(timerFired:)
126 userInfo:nil
127 repeats:YES];
128 // Add the timer to |NSRunLoopCommonModes| because it should run in
129 // |NSEventTrackingRunLoopMode| as well as |NSDefaultRunLoopMode|. This
130 // allows the timer to run when the user resizes the window via mouse drag.
131 [[NSRunLoop mainRunLoop] addTimer:timer_ forMode:NSRunLoopCommonModes];
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 // SecurityStateBubbleDecoration, public:
184
185 SecurityStateBubbleDecoration::SecurityStateBubbleDecoration(
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
198 base::scoped_nsobject<NSMutableParagraphStyle> style(
199 [[NSMutableParagraphStyle alloc] init]);
200 [style setLineBreakMode:NSLineBreakByClipping];
201 [attributes_ setObject:style forKey:NSParagraphStyleAttributeName];
202 } else {
203 SetTextColor(skia::SkColorToSRGBNSColor(kBaseTextColor));
204 }
205 }
206
207 SecurityStateBubbleDecoration::~SecurityStateBubbleDecoration() {
208 // Just in case the timer is still holding onto the animation object, force
209 // cleanup so it can't get back to |this|.
210 [animation_ stopAnimation];
211 animation_.reset();
212 }
213
214 void SecurityStateBubbleDecoration::SetFullLabel(NSString* label) {
215 full_label_.reset([[NSString alloc] initWithString:label]);
Robert Sesek 2016/08/18 19:33:59 [label copy] is what I had in mind, but this is fi
spqchan 2016/08/18 21:08:30 Done.
216 SetLabel(full_label_);
217 }
218
219 void SecurityStateBubbleDecoration::SetLabelColor(SkColor color) {
220 label_color_ = color;
221 }
222
223 void SecurityStateBubbleDecoration::OnAnimationProgressed() {
224 owner_->Layout();
225 }
226
227 void SecurityStateBubbleDecoration::AnimateIn(bool image_fade) {
228 image_fade_ = image_fade;
229 animation_.reset(
230 [[SecurityStateAnimation alloc] initWithOwner:this state:kAnimationIn]);
231 }
232
233 void SecurityStateBubbleDecoration::AnimateOut() {
234 if (!HasAnimatedIn())
235 return;
236
237 animation_.reset(
238 [[SecurityStateAnimation alloc] initWithOwner:this state:kAnimationOut]);
239 }
240
241 bool SecurityStateBubbleDecoration::HasAnimatedIn() const {
242 return animation_.get() && [animation_ state] == kAnimationIn &&
243 ![animation_ isRunning];
244 }
245
246 bool SecurityStateBubbleDecoration::HasAnimatedOut() const {
247 if (!animation_.get())
248 return true;
249
250 return animation_.get() && [animation_ state] == kAnimationOut &&
251 ![animation_ isRunning];
252 }
253
254 bool SecurityStateBubbleDecoration::AnimatingOut() const {
255 return animation_.get() && [animation_ state] == kAnimationOut &&
256 [animation_ isRunning];
257 }
258
259 void SecurityStateBubbleDecoration::ResetAndHide() {
260 SetVisible(false);
261 if (animation_.get() && [animation_ state] == kAnimationIn)
262 animation_.reset();
263 }
264
265 //////////////////////////////////////////////////////////////////
266 // SecurityStateBubbleDecoration::LocationBarDecoration:
267
268 CGFloat SecurityStateBubbleDecoration::GetWidthForSpace(CGFloat width) {
269 if (!ui::MaterialDesignController::IsModeMaterial())
270 return GetWidthForText(width);
271
272 CGFloat location_icon_width = location_icon_->GetWidthForSpace(width);
273 CGFloat text_width = GetWidthForText(width) - location_icon_width;
274 return (text_width * GetAnimationProgress()) + location_icon_width;
275 }
276
277 void SecurityStateBubbleDecoration::DrawInFrame(NSRect frame,
278 NSView* control_view) {
279 const NSRect decoration_frame = NSInsetRect(frame, 0.0, kBackgroundYInset);
280 CGFloat text_offset = NSMinX(decoration_frame);
281 if (image_) {
282 // The image should fade in if we're animating in.
283 CGFloat image_alpha =
284 image_fade_ && animation_.get() && [animation_ state] == kAnimationIn
285 ? GetAnimationProgress()
286 : 1.0;
287
288 // Center the image vertically.
289 const NSSize image_size = [image_ size];
290 NSRect image_rect = decoration_frame;
291 image_rect.origin.y +=
292 std::floor((NSHeight(decoration_frame) - image_size.height) / 2.0);
293 image_rect.size = image_size;
294 [image_ drawInRect:image_rect
295 fromRect:NSZeroRect // Entire image
296 operation:NSCompositeSourceOver
297 fraction:image_alpha
298 respectFlipped:YES
299 hints:nil];
300 text_offset = NSMaxX(image_rect) + kIconLabelPadding;
301 }
302
303 // Set the text color and draw the text.
304 if (label_) {
305 bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
306 NSColor* text_color =
307 in_dark_mode ? skia::SkColorToSRGBNSColor(kMaterialDarkModeTextColor)
308 : GetBackgroundBorderColor();
309 SetTextColor(text_color);
310
311 // Transform the coordinate system to adjust the baseline on Retina.
312 // This is the only way to get fractional adjustments.
313 // gfx::ScopedNSGraphicsContextSaveGState save_graphics_state;
314 CGFloat line_width = [control_view cr_lineWidth];
315 if (line_width < 1) {
316 NSAffineTransform* transform = [NSAffineTransform transform];
317 [transform translateXBy:0 yBy:kRetinaBaselineOffset];
318 [transform concat];
319 }
320
321 base::scoped_nsobject<NSAttributedString> text([[NSAttributedString alloc]
322 initWithString:label_
323 attributes:attributes_]);
324
325 // Calculate the text frame based on the text height and offsets.
326 NSRect text_rect = frame;
327 CGFloat textHeight = [text size].height;
328
329 text_rect.origin.x = text_offset;
330 text_rect.origin.y = std::round(NSMidY(text_rect) - textHeight / 2.0) - 1;
331 text_rect.size.width = NSMaxX(decoration_frame) - NSMinX(text_rect);
332 text_rect.size.height = textHeight;
333
334 NSAffineTransform* transform = [NSAffineTransform transform];
335 CGFloat progress = GetAnimationProgress();
336
337 // Apply transformations so that the text animation:
338 // - Scales from 0.75 to 1.
339 // - Translates the X position to its origin after it got scaled, and
340 // before moving in a position from from -15 to 0
341 // - Translates the Y position so that the text is centered vertically.
342 double scale = gfx::Tween::DoubleValueBetween(progress, kStartScale, 1.0);
343
344 double x_origin_offset = NSMinX(text_rect) * (1 - scale);
345 double y_origin_offset = NSMinY(text_rect) * (1 - scale);
346 double x_offset =
347 gfx::Tween::DoubleValueBetween(progress, kStartx_offset, 0);
348 double y_offset = NSHeight(text_rect) * (1 - scale) / 2.0;
349
350 [transform translateXBy:x_offset + x_origin_offset
351 yBy:y_offset + y_origin_offset];
352 [transform scaleBy:scale];
353 [transform concat];
354
355 // Draw the label.
356 [text drawInRect:text_rect];
357
358 // Draw the divider.
359 NSBezierPath* line = [NSBezierPath bezierPath];
360 [line setLineWidth:line_width];
361 [line moveToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
362 NSMinY(decoration_frame))];
363 [line lineToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
364 NSMaxY(decoration_frame))];
365
366 NSColor* divider_color = GetDividerColor(in_dark_mode);
367 CGFloat divider_alpha =
368 [divider_color alphaComponent] * GetAnimationProgress();
369 divider_color = [divider_color colorWithAlphaComponent:divider_alpha];
370 [divider_color set];
371 [line stroke];
372 }
373 }
374
375 void SecurityStateBubbleDecoration::DrawWithBackgroundInFrame(
376 NSRect background_frame,
377 NSRect frame,
378 NSView* control_view) {
379 if (!ui::MaterialDesignController::IsModeMaterial()) {
380 BubbleDecoration::DrawWithBackgroundInFrame(background_frame, frame,
381 control_view);
382 return;
383 }
384
385 NSRect rect = NSInsetRect(background_frame, 0, 3);
386 rect.size.width -= kRightSideMargin;
387
388 CGFloat line_width = [control_view cr_lineWidth];
389 bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
390 // Only adjust the path rect by 1/2 the line width if it's going to be
391 // stroked (so that the stroke lines fall along pixel lines).
392 if (!in_dark_mode) {
393 rect = NSInsetRect(rect, line_width / 2., line_width / 2.);
394 }
395
396 DrawInFrame(frame, control_view);
397 }
398
399 // Pass mouse operations through to location icon.
400 bool SecurityStateBubbleDecoration::IsDraggable() {
401 return location_icon_->IsDraggable();
402 }
403
404 NSPasteboard* SecurityStateBubbleDecoration::GetDragPasteboard() {
405 return location_icon_->GetDragPasteboard();
406 }
407
408 NSImage* SecurityStateBubbleDecoration::GetDragImage() {
409 return location_icon_->GetDragImage();
410 }
411
412 NSRect SecurityStateBubbleDecoration::GetDragImageFrame(NSRect frame) {
413 return GetImageRectInFrame(frame);
414 }
415
416 bool SecurityStateBubbleDecoration::OnMousePressed(NSRect frame,
417 NSPoint location) {
418 return location_icon_->OnMousePressed(frame, location);
419 }
420
421 bool SecurityStateBubbleDecoration::AcceptsMousePress() {
422 return true;
423 }
424
425 NSPoint SecurityStateBubbleDecoration::GetBubblePointInFrame(NSRect frame) {
426 NSRect image_rect = GetImageRectInFrame(frame);
427 return NSMakePoint(NSMidX(image_rect),
428 NSMaxY(image_rect) - kPageInfoBubblePointYOffset);
429 }
430
431 //////////////////////////////////////////////////////////////////
432 // SecurityStateBubbleDecoration::BubbleDecoration:
433
434 NSColor* SecurityStateBubbleDecoration::GetBackgroundBorderColor() {
435 return skia::SkColorToSRGBNSColor(
436 SkColorSetA(label_color_, 255.0 * GetAnimationProgress()));
437 }
438
439 ui::NinePartImageIds SecurityStateBubbleDecoration::GetBubbleImageIds() {
440 return IMAGE_GRID(IDR_OMNIBOX_EV_BUBBLE);
441 }
442
443 NSColor* SecurityStateBubbleDecoration::GetDarkModeTextColor() {
444 return [NSColor whiteColor];
445 }
446
447 //////////////////////////////////////////////////////////////////
448 // SecurityStateBubbleDecoration, private:
449
450 CGFloat SecurityStateBubbleDecoration::GetAnimationProgress() const {
451 if (!ui::MaterialDesignController::IsModeMaterial())
452 return 1.0;
453
454 return animation_.get() ? [animation_ animationProgress] : 0.0;
455 }
456
457 CGFloat SecurityStateBubbleDecoration::GetWidthForText(CGFloat width) {
458 // Limit with to not take up too much of the available width, but
459 // also don't let it shrink too much.
460 width = std::max(width * kMaxBubbleFraction, kMinElidedBubbleWidth);
461
462 // Use the full label if it fits.
463 NSImage* image = GetImage();
464 const CGFloat all_width = GetWidthForImageAndLabel(image, full_label_);
465 if (all_width <= width) {
466 SetLabel(full_label_);
467 return all_width;
468 }
469
470 // Width left for laying out the label.
471 const CGFloat width_left = width - GetWidthForImageAndLabel(image, @"");
472
473 // Middle-elide the label to fit |width_left|. This leaves the
474 // prefix and the trailing country code in place.
475 NSString* elided_label = base::SysUTF16ToNSString(gfx::ElideText(
476 base::SysNSStringToUTF16(full_label_),
477 gfx::FontList(gfx::Font(GetFont())), width_left, gfx::ELIDE_MIDDLE));
478
479 // Use the elided label.
480 SetLabel(elided_label);
481 return GetWidthForImageAndLabel(image, elided_label);
482 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698