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

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: fixes for the fixes 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 "
Robert Sesek 2016/08/18 14:43:23 #include <cmath>
spqchan 2016/08/18 18:37:18 Done.
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 // TODO(spqchan): Decorations that don't fit in the available space are
24 // omitted. See crbug.com/638427.
25
26 namespace {
27
28 // This is used to increase the right margin of this decoration.
29 const CGFloat kRightSideMargin = 1.0;
30
31 // Padding between the icon and label.
32 CGFloat kIconLabelPadding = 4.0;
33
34 // Inset for the background.
35 const CGFloat kBackgroundYInset = 4.0;
36
37 // The offset of the text's baseline on a retina screen.
38 const CGFloat kRetinaBaselineOffset = 0.5;
39
40 // The info-bubble point should look like it points to the bottom of the lock
41 // icon. Determined with Pixie.app.
42 const CGFloat kPageInfoBubblePointYOffset = 6.0;
43
44 // Minimum acceptable width for the ev bubble.
45 const CGFloat kMinElidedBubbleWidth = 150.0;
46
47 // Maximum amount of available space to make the bubble, subject to
48 // |kMinElidedBubbleWidth|.
49 const float kMaxBubbleFraction = 0.5;
50
51 // The base text color used for non-MD. The color tuples are stolen from
52 // location_bar_view_gtk.cc.
53 const SkColor kBaseTextColor = SkColorSetRGB(0x07, 0x95, 0x00);
54
55 // Duration of animation in ms.
56 const NSTimeInterval kInAnimationDuration = 0.330;
57 const NSTimeInterval kOutAnimationDuration = 0.250;
58
59 // Interval of the animation timer, 60Hz.
60 const NSTimeInterval kAnimationInterval = 1.0 / 60.0;
61
62 // Transformation values at the beginning of the animation.
63 const CGFloat kStartScale = 0.25;
64 const CGFloat kStartx_offset = -15.0;
65
66 // Different states of the animation. In |kAnimationIn|, the verbose state
67 // animates in. In |kAnimationOut|, the verbose state animates out.
68 enum AnimationState {
69 kAnimationIn,
70 kAnimationOut,
71 };
72
73 } // namespace
74
75 @interface SecurityStateAnimation : NSObject {
76 @private
77 SecurityStateBubbleDecoration* owner_; // Weak, owns this.
78
79 // Counter, [0..1], with aninmation progress.
80 double progress_;
81
82 // Animation timer. Owns this, owned by the run loop.
83 NSTimer* timer_;
84
85 // The timestamp of the last time timerFired: was called.
86 NSTimeInterval frameTimestamp_;
87 }
88
89 @property(readonly, nonatomic) AnimationState state;
90
91 // Designated initializer. |owner| must not be nil. Animation timer will start
92 // as soon as the object is created.
93 - (instancetype)initWithOwner:(SecurityStateBubbleDecoration*)owner
94 state:(AnimationState)state;
95
96 // Call when |owner| is going away or the animation needs to be stopped.
97 // Ensures that any dangling references are cleared. Can be called multiple
98 // times.
99 - (void)stopAnimation;
100
101 // Returns the current progress of the animation with the curve applied to it.
102 // A value in the range of [0, 1].
103 - (double)animationProgress;
104
105 // Returns true if the animation is running.
106 - (BOOL)isRunning;
107
108 @end
109
110 @implementation SecurityStateAnimation
111
112 @synthesize state = state_;
113
114 - (instancetype)initWithOwner:(SecurityStateBubbleDecoration*)owner
115 state:(AnimationState)state {
116 DCHECK(ui::MaterialDesignController::IsModeMaterial());
117
118 if ((self = [super init])) {
119 owner_ = owner;
120 frameTimestamp_ = CACurrentMediaTime();
121 timer_ = [NSTimer scheduledTimerWithTimeInterval:kAnimationInterval
122 target:self
123 selector:@selector(timerFired:)
124 userInfo:nil
125 repeats:YES];
126 [[NSRunLoop mainRunLoop] addTimer:timer_ forMode:NSRunLoopCommonModes];
127
128 state_ = state;
129 }
130
131 return self;
132 }
133
134 - (void)dealloc {
135 DCHECK(!timer_);
136 [super dealloc];
137 }
138
139 // Clear weak references and stop the timer.
140 - (void)stopAnimation {
141 [timer_ invalidate];
142 timer_ = nil;
143 }
144
145 - (BOOL)isRunning {
146 return timer_ != nil;
147 }
148
149 - (void)timerFired:(NSTimer*)timer {
150 CGFloat currentTime = CACurrentMediaTime();
151 CGFloat elapsedTime = currentTime - frameTimestamp_;
152 frameTimestamp_ = currentTime;
153
154 // Increment animation progress, normalized to [0..1].
155 CGFloat duration =
156 state_ == kAnimationIn ? kInAnimationDuration : kOutAnimationDuration;
157 progress_ += elapsedTime / duration;
158 progress_ = std::min(progress_, 1.0);
159
160 // Stop timer if it has reached the end of its life.
161 if (progress_ >= 1.0)
162 [self stopAnimation];
163
164 owner_->OnAnimationProgressed();
165 }
166
167 - (double)animationProgress {
168 if (state_ == kAnimationIn)
169 return gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN, progress_);
170
171 return 1 - gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN_EXPO,
172 progress_);
173 }
174
175 @end
176
177 //////////////////////////////////////////////////////////////////
178 // SecurityStateBubbleDecoration, public:
179
180 SecurityStateBubbleDecoration::SecurityStateBubbleDecoration(
181 LocationIconDecoration* location_icon,
182 LocationBarViewMac* owner)
183 : location_icon_(location_icon),
184 label_color_(gfx::kGoogleGreen700),
185 image_fade_(true),
186 owner_(owner) {
187 if (ui::MaterialDesignController::IsModeMaterial()) {
188 // On Retina the text label is 1px above the Omnibox textfield's text
189 // baseline. If the Omnibox textfield also drew the label the baselines
190 // would align.
191 SetRetinaBaselineOffset(kRetinaBaselineOffset);
192
193 base::scoped_nsobject<NSMutableParagraphStyle> style(
194 [[NSMutableParagraphStyle alloc] init]);
195 [style setLineBreakMode:NSLineBreakByClipping];
196 [attributes_ setObject:style forKey:NSParagraphStyleAttributeName];
197 } else {
198 SetTextColor(skia::SkColorToSRGBNSColor(kBaseTextColor));
199 }
200 }
201
202 SecurityStateBubbleDecoration::~SecurityStateBubbleDecoration() {
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 SecurityStateBubbleDecoration::SetFullLabel(NSString* label) {
210 full_label_.reset([label retain]);
Robert Sesek 2016/08/18 14:43:24 copy NSStings
spqchan 2016/08/18 18:37:18 Done.
211 SetLabel(full_label_);
212 }
213
214 void SecurityStateBubbleDecoration::SetLabelColor(SkColor color) {
215 label_color_ = color;
216 }
217
218 void SecurityStateBubbleDecoration::OnAnimationProgressed() {
219 owner_->Layout();
220 }
221
222 void SecurityStateBubbleDecoration::AnimateIn(bool image_fade) {
223 image_fade_ = image_fade;
224 animation_.reset(
225 [[SecurityStateAnimation alloc] initWithOwner:this state:kAnimationIn]);
226 }
227
228 void SecurityStateBubbleDecoration::AnimateOut() {
229 if (!HasAnimatedIn())
230 return;
231
232 animation_.reset(
233 [[SecurityStateAnimation alloc] initWithOwner:this state:kAnimationOut]);
234 }
235
236 bool SecurityStateBubbleDecoration::HasAnimatedIn() const {
237 return animation_.get() && [animation_ state] == kAnimationIn &&
238 ![animation_ isRunning];
239 }
240
241 bool SecurityStateBubbleDecoration::HasAnimatedOut() const {
242 if (!animation_.get())
243 return true;
244
245 return animation_.get() && [animation_ state] == kAnimationOut &&
246 ![animation_ isRunning];
247 }
248
249 bool SecurityStateBubbleDecoration::AnimatingOut() const {
250 return animation_.get() && [animation_ state] == kAnimationOut &&
251 [animation_ isRunning];
252 }
253
254 void SecurityStateBubbleDecoration::ResetAndHide() {
255 SetVisible(false);
256 if (animation_.get() && [animation_ state] == kAnimationIn)
257 animation_.reset();
258 }
259
260 //////////////////////////////////////////////////////////////////
261 // SecurityStateBubbleDecoration::LocationBarDecoration:
262
263 CGFloat SecurityStateBubbleDecoration::GetWidthForSpace(CGFloat width) {
264 if (!ui::MaterialDesignController::IsModeMaterial())
265 return GetWidthForText(width);
266
267 CGFloat location_icon_width = location_icon_->GetWidthForSpace(width);
268 CGFloat text_width = GetWidthForText(width) - location_icon_width;
269 return (text_width * GetAnimationProgress()) + location_icon_width;
270 }
271
272 void SecurityStateBubbleDecoration::DrawInFrame(NSRect frame,
273 NSView* control_view) {
274 const NSRect decoration_frame = NSInsetRect(frame, 0.0, kBackgroundYInset);
275 CGFloat text_offset = NSMinX(decoration_frame);
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 image_size = [image_ size];
285 NSRect image_rect = decoration_frame;
286 image_rect.origin.y +=
287 std::floor((NSHeight(decoration_frame) - image_size.height) / 2.0);
288 image_rect.size = image_size;
289 [image_ drawInRect:image_rect
290 fromRect:NSZeroRect // Entire image
291 operation:NSCompositeSourceOver
292 fraction:image_alpha
293 respectFlipped:YES
294 hints:nil];
295 text_offset = NSMaxX(image_rect) + kIconLabelPadding;
296 }
297
298 // Set the text color and draw the text.
299 if (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 save_graphics_state;
309 CGFloat line_width = [control_view cr_lineWidth];
310 if (line_width < 1) {
311 NSAffineTransform* transform = [NSAffineTransform transform];
312 [transform translateXBy:0 yBy:kRetinaBaselineOffset];
313 [transform concat];
314 }
315
316 base::scoped_nsobject<NSAttributedString> text([[NSAttributedString alloc]
317 initWithString:label_
318 attributes:attributes_]);
319
320 // Calculate the text frame based on the text height and offsets.
321 NSRect text_rect = frame;
322 CGFloat textHeight = [text size].height;
323
324 text_rect.origin.x = text_offset;
325 text_rect.origin.y = roundf(NSMidY(text_rect) - textHeight / 2.0) - 1;
Robert Sesek 2016/08/18 14:43:24 Prefer std::round() [c++11]
spqchan 2016/08/18 18:37:18 Done.
326 text_rect.size.width = NSMaxX(decoration_frame) - NSMinX(text_rect);
327 text_rect.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 x_origin_offset = NSMinX(text_rect) * (1 - scale);
340 double y_origin_offset = NSMinY(text_rect) * (1 - scale);
341 double x_offset =
342 gfx::Tween::DoubleValueBetween(progress, kStartx_offset, 0);
343 double y_offset = NSHeight(text_rect) * (1 - scale) / 2.0;
344
345 [transform translateXBy:x_offset + x_origin_offset
346 yBy:y_offset + y_origin_offset];
347 [transform scaleBy:scale];
348 [transform concat];
349
350 // Draw the label.
351 [text drawInRect:text_rect];
352
353 // Draw the divider.
354 NSBezierPath* line = [NSBezierPath bezierPath];
355 [line setLineWidth:1];
Robert Sesek 2016/08/18 14:43:23 Not cr_lineWidth ?
spqchan 2016/08/18 18:37:18 Done.
356 [line moveToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
357 NSMinY(decoration_frame))];
358 [line lineToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
359 NSMaxY(decoration_frame))];
360
361 NSColor* divider_color = GetDividerColor(in_dark_mode);
362 CGFloat divider_alpha =
363 [divider_color alphaComponent] * GetAnimationProgress();
364 divider_color = [divider_color colorWithAlphaComponent:divider_alpha];
365 [divider_color set];
366 [line stroke];
367 }
368 }
369
370 void SecurityStateBubbleDecoration::DrawWithBackgroundInFrame(
371 NSRect background_frame,
372 NSRect frame,
373 NSView* control_view) {
374 if (!ui::MaterialDesignController::IsModeMaterial()) {
375 BubbleDecoration::DrawWithBackgroundInFrame(background_frame, frame,
376 control_view);
377 return;
378 }
379
380 NSRect rect = NSInsetRect(background_frame, 0, 3);
381 rect.size.width -= kRightSideMargin;
382
383 CGFloat line_width = [control_view cr_lineWidth];
384 bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
385 // Only adjust the path rect by 1/2 the line width if it's going to be
386 // stroked (so that the stroke lines fall along pixel lines).
387 if (!in_dark_mode) {
388 rect = NSInsetRect(rect, line_width / 2., line_width / 2.);
389 }
390
391 DrawInFrame(frame, control_view);
392 }
393
394 // Pass mouse operations through to location icon.
395 bool SecurityStateBubbleDecoration::IsDraggable() {
396 return location_icon_->IsDraggable();
397 }
398
399 NSPasteboard* SecurityStateBubbleDecoration::GetDragPasteboard() {
400 return location_icon_->GetDragPasteboard();
401 }
402
403 NSImage* SecurityStateBubbleDecoration::GetDragImage() {
404 return location_icon_->GetDragImage();
405 }
406
407 NSRect SecurityStateBubbleDecoration::GetDragImageFrame(NSRect frame) {
408 return GetImageRectInFrame(frame);
409 }
410
411 bool SecurityStateBubbleDecoration::OnMousePressed(NSRect frame,
412 NSPoint location) {
413 return location_icon_->OnMousePressed(frame, location);
414 }
415
416 bool SecurityStateBubbleDecoration::AcceptsMousePress() {
417 return true;
418 }
419
420 NSPoint SecurityStateBubbleDecoration::GetBubblePointInFrame(NSRect frame) {
421 NSRect image_rect = GetImageRectInFrame(frame);
422 return NSMakePoint(NSMidX(image_rect),
423 NSMaxY(image_rect) - kPageInfoBubblePointYOffset);
424 }
425
426 //////////////////////////////////////////////////////////////////
427 // SecurityStateBubbleDecoration::BubbleDecoration:
428
429 NSColor* SecurityStateBubbleDecoration::GetBackgroundBorderColor() {
430 return skia::SkColorToSRGBNSColor(
431 SkColorSetA(label_color_, 255.0 * GetAnimationProgress()));
432 }
433
434 ui::NinePartImageIds SecurityStateBubbleDecoration::GetBubbleImageIds() {
435 return IMAGE_GRID(IDR_OMNIBOX_EV_BUBBLE);
436 }
437
438 //////////////////////////////////////////////////////////////////
439 // SecurityStateBubbleDecoration, private:
440
441 CGFloat SecurityStateBubbleDecoration::GetAnimationProgress() const {
442 if (!ui::MaterialDesignController::IsModeMaterial())
443 return 1.0;
444
445 return animation_.get() ? [animation_ animationProgress] : 0.0;
446 }
447
448 CGFloat SecurityStateBubbleDecoration::GetWidthForText(CGFloat width) {
449 // Limit with to not take up too much of the available width, but
450 // also don't let it shrink too much.
451 width = std::max(width * kMaxBubbleFraction, kMinElidedBubbleWidth);
452
453 // Use the full label if it fits.
454 NSImage* image = GetImage();
455 const CGFloat all_width = GetWidthForImageAndLabel(image, full_label_);
456 if (all_width <= width) {
457 SetLabel(full_label_);
458 return all_width;
459 }
460
461 // Width left for laying out the label.
462 const CGFloat width_left = width - GetWidthForImageAndLabel(image, @"");
463
464 // Middle-elide the label to fit |width_left|. This leaves the
465 // prefix and the trailing country code in place.
466 NSString* elided_label = base::SysUTF16ToNSString(gfx::ElideText(
467 base::SysNSStringToUTF16(full_label_),
468 gfx::FontList(gfx::Font(GetFont())), width_left, gfx::ELIDE_MIDDLE));
469
470 // Use the elided label.
471 SetLabel(elided_label);
472 return GetWidthForImageAndLabel(image, elided_label);
473 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698