1 // Copyright (c) 2011 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/styled_text_field_cell.h" 6 7 #include "base/logging.h" 8 #include "chrome/browser/themes/theme_service.h" 9 #import "chrome/browser/ui/cocoa/nsview_additions.h" 10 #import "chrome/browser/ui/cocoa/themed_window.h" 11 #include "grit/theme_resources.h" 12 #import "third_party/GTM/AppKit/GTMNSBezierPath+RoundRect.h" 13 #include "ui/base/resource/resource_bundle.h" 14 #include "ui/gfx/font.h" 15 16 namespace { 17 18 NSBezierPath* RectPathWithInset(StyledTextFieldCellRoundedFlags roundedFlags, 19 const NSRect frame, 20 const CGFloat inset, 21 const CGFloat outerRadius) { 22 NSRect insetFrame = NSInsetRect(frame, inset, inset); 23 24 if (outerRadius > 0.0) { 25 CGFloat leftRadius = outerRadius - inset; 26 CGFloat rightRadius = 27 (roundedFlags == StyledTextFieldCellRoundedLeft) ? 0 : leftRadius; 28 29 return [NSBezierPath gtm_bezierPathWithRoundRect:insetFrame 30 topLeftCornerRadius:leftRadius 31 topRightCornerRadius:rightRadius 32 bottomLeftCornerRadius:leftRadius 33 bottomRightCornerRadius:rightRadius]; 34 } else { 35 return [NSBezierPath bezierPathWithRect:insetFrame]; 36 } 37 } 38 39 // Similar to |NSRectFill()|, additionally sets |color| as the fill 40 // color. |outerRadius| greater than 0.0 uses rounded corners, with 41 // inset backed out of the radius. 42 void FillRectWithInset(StyledTextFieldCellRoundedFlags roundedFlags, 43 const NSRect frame, 44 const CGFloat inset, 45 const CGFloat outerRadius, 46 NSColor* color) { 47 NSBezierPath* path = 48 RectPathWithInset(roundedFlags, frame, inset, outerRadius); 49 [color setFill]; 50 [path fill]; 51 } 52 53 // Similar to |NSFrameRectWithWidth()|, additionally sets |color| as 54 // the stroke color (as opposed to the fill color). |outerRadius| 55 // greater than 0.0 uses rounded corners, with inset backed out of the 56 // radius. 57 void FrameRectWithInset(StyledTextFieldCellRoundedFlags roundedFlags, 58 const NSRect frame, 59 const CGFloat inset, 60 const CGFloat outerRadius, 61 const CGFloat lineWidth, 62 NSColor* color) { 63 const CGFloat finalInset = inset + (lineWidth / 2.0); 64 NSBezierPath* path = 65 RectPathWithInset(roundedFlags, frame, finalInset, outerRadius); 66 [color setStroke]; 67 [path setLineWidth:lineWidth]; 68 [path stroke]; 69 } 70 71 // TODO(shess): Maybe we need a |cocoa_util.h|? 72 class ScopedSaveGraphicsState { 73 public: 74 ScopedSaveGraphicsState() 75 : context_([NSGraphicsContext currentContext]) { 76 [context_ saveGraphicsState]; 77 } 78 explicit ScopedSaveGraphicsState(NSGraphicsContext* context) 79 : context_(context) { 80 [context_ saveGraphicsState]; 81 } 82 ~ScopedSaveGraphicsState() { 83 [context_ restoreGraphicsState]; 84 } 85 86 private: 87 NSGraphicsContext* context_; 88 }; 89 90 } // namespace 91 92 @implementation StyledTextFieldCell 93 94 - (CGFloat)baselineAdjust { 95 return 0.0; 96 } 97 98 - (CGFloat)cornerRadius { 99 return 0.0; 100 } 101 102 - (StyledTextFieldCellRoundedFlags)roundedFlags { 103 return StyledTextFieldCellRoundedAll; 104 } 105 106 - (BOOL)shouldDrawBezel { 107 return NO; 108 } 109 110 // Returns the same value as textCursorFrameForFrame, but does not call it 111 // directly to avoid potential infinite loops. 112 - (NSRect)textFrameForFrame:(NSRect)cellFrame { 113 return NSInsetRect(cellFrame, 0, [self baselineAdjust]); 114 } 115 116 // Returns the same value as textFrameForFrame, but does not call it directly to 117 // avoid potential infinite loops. 118 - (NSRect)textCursorFrameForFrame:(NSRect)cellFrame { 119 return NSInsetRect(cellFrame, 0, [self baselineAdjust]); 120 } 121 122 // Override to show the I-beam cursor only in the area given by 123 // |textCursorFrameForFrame:|. 124 - (void)resetCursorRect:(NSRect)cellFrame inView:(NSView *)controlView { 125 [super resetCursorRect:[self textCursorFrameForFrame:cellFrame] 126 inView:controlView]; 127 } 128 129 // For NSTextFieldCell this is the area within the borders. For our 130 // purposes, we count the info decorations as being part of the 131 // border. 132 - (NSRect)drawingRectForBounds:(NSRect)theRect { 133 return [super drawingRectForBounds:[self textFrameForFrame:theRect]]; 134 } 135 136 // TODO(shess): This code is manually drawing the cell's border area, 137 // but otherwise the cell assumes -setBordered:YES for purposes of 138 // calculating things like the editing area. This is probably 139 // incorrect. I know that this affects -drawingRectForBounds:. 140 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { 141 const CGFloat lineWidth = [controlView cr_lineWidth]; 142 const CGFloat halfLineWidth = lineWidth / 2.0; 143 144 DCHECK([controlView isFlipped]); 145 StyledTextFieldCellRoundedFlags roundedFlags = [self roundedFlags]; 146 147 // TODO(shess): This inset is also reflected by |kFieldVisualInset| 148 // in autocomplete_popup_view_mac.mm. 149 const NSRect frame = NSInsetRect(cellFrame, 0, lineWidth); 150 const CGFloat radius = [self cornerRadius]; 151 152 // Paint button background image if there is one (otherwise the border won't 153 // look right). 154 ThemeService* themeProvider = 155 static_cast<ThemeService*>([[controlView window] themeProvider]); 156 if (themeProvider) { 157 NSColor* backgroundImageColor = 158 themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND, false); 159 if (backgroundImageColor) { 160 // Set the phase to match window. 161 NSRect trueRect = [controlView convertRect:cellFrame toView:nil]; 162 NSPoint midPoint = NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect)); 163 [[NSGraphicsContext currentContext] setPatternPhase:midPoint]; 164 165 // NOTE(shess): This seems like it should be using a 0.0 inset, 166 // but AFAICT using a halfLineWidth inset is important in mixing the 167 // toolbar background and the omnibox background. 168 FillRectWithInset(roundedFlags, frame, halfLineWidth, radius, 169 backgroundImageColor); 170 } 171 172 // Draw the outer stroke (over the background). 173 BOOL active = [[controlView window] isMainWindow]; 174 NSColor* strokeColor = themeProvider->GetNSColor( 175 active ? ThemeService::COLOR_TOOLBAR_BUTTON_STROKE : 176 ThemeService::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE, 177 true); 178 FrameRectWithInset(roundedFlags, frame, 0.0, radius, lineWidth, 179 strokeColor); 180 } 181 182 // Fill interior with background color. 183 FillRectWithInset(roundedFlags, frame, lineWidth, radius, 184 [self backgroundColor]); 185 186 // Draw the shadow. For the rounded-rect case, the shadow needs to 187 // slightly turn in at the corners. |shadowFrame| is at the same 188 // midline as the inner border line on the top and left, but at the 189 // outer border line on the bottom and right. The clipping change 190 // will clip the bottom and right edges (and corner). 191 { 192 ScopedSaveGraphicsState state; 193 [RectPathWithInset(roundedFlags, frame, lineWidth, radius) addClip]; 194 const NSRect shadowFrame = 195 NSOffsetRect(frame, halfLineWidth, halfLineWidth); 196 NSColor* shadowShade = [NSColor colorWithCalibratedWhite:0.0 alpha:0.05]; 197 FrameRectWithInset(roundedFlags, shadowFrame, halfLineWidth, 198 radius - halfLineWidth, lineWidth, shadowShade); 199 } 200 201 // Draw optional bezel below bottom stroke. 202 if ([self shouldDrawBezel] && themeProvider && 203 themeProvider->UsingDefaultTheme()) { 204 205 NSColor* bezelColor = themeProvider->GetNSColor( 206 ThemeService::COLOR_TOOLBAR_BEZEL, true); 207 [[bezelColor colorWithAlphaComponent:0.5] set]; 208 NSRect bezelRect = NSMakeRect(cellFrame.origin.x, 209 NSMaxY(cellFrame) - lineWidth, 210 NSWidth(cellFrame), 211 lineWidth); 212 bezelRect = NSInsetRect(bezelRect, radius - halfLineWidth, 0.0); 213 NSRectFillUsingOperation(bezelRect, NSCompositeSourceOver); 214 } 215 216 // Draw the focus ring if needed. 217 if ([self showsFirstResponder]) { 218 NSColor* color = 219 [[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:0.5]; 220 FrameRectWithInset(roundedFlags, frame, 0.0, radius, lineWidth * 2, color); 221 } 222 223 [self drawInteriorWithFrame:cellFrame inView:controlView]; 224 } 225 226 @end 227