Home | History | Annotate | Download | only in cocoa
      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