1 // Copyright (c) 2010 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 #include <cmath> 6 7 #import "chrome/browser/ui/cocoa/location_bar/bubble_decoration.h" 8 9 #include "base/logging.h" 10 #import "chrome/browser/ui/cocoa/image_utils.h" 11 12 namespace { 13 14 // Padding between the icon/label and bubble edges. 15 const CGFloat kBubblePadding = 3.0; 16 17 // The image needs to be in the same position as for the location 18 // icon, which implies that the bubble's padding in the Omnibox needs 19 // to differ from the location icon's. Indeed, that's how the views 20 // implementation handles the problem. This draws the bubble edge a 21 // little bit further left, which is easier but no less hacky. 22 const CGFloat kLeftSideOverdraw = 2.0; 23 24 // Omnibox corner radius is |4.0|, this needs to look tight WRT that. 25 const CGFloat kBubbleCornerRadius = 2.0; 26 27 // How far to inset the bubble from the top and bottom of the drawing 28 // frame. 29 // TODO(shess): Would be nicer to have the drawing code factor out the 30 // space outside the border, and perhaps the border. Then this could 31 // reflect the single pixel space w/in that. 32 const CGFloat kBubbleYInset = 4.0; 33 34 } // namespace 35 36 BubbleDecoration::BubbleDecoration(NSFont* font) { 37 DCHECK(font); 38 if (font) { 39 NSDictionary* attributes = 40 [NSDictionary dictionaryWithObject:font 41 forKey:NSFontAttributeName]; 42 attributes_.reset([attributes retain]); 43 } 44 } 45 46 BubbleDecoration::~BubbleDecoration() { 47 } 48 49 CGFloat BubbleDecoration::GetWidthForImageAndLabel(NSImage* image, 50 NSString* label) { 51 if (!image && !label) 52 return kOmittedWidth; 53 54 const CGFloat image_width = image ? [image size].width : 0.0; 55 if (!label) 56 return kBubblePadding + image_width; 57 58 // The bubble needs to take up an integral number of pixels. 59 // Generally -sizeWithAttributes: seems to overestimate rather than 60 // underestimate, so floor() seems to work better. 61 const CGFloat label_width = 62 std::floor([label sizeWithAttributes:attributes_].width); 63 return kBubblePadding + image_width + label_width; 64 } 65 66 NSRect BubbleDecoration::GetImageRectInFrame(NSRect frame) { 67 NSRect imageRect = NSInsetRect(frame, 0.0, kBubbleYInset); 68 if (image_) { 69 // Center the image vertically. 70 const NSSize imageSize = [image_ size]; 71 imageRect.origin.y += 72 std::floor((NSHeight(frame) - imageSize.height) / 2.0); 73 imageRect.size = imageSize; 74 } 75 return imageRect; 76 } 77 78 CGFloat BubbleDecoration::GetWidthForSpace(CGFloat width) { 79 const CGFloat all_width = GetWidthForImageAndLabel(image_, label_); 80 if (all_width <= width) 81 return all_width; 82 83 const CGFloat image_width = GetWidthForImageAndLabel(image_, nil); 84 if (image_width <= width) 85 return image_width; 86 87 return kOmittedWidth; 88 } 89 90 void BubbleDecoration::DrawInFrame(NSRect frame, NSView* control_view) { 91 const NSRect decorationFrame = NSInsetRect(frame, 0.0, kBubbleYInset); 92 93 // The inset is to put the border down the middle of the pixel. 94 NSRect bubbleFrame = NSInsetRect(decorationFrame, 0.5, 0.5); 95 bubbleFrame.origin.x -= kLeftSideOverdraw; 96 bubbleFrame.size.width += kLeftSideOverdraw; 97 NSBezierPath* path = 98 [NSBezierPath bezierPathWithRoundedRect:bubbleFrame 99 xRadius:kBubbleCornerRadius 100 yRadius:kBubbleCornerRadius]; 101 102 [background_color_ setFill]; 103 [path fill]; 104 105 [border_color_ setStroke]; 106 [path setLineWidth:1.0]; 107 [path stroke]; 108 109 NSRect imageRect = decorationFrame; 110 if (image_) { 111 // Center the image vertically. 112 const NSSize imageSize = [image_ size]; 113 imageRect.origin.y += 114 std::floor((NSHeight(decorationFrame) - imageSize.height) / 2.0); 115 imageRect.size = imageSize; 116 [image_ drawInRect:imageRect 117 fromRect:NSZeroRect // Entire image 118 operation:NSCompositeSourceOver 119 fraction:1.0 120 neverFlipped:YES]; 121 } else { 122 imageRect.size = NSZeroSize; 123 } 124 125 if (label_) { 126 NSRect textRect = decorationFrame; 127 textRect.origin.x = NSMaxX(imageRect); 128 textRect.size.width = NSMaxX(decorationFrame) - NSMinX(textRect); 129 [label_ drawInRect:textRect withAttributes:attributes_]; 130 } 131 } 132 133 NSImage* BubbleDecoration::GetImage() { 134 return image_; 135 } 136 137 void BubbleDecoration::SetImage(NSImage* image) { 138 image_.reset([image retain]); 139 } 140 141 void BubbleDecoration::SetLabel(NSString* label) { 142 // If the initializer was called with |nil|, then the code cannot 143 // process a label. 144 DCHECK(attributes_); 145 if (attributes_) 146 label_.reset([label copy]); 147 } 148 149 void BubbleDecoration::SetColors(NSColor* border_color, 150 NSColor* background_color, 151 NSColor* text_color) { 152 border_color_.reset([border_color retain]); 153 background_color_.reset([background_color retain]); 154 155 scoped_nsobject<NSMutableDictionary> attributes([attributes_ mutableCopy]); 156 [attributes setObject:text_color forKey:NSForegroundColorAttributeName]; 157 attributes_.reset([attributes copy]); 158 } 159