Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |