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 16namespace { 17 18NSBezierPath* 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. 42void 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. 57void 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|? 72class 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 86private: 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