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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/cocoa/location_bar/verbose_state_bubble_decoration.mm
diff --git a/chrome/browser/ui/cocoa/location_bar/verbose_state_bubble_decoration.mm b/chrome/browser/ui/cocoa/location_bar/verbose_state_bubble_decoration.mm
new file mode 100644
index 0000000000000000000000000000000000000000..be2979ef1c95f36b0eda914bb61f8423ca47d233
--- /dev/null
+++ b/chrome/browser/ui/cocoa/location_bar/verbose_state_bubble_decoration.mm
@@ -0,0 +1,472 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/location_bar/verbose_state_bubble_decoration.h"
+
+#import "base/mac/mac_util.h"
+#include "base/strings/sys_string_conversions.h"
+#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
+#import "chrome/browser/ui/cocoa/location_bar/location_icon_decoration.h"
+#import "chrome/browser/ui/cocoa/themed_window.h"
+#include "grit/theme_resources.h"
+#include "skia/ext/skia_utils_mac.h"
+#import "ui/base/cocoa/nsview_additions.h"
+#include "ui/base/material_design/material_design_controller.h"
+#include "ui/gfx/animation/tween.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/image/image_skia_util_mac.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+#include "ui/gfx/text_elider.h"
+
+namespace {
+
+// This is used to increase the right margin of this decoration.
+const CGFloat kRightSideMargin = 1.0;
+
+// Padding between the icon and label.
+CGFloat kIconLabelPadding = 4.0;
+
+// Inset for the background.
+const CGFloat kBackgroundYInset = 4.0;
+
+// The offset of the text's baseline on a retina screen.
+const CGFloat kRetinaBaselineOffset = 0.5;
+
+// TODO(shess): In general, decorations that don't fit in the
+// available space are omitted. This one never goes to omitted, it
+// sticks at 150px, which AFAICT follows the Windows code. Since the
+// Layout() code doesn't take this into account, it's possible the
+// control could end up with display artifacts, though things still
+// work (and don't crash).
+// 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
+
+// The info-bubble point should look like it points to the bottom of the lock
+// icon. Determined with Pixie.app.
+const CGFloat kPageInfoBubblePointYOffset = 6.0;
+
+// Minimum acceptable width for the ev bubble.
+const CGFloat kMinElidedBubbleWidth = 150.0;
+
+// Maximum amount of available space to make the bubble, subject to
+// |kMinElidedBubbleWidth|.
+const float kMaxBubbleFraction = 0.5;
+
+// The base text color used for non-MD. The color tuples are stolen from
+// location_bar_view_gtk.cc.
+const SkColor kBaseTextColor = SkColorSetRGB(0x07, 0x95, 0x00);
+
+// Duration of animation in ms.
+const NSTimeInterval kInAnimationDuration = 0.330;
+const NSTimeInterval kOutAnimationDuration = 0.250;
+
+// Interval of the animation timer, 60Hz.
+const NSTimeInterval kAnimationInterval = 1.0 / 60.0;
+
+// Transformation values at the beginning of the animation.
+const CGFloat kStartScale = 0.25;
+const CGFloat kStartXOffset = -15.0;
+
+// Different states of the animation. In |kAnimationIn|, the verbose state
+// animates in. In |kAnimationOut|, the verbose state animates out.
+enum AnimationState {
+ kAnimationIn,
+ kAnimationOut,
+};
+
+} // namespace
+
+@interface VerboseAnimation : NSObject {
+ @private
+ VerboseStateBubbleDecoration* owner_; // Weak, owns this.
+
+ // Counter, [0..1], with aninmation progress.
+ double progress_;
+
+ // Animation timer. Owns this, owned by the run loop.
+ NSTimer* timer_;
+
+ // The timestamp of the last time timerFired: was called.
+ NSTimeInterval frameTimestamp_;
+}
+
+@property(readonly, nonatomic) AnimationState state;
+
+// Designated initializer. |owner| must not be nil. Animation timer will start
+// as soon as the object is created.
+- (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.
+ state:(AnimationState)state;
+
+// Call when |owner| is going away or the animation needs to be stopped.
+// Ensures that any dangling references are cleared. Can be called multiple
+// times.
+- (void)stopAnimation;
+
+// Returns the current progress of the animation with the curve applied to it.
+// A value in the range of [0, 1].
+- (double)animationProgress;
+
+// Returns true if the animation is running.
+- (BOOL)isRunning;
+
+@end
+
+@implementation VerboseAnimation
+
+@synthesize state = state_;
+
+- (id)initWithOwner:(VerboseStateBubbleDecoration*)owner
+ state:(AnimationState)state {
+ DCHECK(ui::MaterialDesignController::IsModeMaterial());
+
+ if ((self = [super init])) {
+ owner_ = owner;
+ frameTimestamp_ = CACurrentMediaTime();
+ timer_ = [NSTimer scheduledTimerWithTimeInterval:kAnimationInterval
+ target:self
+ selector:@selector(timerFired:)
+ userInfo:nil
+ repeats:YES];
+ [[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.
+
+ state_ = state;
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ DCHECK(!timer_);
+ [super dealloc];
+}
+
+// Clear weak references and stop the timer.
+- (void)stopAnimation {
+ [timer_ invalidate];
+ timer_ = nil;
+}
+
+- (BOOL)isRunning {
+ return timer_ != nil;
+}
+
+- (void)timerFired:(NSTimer*)timer {
+ CGFloat currentTime = CACurrentMediaTime();
+ CGFloat elapsedTime = currentTime - frameTimestamp_;
+ frameTimestamp_ = currentTime;
+
+ // Increment animation progress, normalized to [0..1].
+ CGFloat duration =
+ state_ == kAnimationIn ? kInAnimationDuration : kOutAnimationDuration;
+ progress_ += elapsedTime / duration;
+ progress_ = std::min(progress_, 1.0);
+
+ // Stop timer if it has reached the end of its life.
+ if (progress_ >= 1.0)
+ [self stopAnimation];
+
+ owner_->OnAnimationProgressed();
+}
+
+- (double)animationProgress {
+ if (state_ == kAnimationIn)
+ return gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN, progress_);
+
+ return 1 - gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN_EXPO,
+ progress_);
+}
+
+@end
+
+//////////////////////////////////////////////////////////////////
+// VerboseStateBubbleDecoration, public:
+
+VerboseStateBubbleDecoration::VerboseStateBubbleDecoration(
+ LocationIconDecoration* location_icon,
+ LocationBarViewMac* owner)
+ : location_icon_(location_icon),
+ label_color_(gfx::kGoogleGreen700),
+ image_fade_(true),
+ owner_(owner) {
+ if (ui::MaterialDesignController::IsModeMaterial()) {
+ // On Retina the text label is 1px above the Omnibox textfield's text
+ // baseline. If the Omnibox textfield also drew the label the baselines
+ // would align.
+ SetRetinaBaselineOffset(kRetinaBaselineOffset);
+ } else {
+ SetTextColor(skia::SkColorToSRGBNSColor(kBaseTextColor));
+ }
+}
+
+VerboseStateBubbleDecoration::~VerboseStateBubbleDecoration() {
+ // Just in case the timer is still holding onto the animation object, force
+ // cleanup so it can't get back to |this|.
+ [animation_ stopAnimation];
+ animation_.reset();
+}
+
+void VerboseStateBubbleDecoration::SetFullLabel(NSString* label) {
+ full_label_.reset([label retain]);
+ SetLabel(full_label_);
+}
+
+void VerboseStateBubbleDecoration::SetLabelColor(SkColor color) {
+ label_color_ = color;
+}
+
+void VerboseStateBubbleDecoration::OnAnimationProgressed() {
+ owner_->Layout();
+}
+
+void VerboseStateBubbleDecoration::AnimateIn(bool image_fade) {
+ image_fade_ = image_fade;
+ animation_.reset(
+ [[VerboseAnimation alloc] initWithOwner:this state:kAnimationIn]);
+}
+
+void VerboseStateBubbleDecoration::AnimateOut() {
+ if (!HasAnimatedIn())
+ return;
+
+ animation_.reset(
+ [[VerboseAnimation alloc] initWithOwner:this state:kAnimationOut]);
+}
+
+bool VerboseStateBubbleDecoration::HasAnimatedIn() const {
+ return animation_.get() && [animation_ state] == kAnimationIn &&
+ ![animation_ isRunning];
+}
+
+bool VerboseStateBubbleDecoration::HasAnimatedOut() const {
+ if (!animation_.get())
+ return true;
+
+ return animation_.get() && [animation_ state] == kAnimationOut &&
+ ![animation_ isRunning];
+}
+
+bool VerboseStateBubbleDecoration::AnimatingOut() const {
+ return animation_.get() && [animation_ state] == kAnimationOut &&
+ [animation_ isRunning];
+}
+
+void VerboseStateBubbleDecoration::ResetAndHide() {
+ SetVisible(false);
+ if (animation_.get() && [animation_ state] == kAnimationIn)
+ animation_.reset();
+}
+
+//////////////////////////////////////////////////////////////////
+// VerboseStateBubbleDecoration::LocationBarDecoration:
+
+CGFloat VerboseStateBubbleDecoration::GetWidthForSpace(CGFloat width) {
+ if (!ui::MaterialDesignController::IsModeMaterial())
+ return GetWidthForText(width);
+
+ CGFloat locationIconWidth = location_icon_->GetWidthForSpace(width);
+ CGFloat textWidth = GetWidthForText(width) - locationIconWidth;
+ return (textWidth * GetAnimationProgress()) + locationIconWidth;
+}
+
+void VerboseStateBubbleDecoration::DrawInFrame(NSRect frame,
+ NSView* control_view) {
+ const NSRect decoration_frame = NSInsetRect(frame, 0.0, kBackgroundYInset);
+ 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.
+ if (image_) {
+ // The image should fade in if we're animating in.
+ CGFloat image_alpha =
+ image_fade_ && animation_.get() && [animation_ state] == kAnimationIn
+ ? GetAnimationProgress()
+ : 1.0;
+
+ // Center the image vertically.
+ const NSSize imageSize = [image_ size];
+ NSRect imageRect = decoration_frame;
+ imageRect.origin.y +=
+ std::floor((NSHeight(decoration_frame) - imageSize.height) / 2.0);
+ imageRect.size = imageSize;
+ [image_ drawInRect:imageRect
+ fromRect:NSZeroRect // Entire image
+ operation:NSCompositeSourceOver
+ fraction:image_alpha
+ respectFlipped:YES
+ hints:nil];
+ textOffset = NSMaxX(imageRect) + kIconLabelPadding;
+ }
+
+ // Set the text color and draw the text.
+ if (full_label_) {
+ bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
+ NSColor* text_color =
+ in_dark_mode ? skia::SkColorToSRGBNSColor(kMaterialDarkModeTextColor)
+ : GetBackgroundBorderColor();
+ SetTextColor(text_color);
+
+ // Transform the coordinate system to adjust the baseline on Retina.
+ // This is the only way to get fractional adjustments.
+ gfx::ScopedNSGraphicsContextSaveGState saveGraphicsState;
+ CGFloat lineWidth = [control_view cr_lineWidth];
+ if (lineWidth < 1) {
+ NSAffineTransform* transform = [NSAffineTransform transform];
+ [transform translateXBy:0 yBy:kRetinaBaselineOffset];
+ [transform concat];
+ }
+
+ 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.
+ initWithString:full_label_
+ attributes:attributes_]);
+
+ // Calculate the text frame based on the text height and offsets.
+ NSRect textRect = frame;
+ CGFloat textHeight = [str size].height;
+
+ textRect.origin.x = textOffset;
+ textRect.origin.y = roundf(NSMidY(textRect) - textHeight / 2.0) - 1;
+ textRect.size.width = NSMaxX(decoration_frame) - NSMinX(textRect);
+ textRect.size.height = textHeight;
+
+ NSAffineTransform* transform = [NSAffineTransform transform];
+ CGFloat progress = GetAnimationProgress();
+
+ // Apply transformations so that the text animation:
+ // - Scales from 0.75 to 1.
+ // - Translates the X position to its origin after it got scaled, and
+ // before moving in a position from from -15 to 0
+ // - Translates the Y position so that the text is centered vertically.
+ double scale = gfx::Tween::DoubleValueBetween(progress, kStartScale, 1.0);
+
+ double xOriginOffset = NSMinX(textRect) * (1 - scale);
+ double yOriginOffset = NSMinY(textRect) * (1 - scale);
+ double xOffset = gfx::Tween::DoubleValueBetween(progress, kStartXOffset, 0);
+ double yOffset = NSHeight(textRect) * (1 - scale) / 2.0;
+
+ [transform translateXBy:xOffset + xOriginOffset
+ yBy:yOffset + yOriginOffset];
+ [transform scaleBy:scale];
+ [transform concat];
+
+ // Draw the label.
+ [str drawInRect:textRect];
+
+ // Draw the divider.
+ NSBezierPath* line = [NSBezierPath bezierPath];
+ [line setLineWidth:1];
+ [line moveToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
+ NSMinY(decoration_frame))];
+ [line lineToPoint:NSMakePoint(NSMaxX(decoration_frame) - DividerPadding(),
+ NSMaxY(decoration_frame))];
+
+ NSColor* divider_color = GetDividerColor(in_dark_mode);
+ CGFloat divider_alpha =
+ [divider_color alphaComponent] * GetAnimationProgress();
+ divider_color = [divider_color colorWithAlphaComponent:divider_alpha];
+ [divider_color set];
+ [line stroke];
+ }
+}
+
+void VerboseStateBubbleDecoration::DrawWithBackgroundInFrame(
+ NSRect background_frame,
+ NSRect frame,
+ NSView* control_view) {
+ if (!ui::MaterialDesignController::IsModeMaterial()) {
+ BubbleDecoration::DrawWithBackgroundInFrame(background_frame, frame,
+ control_view);
+ return;
+ }
+
+ NSRect rect = NSInsetRect(background_frame, 0, 3);
+ rect.size.width -= kRightSideMargin;
+
+ CGFloat line_width = [control_view cr_lineWidth];
+ bool in_dark_mode = [[control_view window] inIncognitoModeWithSystemTheme];
+ // Only adjust the path rect by 1/2 the line width if it's going to be
+ // stroked (so that the stroke lines fall along pixel lines).
+ if (!in_dark_mode) {
+ rect = NSInsetRect(rect, line_width / 2., line_width / 2.);
+ }
+
+ DrawInFrame(frame, control_view);
+}
+
+// Pass mouse operations through to location icon.
+bool VerboseStateBubbleDecoration::IsDraggable() {
+ return location_icon_->IsDraggable();
+}
+
+NSPasteboard* VerboseStateBubbleDecoration::GetDragPasteboard() {
+ return location_icon_->GetDragPasteboard();
+}
+
+NSImage* VerboseStateBubbleDecoration::GetDragImage() {
+ return location_icon_->GetDragImage();
+}
+
+NSRect VerboseStateBubbleDecoration::GetDragImageFrame(NSRect frame) {
+ return GetImageRectInFrame(frame);
+}
+
+bool VerboseStateBubbleDecoration::OnMousePressed(NSRect frame,
+ NSPoint location) {
+ return location_icon_->OnMousePressed(frame, location);
+}
+
+bool VerboseStateBubbleDecoration::AcceptsMousePress() {
+ return true;
+}
+
+NSPoint VerboseStateBubbleDecoration::GetBubblePointInFrame(NSRect frame) {
+ NSRect image_rect = GetImageRectInFrame(frame);
+ return NSMakePoint(NSMidX(image_rect),
+ NSMaxY(image_rect) - kPageInfoBubblePointYOffset);
+}
+
+//////////////////////////////////////////////////////////////////
+// VerboseStateBubbleDecoration::BubbleDecoration:
+
+NSColor* VerboseStateBubbleDecoration::GetBackgroundBorderColor() {
+ return skia::SkColorToSRGBNSColor(
+ SkColorSetA(label_color_, 255.0 * GetAnimationProgress()));
+}
+
+ui::NinePartImageIds VerboseStateBubbleDecoration::GetBubbleImageIds() {
+ return IMAGE_GRID(IDR_OMNIBOX_EV_BUBBLE);
+}
+
+//////////////////////////////////////////////////////////////////
+// VerboseStateBubbleDecoration, private:
+
+CGFloat VerboseStateBubbleDecoration::GetAnimationProgress() const {
+ if (!ui::MaterialDesignController::IsModeMaterial())
+ return 1.0;
+
+ return animation_.get() ? [animation_ animationProgress] : 0.0;
+}
+
+CGFloat VerboseStateBubbleDecoration::GetWidthForText(CGFloat width) {
+ // Limit with to not take up too much of the available width, but
+ // also don't let it shrink too much.
+ width = std::max(width * kMaxBubbleFraction, kMinElidedBubbleWidth);
+
+ // Use the full label if it fits.
+ NSImage* image = GetImage();
+ const CGFloat all_width = GetWidthForImageAndLabel(image, full_label_);
+ if (all_width <= width) {
+ SetLabel(full_label_);
+ return all_width;
+ }
+
+ // Width left for laying out the label.
+ const CGFloat width_left = width - GetWidthForImageAndLabel(image, @"");
+
+ // Middle-elide the label to fit |width_left|. This leaves the
+ // prefix and the trailing country code in place.
+ NSString* elided_label = base::SysUTF16ToNSString(gfx::ElideText(
+ base::SysNSStringToUTF16(full_label_),
+ gfx::FontList(gfx::Font(GetFont())), width_left, gfx::ELIDE_MIDDLE));
+
+ // Use the elided label.
+ SetLabel(elided_label);
+ return GetWidthForImageAndLabel(image, elided_label);
+}

Powered by Google App Engine
This is Rietveld 408576698