blob: 64fc0e4db2e0984767f24fe58a99fc94c372cbe4 [file] [log] [blame]
// Copyright (c) 2009 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/cocoa/styled_text_field_cell.h"
#include "app/resource_bundle.h"
#include "base/logging.h"
#include "chrome/browser/browser_theme_provider.h"
#import "chrome/browser/cocoa/themed_window.h"
#include "gfx/font.h"
#include "grit/theme_resources.h"
namespace {
NSBezierPath* RectPathWithInset(const NSRect frame,
const CGFloat inset,
const CGFloat outerRadius) {
const NSRect insetFrame = NSInsetRect(frame, inset, inset);
if (outerRadius > 0.0) {
return [NSBezierPath bezierPathWithRoundedRect:insetFrame
xRadius:outerRadius - inset
yRadius:outerRadius - inset];
} else {
return [NSBezierPath bezierPathWithRect:insetFrame];
}
}
// Similar to |NSRectFill()|, additionally sets |color| as the fill
// color. |outerRadius| greater than 0.0 uses rounded corners, with
// inset backed out of the radius.
void FillRectWithInset(const NSRect frame,
const CGFloat inset,
const CGFloat outerRadius,
NSColor* color) {
NSBezierPath* path = RectPathWithInset(frame, inset, outerRadius);
[color setFill];
[path fill];
}
// Similar to |NSFrameRectWithWidth()|, additionally sets |color| as
// the stroke color (as opposed to the fill color). |outerRadius|
// greater than 0.0 uses rounded corners, with inset backed out of the
// radius.
void FrameRectWithInset(const NSRect frame,
const CGFloat inset,
const CGFloat outerRadius,
const CGFloat lineWidth,
NSColor* color) {
const CGFloat finalInset = inset + (lineWidth / 2.0);
NSBezierPath* path = RectPathWithInset(frame, finalInset, outerRadius);
[color setStroke];
[path setLineWidth:lineWidth];
[path stroke];
}
// TODO(shess): Maybe we need a |cocoa_util.h|?
class ScopedSaveGraphicsState {
public:
ScopedSaveGraphicsState()
: context_([NSGraphicsContext currentContext]) {
[context_ saveGraphicsState];
}
explicit ScopedSaveGraphicsState(NSGraphicsContext* context)
: context_(context) {
[context_ saveGraphicsState];
}
~ScopedSaveGraphicsState() {
[context_ restoreGraphicsState];
}
private:
NSGraphicsContext* context_;
};
} // namespace
@implementation StyledTextFieldCell
- (CGFloat)baselineAdjust {
return 0.0;
}
- (CGFloat)cornerRadius {
return 0.0;
}
// Returns the same value as textCursorFrameForFrame, but does not call it
// directly to avoid potential infinite loops.
- (NSRect)textFrameForFrame:(NSRect)cellFrame {
return NSInsetRect(cellFrame, 0, [self baselineAdjust]);
}
// Returns the same value as textFrameForFrame, but does not call it directly to
// avoid potential infinite loops.
- (NSRect)textCursorFrameForFrame:(NSRect)cellFrame {
return NSInsetRect(cellFrame, 0, [self baselineAdjust]);
}
// Override to show the I-beam cursor only in the area given by
// |textCursorFrameForFrame:|.
- (void)resetCursorRect:(NSRect)cellFrame inView:(NSView *)controlView {
[super resetCursorRect:[self textCursorFrameForFrame:cellFrame]
inView:controlView];
}
// For NSTextFieldCell this is the area within the borders. For our
// purposes, we count the info decorations as being part of the
// border.
- (NSRect)drawingRectForBounds:(NSRect)theRect {
return [super drawingRectForBounds:[self textFrameForFrame:theRect]];
}
// TODO(shess): This code is manually drawing the cell's border area,
// but otherwise the cell assumes -setBordered:YES for purposes of
// calculating things like the editing area. This is probably
// incorrect. I know that this affects -drawingRectForBounds:.
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
DCHECK([controlView isFlipped]);
// TODO(shess): This inset is also reflected by |kFieldVisualInset|
// in autocomplete_popup_view_mac.mm.
const NSRect frame = NSInsetRect(cellFrame, 0, 1);
const CGFloat radius = [self cornerRadius];
// Paint button background image if there is one (otherwise the border won't
// look right).
ThemeProvider* themeProvider = [[controlView window] themeProvider];
if (themeProvider) {
NSColor* backgroundImageColor =
themeProvider->GetNSImageColorNamed(IDR_THEME_BUTTON_BACKGROUND, false);
if (backgroundImageColor) {
// Set the phase to match window.
NSRect trueRect = [controlView convertRect:cellFrame toView:nil];
NSPoint midPoint = NSMakePoint(NSMinX(trueRect), NSMaxY(trueRect));
[[NSGraphicsContext currentContext] setPatternPhase:midPoint];
// NOTE(shess): This seems like it should be using a 0.0 inset,
// but AFAICT using a 0.5 inset is important in mixing the
// toolbar background and the omnibox background.
FillRectWithInset(frame, 0.5, radius, backgroundImageColor);
}
// Draw the outer stroke (over the background).
BOOL active = [[controlView window] isMainWindow];
NSColor* strokeColor = themeProvider->GetNSColor(
active ? BrowserThemeProvider::COLOR_TOOLBAR_BUTTON_STROKE :
BrowserThemeProvider::COLOR_TOOLBAR_BUTTON_STROKE_INACTIVE,
true);
FrameRectWithInset(frame, 0.0, radius, 1.0, strokeColor);
}
// Fill interior with background color.
FillRectWithInset(frame, 1.0, radius, [self backgroundColor]);
// Draw the shadow. For the rounded-rect case, the shadow needs to
// slightly turn in at the corners. |shadowFrame| is at the same
// midline as the inner border line on the top and left, but at the
// outer border line on the bottom and right. The clipping change
// will clip the bottom and right edges (and corner).
{
ScopedSaveGraphicsState state;
[RectPathWithInset(frame, 1.0, radius) addClip];
const NSRect shadowFrame = NSOffsetRect(frame, 0.5, 0.5);
NSColor* shadowShade = [NSColor colorWithCalibratedWhite:0.0 alpha:0.05];
FrameRectWithInset(shadowFrame, 0.5, radius - 0.5, 1.0, shadowShade);
}
// Draw the focus ring if needed.
if ([self showsFirstResponder]) {
NSColor* color =
[[NSColor keyboardFocusIndicatorColor] colorWithAlphaComponent:0.5];
FrameRectWithInset(frame, 0.0, radius, 2.0, color);
}
[self drawInteriorWithFrame:cellFrame inView:controlView];
}
@end