omnibox_result_view.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright (c) 2012 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// For WinDDK ATL compatibility, these ATL headers must come first.
6#include "build/build_config.h"
7#if defined(OS_WIN)
8#include <atlbase.h>  // NOLINT
9#include <atlwin.h>  // NOLINT
10#endif
11
12#include "chrome/browser/ui/views/omnibox/omnibox_result_view.h"
13
14#include <algorithm>  // NOLINT
15
16#include "base/i18n/bidi_line_iterator.h"
17#include "base/memory/scoped_vector.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_util.h"
20#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
21#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
22#include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
23#include "grit/generated_resources.h"
24#include "grit/theme_resources.h"
25#include "ui/base/l10n/l10n_util.h"
26#include "ui/base/theme_provider.h"
27#include "ui/gfx/canvas.h"
28#include "ui/gfx/color_utils.h"
29#include "ui/gfx/image/image.h"
30#include "ui/gfx/range/range.h"
31#include "ui/gfx/render_text.h"
32#include "ui/gfx/text_elider.h"
33#include "ui/gfx/text_utils.h"
34#include "ui/native_theme/native_theme.h"
35
36#if defined(OS_WIN)
37#include "ui/native_theme/native_theme_win.h"
38#endif
39
40#if defined(USE_AURA)
41#include "ui/native_theme/native_theme_aura.h"
42#endif
43
44namespace {
45
46// The minimum distance between the top and bottom of the {icon|text} and the
47// top or bottom of the row.
48const int kMinimumIconVerticalPadding = 2;
49const int kMinimumTextVerticalPadding = 3;
50
51}  // namespace
52
53////////////////////////////////////////////////////////////////////////////////
54// OmniboxResultView, public:
55
56// This class is a utility class for calculations affected by whether the result
57// view is horizontally mirrored.  The drawing functions can be written as if
58// all drawing occurs left-to-right, and then use this class to get the actual
59// coordinates to begin drawing onscreen.
60class OmniboxResultView::MirroringContext {
61 public:
62  MirroringContext() : center_(0), right_(0) {}
63
64  // Tells the mirroring context to use the provided range as the physical
65  // bounds of the drawing region.  When coordinate mirroring is needed, the
66  // mirror point will be the center of this range.
67  void Initialize(int x, int width) {
68    center_ = x + width / 2;
69    right_ = x + width;
70  }
71
72  // Given a logical range within the drawing region, returns the coordinate of
73  // the possibly-mirrored "left" side.  (This functions exactly like
74  // View::MirroredLeftPointForRect().)
75  int mirrored_left_coord(int left, int right) const {
76    return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left;
77  }
78
79  // Given a logical coordinate within the drawing region, returns the remaining
80  // width available.
81  int remaining_width(int x) const {
82    return right_ - x;
83  }
84
85 private:
86  int center_;
87  int right_;
88
89  DISALLOW_COPY_AND_ASSIGN(MirroringContext);
90};
91
92OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView* model,
93                                     int model_index,
94                                     LocationBarView* location_bar_view,
95                                     const gfx::FontList& font_list)
96    : edge_item_padding_(LocationBarView::GetItemPadding()),
97      item_padding_(LocationBarView::GetItemPadding()),
98      minimum_text_vertical_padding_(kMinimumTextVerticalPadding),
99      model_(model),
100      model_index_(model_index),
101      location_bar_view_(location_bar_view),
102      font_list_(font_list),
103      font_height_(
104          std::max(font_list.GetHeight(),
105                   font_list.DeriveWithStyle(gfx::Font::BOLD).GetHeight())),
106      mirroring_context_(new MirroringContext()),
107      keyword_icon_(new views::ImageView()),
108      animation_(new gfx::SlideAnimation(this)) {
109  CHECK_GE(model_index, 0);
110  if (default_icon_size_ == 0) {
111    default_icon_size_ =
112        location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
113            AutocompleteMatch::TypeToIcon(
114                AutocompleteMatchType::URL_WHAT_YOU_TYPED))->width();
115  }
116  if (ellipsis_width_ == 0)
117    ellipsis_width_ = CreateRenderText(gfx::kEllipsisUTF16)->GetContentWidth();
118  keyword_icon_->set_owned_by_client();
119  keyword_icon_->EnableCanvasFlippingForRTLUI(true);
120  keyword_icon_->SetImage(GetKeywordIcon());
121  keyword_icon_->SizeToPreferredSize();
122}
123
124OmniboxResultView::~OmniboxResultView() {
125}
126
127SkColor OmniboxResultView::GetColor(
128    ResultViewState state,
129    ColorKind kind) const {
130  const ui::NativeTheme* theme = GetNativeTheme();
131#if defined(OS_WIN)
132  if (theme == ui::NativeThemeWin::instance()) {
133    static bool win_initialized = false;
134    static SkColor win_colors[NUM_STATES][NUM_KINDS];
135    if (!win_initialized) {
136      win_colors[NORMAL][BACKGROUND] = color_utils::GetSysSkColor(COLOR_WINDOW);
137      win_colors[SELECTED][BACKGROUND] =
138          color_utils::GetSysSkColor(COLOR_HIGHLIGHT);
139      win_colors[NORMAL][TEXT] = color_utils::GetSysSkColor(COLOR_WINDOWTEXT);
140      win_colors[SELECTED][TEXT] =
141          color_utils::GetSysSkColor(COLOR_HIGHLIGHTTEXT);
142      CommonInitColors(theme, win_colors);
143      win_initialized = true;
144    }
145    return win_colors[state][kind];
146  }
147#endif
148  static bool initialized = false;
149  static SkColor colors[NUM_STATES][NUM_KINDS];
150  if (!initialized) {
151    colors[NORMAL][BACKGROUND] = theme->GetSystemColor(
152        ui::NativeTheme::kColorId_TextfieldDefaultBackground);
153    colors[NORMAL][TEXT] = theme->GetSystemColor(
154        ui::NativeTheme::kColorId_TextfieldDefaultColor);
155    colors[NORMAL][URL] = SkColorSetARGB(0xff, 0x00, 0x99, 0x33);
156    colors[SELECTED][BACKGROUND] = theme->GetSystemColor(
157        ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused);
158    colors[SELECTED][TEXT] = theme->GetSystemColor(
159        ui::NativeTheme::kColorId_TextfieldSelectionColor);
160    colors[SELECTED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22);
161    colors[HOVERED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22);
162    CommonInitColors(theme, colors);
163    initialized = true;
164  }
165  return colors[state][kind];
166}
167
168void OmniboxResultView::SetMatch(const AutocompleteMatch& match) {
169  match_ = match;
170  match_contents_render_text_.reset();
171  animation_->Reset();
172
173  AutocompleteMatch* associated_keyword_match = match_.associated_keyword.get();
174  if (associated_keyword_match) {
175    keyword_icon_->SetImage(GetKeywordIcon());
176
177    if (!keyword_icon_->parent())
178      AddChildView(keyword_icon_.get());
179  } else if (keyword_icon_->parent()) {
180    RemoveChildView(keyword_icon_.get());
181  }
182
183  render_associated_keyword_match_ =
184      associated_keyword_match && keyword_icon_->x() <= icon_bounds_.right();
185  RenderMatchContents();
186  Layout();
187}
188
189void OmniboxResultView::ShowKeyword(bool show_keyword) {
190  if (show_keyword)
191    animation_->Show();
192  else
193    animation_->Hide();
194}
195
196void OmniboxResultView::Invalidate() {
197  keyword_icon_->SetImage(GetKeywordIcon());
198  match_contents_render_text_.reset();
199  SchedulePaint();
200}
201
202gfx::Size OmniboxResultView::GetPreferredSize() {
203  return gfx::Size(0, std::max(
204      default_icon_size_ + (kMinimumIconVerticalPadding * 2),
205      GetTextHeight() + (minimum_text_vertical_padding_ * 2)));
206}
207
208////////////////////////////////////////////////////////////////////////////////
209// OmniboxResultView, protected:
210
211OmniboxResultView::ResultViewState OmniboxResultView::GetState() const {
212  if (model_->IsSelectedIndex(model_index_))
213    return SELECTED;
214  return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
215}
216
217int OmniboxResultView::GetTextHeight() const {
218  return font_height_;
219}
220
221void OmniboxResultView::PaintMatch(gfx::Canvas* canvas, int x) {
222  const AutocompleteMatch& match = display_match();
223  int y = text_bounds_.y();
224  int contents_max_width, description_max_width;
225
226  gfx::RenderText* contents_render_text = RenderMatchContents();
227
228  const base::string16& separator =
229      l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
230  scoped_ptr<gfx::RenderText> separator_render_text(
231      CreateRenderText(separator));
232  separator_render_text->SetColor(GetColor(GetState(), DIMMED_TEXT));
233
234  scoped_ptr<gfx::RenderText> description_render_text(
235      CreateRenderText(match.description));
236  ApplyClassifications(description_render_text.get(), match.description_class,
237                       true);
238
239  ComputeMatchMaxWidths(
240      contents_render_text->GetContentWidth(),
241      separator_render_text->GetContentWidth(),
242      description_render_text->GetContentWidth(),
243      mirroring_context_->remaining_width(x),
244      !AutocompleteMatch::IsSearchType(match.type),
245      &contents_max_width,
246      &description_max_width);
247
248  x = DrawRenderText(canvas, contents_render_text, true, x, y,
249                     contents_max_width);
250
251  if (description_max_width != 0) {
252      x = DrawRenderText(canvas, separator_render_text.get(), false, x, y, -1);
253      DrawRenderText(canvas, description_render_text.get(), false, x, y,
254                     description_max_width);
255  }
256}
257
258int OmniboxResultView::DrawRenderText(
259    gfx::Canvas* canvas,
260    gfx::RenderText* render_text,
261    bool contents,
262    int x,
263    int y,
264    int max_width) const {
265  DCHECK(!render_text->text().empty());
266
267  int remaining_width = mirroring_context_->remaining_width(x);
268  if (max_width >= 0)
269      remaining_width = std::min(remaining_width, max_width);
270  const int content_width = render_text->GetContentWidth();
271  int right_x = x + std::min(remaining_width, content_width);
272
273  const AutocompleteMatch& match = display_match();
274
275  // Infinite suggestions should appear with the leading ellipses vertically
276  // stacked.
277  if (contents &&
278      (match.type == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE)) {
279    // When the directionality of suggestion doesn't match the UI, we try to
280    // vertically stack the ellipsis by restricting the end edge (right_x).
281    const bool is_ui_rtl = base::i18n::IsRTL();
282    const bool is_match_contents_rtl =
283        (render_text->GetTextDirection() == base::i18n::RIGHT_TO_LEFT);
284    const int offset = GetDisplayOffset(is_ui_rtl, is_match_contents_rtl);
285
286    scoped_ptr<gfx::RenderText> prefix_render_text(
287        CreateRenderText(base::UTF8ToUTF16(
288            match.GetAdditionalInfo("match contents prefix"))));
289    const int prefix_width = prefix_render_text->GetContentWidth();
290    int prefix_x = x;
291
292    const int max_match_contents_width = model_->max_match_contents_width();
293
294    if (is_ui_rtl != is_match_contents_rtl) {
295      // RTL infinite suggestions appear near the left edge in LTR UI, while LTR
296      // infinite suggestions appear near the right edge in RTL UI. This is
297      // against the natural horizontal alignment of the text. We reduce the
298      // width of the box for suggestion display, so that the suggestions appear
299      // in correct confines.  This reduced width allows us to modify the text
300      // alignment (see below).
301      right_x = x + std::min(remaining_width - prefix_width,
302                             std::max(offset, max_match_contents_width));
303      prefix_x = right_x;
304      // We explicitly set the horizontal alignment so that when LTR suggestions
305      // show in RTL UI (or vice versa), their ellipses appear stacked in a
306      // single column.
307      render_text->SetHorizontalAlignment(
308          is_match_contents_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
309    } else {
310      // If the dropdown is wide enough, place the ellipsis at the position
311      // where the omitted text would have ended. Otherwise reduce the offset of
312      // the ellipsis such that the widest suggestion reaches the end of the
313      // dropdown.
314      const int start_offset = std::max(prefix_width,
315          std::min(remaining_width - (prefix_width + max_match_contents_width),
316                   offset));
317      right_x = x + std::min(remaining_width, start_offset + content_width);
318      x += start_offset;
319      prefix_x = x - prefix_width;
320    }
321    prefix_render_text->SetDirectionalityMode(is_match_contents_rtl ?
322        gfx::DIRECTIONALITY_FORCE_RTL : gfx::DIRECTIONALITY_FORCE_LTR);
323    prefix_render_text->SetHorizontalAlignment(
324          is_match_contents_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
325    prefix_render_text->SetDisplayRect(gfx::Rect(
326          mirroring_context_->mirrored_left_coord(
327              prefix_x, prefix_x + prefix_width), y,
328          prefix_width, height()));
329    prefix_render_text->Draw(canvas);
330  }
331
332  // Set the display rect to trigger eliding.
333  render_text->SetDisplayRect(gfx::Rect(
334      mirroring_context_->mirrored_left_coord(x, right_x), y,
335      right_x - x, height()));
336  render_text->Draw(canvas);
337  return right_x;
338}
339
340scoped_ptr<gfx::RenderText> OmniboxResultView::CreateRenderText(
341    const base::string16& text) const {
342  scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
343  render_text->SetFontList(font_list_);
344  render_text->SetText(text);
345  render_text->SetElideBehavior(gfx::ELIDE_AT_END);
346  render_text->SetCursorEnabled(false);
347  return render_text.Pass();
348}
349
350void OmniboxResultView::ApplyClassifications(
351    gfx::RenderText* render_text,
352    const ACMatchClassifications& classifications,
353    bool force_dim) const {
354  const size_t text_length = render_text->text().length();
355  for (size_t i = 0; i < classifications.size(); ++i) {
356    const size_t text_start = classifications[i].offset;
357    if (text_start >= text_length)
358      break;
359
360    const size_t text_end = (i < (classifications.size() - 1)) ?
361        std::min(classifications[i + 1].offset, text_length) :
362        text_length;
363    const gfx::Range current_range(text_start, text_end);
364
365    // Calculate style-related data.
366    if (classifications[i].style & ACMatchClassification::MATCH)
367      render_text->ApplyStyle(gfx::BOLD, true, current_range);
368
369    ColorKind color_kind = TEXT;
370    if (classifications[i].style & ACMatchClassification::URL) {
371      color_kind = URL;
372      // Consider logical string for domain "ABC.comי/hello" where ABC are
373      // Hebrew (RTL) characters. This string should ideally show as
374      // "CBA.com/hello". If we do not force LTR on URL, it will appear as
375      // "com/hello.CBA".
376      // With IDN and RTL TLDs, it might be okay to allow RTL rendering of URLs,
377      // but it still has some pitfalls like :
378      // ABC.COM/abc-pqr/xyz/FGH will appear as HGF/abc-pqr/xyz/MOC.CBA which
379      // really confuses the path hierarchy of the URL.
380      // Also, if the URL supports https, the appearance will change into LTR
381      // directionality.
382      // In conclusion, LTR rendering of URL is probably the safest bet.
383      render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR);
384    } else if (force_dim ||
385        (classifications[i].style & ACMatchClassification::DIM)) {
386      color_kind = DIMMED_TEXT;
387    }
388    render_text->ApplyColor(GetColor(GetState(), color_kind), current_range);
389  }
390}
391
392gfx::RenderText* OmniboxResultView::RenderMatchContents() {
393  if (!match_contents_render_text_) {
394    const AutocompleteMatch& match = display_match();
395    match_contents_render_text_.reset(
396        CreateRenderText(match.contents).release());
397    ApplyClassifications(match_contents_render_text_.get(),
398                         match.contents_class, false);
399  }
400  return match_contents_render_text_.get();
401}
402
403int OmniboxResultView::GetMatchContentsWidth() const {
404  return match_contents_render_text_->GetContentWidth();
405}
406
407// TODO(skanuj): This is probably identical across all OmniboxResultView rows in
408// the omnibox dropdown. Consider sharing the result.
409int OmniboxResultView::GetDisplayOffset(
410    bool is_ui_rtl,
411    bool is_match_contents_rtl) const {
412  const AutocompleteMatch& match = display_match();
413  if (match.type != AutocompleteMatchType::SEARCH_SUGGEST_INFINITE)
414    return 0;
415
416  const base::string16& input_text =
417      base::UTF8ToUTF16(match.GetAdditionalInfo("input text"));
418  int contents_start_index = 0;
419  base::StringToInt(match.GetAdditionalInfo("match contents start index"),
420                    &contents_start_index);
421
422  scoped_ptr<gfx::RenderText> input_render_text(CreateRenderText(input_text));
423  const gfx::Range& glyph_bounds =
424      input_render_text->GetGlyphBounds(contents_start_index);
425  const int start_padding = is_match_contents_rtl ?
426      std::max(glyph_bounds.start(), glyph_bounds.end()) :
427      std::min(glyph_bounds.start(), glyph_bounds.end());
428
429  return is_ui_rtl ?
430      (input_render_text->GetContentWidth() - start_padding) : start_padding;
431}
432
433// static
434void OmniboxResultView::ComputeMatchMaxWidths(int contents_width,
435                                              int separator_width,
436                                              int description_width,
437                                              int available_width,
438                                              bool allow_shrinking_contents,
439                                              int* contents_max_width,
440                                              int* description_max_width) {
441  if (available_width <= 0) {
442      *contents_max_width = 0;
443      *description_max_width = 0;
444      return;
445  }
446
447  int total_width = contents_width + separator_width + description_width;
448
449  // The contents should never be empty.
450  DCHECK(contents_width);
451  *contents_max_width = -1;
452
453  // If the description is empty, the contents can get the full width.
454  *description_max_width = description_width ? -1 : 0;
455  if (!description_width)
456    return;
457
458  if (total_width > available_width) {
459    if (allow_shrinking_contents) {
460      // Try to split the available space fairly between contents and
461      // description (if one wants less than half, give it all it wants and
462      // give the other the remaining space; otherwise, give each half).
463      // However, if this makes the contents too narrow to show a significant
464      // amount of information, give the contents more space.
465      *contents_max_width = std::max(
466          (available_width - separator_width + 1) / 2,
467          available_width - separator_width - description_width);
468
469      const int kMinimumContentsWidth = 300;
470      *contents_max_width = std::min(
471          std::max(*contents_max_width, kMinimumContentsWidth), contents_width);
472    }
473
474    // Give the description the remaining space, unless this makes it too small
475    // to display anything meaningful, in which case just hide the description
476    // and let the contents take up the whole width.
477    *description_max_width =
478      available_width - separator_width -
479      (*contents_max_width == -1 ? contents_width : *contents_max_width);
480    const int kMinimumDescriptionWidth = 75;
481    if (*description_max_width <
482        std::min(description_width, kMinimumDescriptionWidth)) {
483      *description_max_width = 0;
484      *contents_max_width = -1;
485    }
486  }
487}
488
489
490// static
491void OmniboxResultView::CommonInitColors(const ui::NativeTheme* theme,
492                                         SkColor colors[][NUM_KINDS]) {
493  colors[HOVERED][BACKGROUND] =
494      color_utils::AlphaBlend(colors[SELECTED][BACKGROUND],
495                              colors[NORMAL][BACKGROUND], 64);
496  colors[HOVERED][TEXT] = colors[NORMAL][TEXT];
497#if defined(USE_AURA)
498  const bool is_aura = theme == ui::NativeThemeAura::instance();
499#else
500  const bool is_aura = false;
501#endif
502  for (int i = 0; i < NUM_STATES; ++i) {
503    if (is_aura) {
504      colors[i][TEXT] =
505          color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xdd);
506      colors[i][DIMMED_TEXT] =
507          color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xbb);
508    } else {
509      colors[i][DIMMED_TEXT] =
510          color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 128);
511      colors[i][URL] = color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0),
512                                                     colors[i][BACKGROUND]);
513    }
514
515    // TODO(joi): Programmatically draw the dropdown border using
516    // this color as well. (Right now it's drawn as black with 25%
517    // alpha.)
518    colors[i][DIVIDER] =
519        color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 0x34);
520  }
521}
522
523// static
524int OmniboxResultView::default_icon_size_ = 0;
525
526// static
527int OmniboxResultView::ellipsis_width_ = 0;
528
529gfx::ImageSkia OmniboxResultView::GetIcon() const {
530  const gfx::Image image = model_->GetIconIfExtensionMatch(model_index_);
531  if (!image.IsEmpty())
532    return image.AsImageSkia();
533
534  int icon = match_.starred ?
535      IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type);
536  if (GetState() == SELECTED) {
537    switch (icon) {
538      case IDR_OMNIBOX_EXTENSION_APP:
539        icon = IDR_OMNIBOX_EXTENSION_APP_SELECTED;
540        break;
541      case IDR_OMNIBOX_HTTP:
542        icon = IDR_OMNIBOX_HTTP_SELECTED;
543        break;
544      case IDR_OMNIBOX_SEARCH:
545        icon = IDR_OMNIBOX_SEARCH_SELECTED;
546        break;
547      case IDR_OMNIBOX_STAR:
548        icon = IDR_OMNIBOX_STAR_SELECTED;
549        break;
550      default:
551        NOTREACHED();
552        break;
553    }
554  }
555  return *(location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(icon));
556}
557
558const gfx::ImageSkia* OmniboxResultView::GetKeywordIcon() const {
559  // NOTE: If we ever begin returning icons of varying size, then callers need
560  // to ensure that |keyword_icon_| is resized each time its image is reset.
561  return location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
562      (GetState() == SELECTED) ? IDR_OMNIBOX_TTS_SELECTED : IDR_OMNIBOX_TTS);
563}
564
565void OmniboxResultView::Layout() {
566  const gfx::ImageSkia icon = GetIcon();
567
568  icon_bounds_.SetRect(edge_item_padding_ +
569      ((icon.width() == default_icon_size_) ?
570          0 : LocationBarView::kIconInternalPadding),
571      (height() - icon.height()) / 2, icon.width(), icon.height());
572
573  int text_x = edge_item_padding_ + default_icon_size_ + item_padding_;
574  int text_width = width() - text_x - edge_item_padding_;
575
576  if (match_.associated_keyword.get()) {
577    const int kw_collapsed_size =
578        keyword_icon_->width() + edge_item_padding_;
579    const int max_kw_x = width() - kw_collapsed_size;
580    const int kw_x =
581        animation_->CurrentValueBetween(max_kw_x, edge_item_padding_);
582    const int kw_text_x = kw_x + keyword_icon_->width() + item_padding_;
583
584    text_width = kw_x - text_x - item_padding_;
585    keyword_text_bounds_.SetRect(
586        kw_text_x, 0,
587        std::max(width() - kw_text_x - edge_item_padding_, 0), height());
588    keyword_icon_->SetPosition(
589        gfx::Point(kw_x, (height() - keyword_icon_->height()) / 2));
590  }
591
592  text_bounds_.SetRect(text_x, 0, std::max(text_width, 0), height());
593}
594
595void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
596  animation_->SetSlideDuration(width() / 4);
597}
598
599void OmniboxResultView::OnPaint(gfx::Canvas* canvas) {
600  const ResultViewState state = GetState();
601  if (state != NORMAL)
602    canvas->DrawColor(GetColor(state, BACKGROUND));
603
604  if (!render_associated_keyword_match_) {
605    // Paint the icon.
606    canvas->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_),
607                         icon_bounds_.y());
608  }
609  const gfx::Rect& text_bounds = render_associated_keyword_match_ ?
610      keyword_text_bounds_ : text_bounds_;
611  int x = GetMirroredXForRect(text_bounds);
612  mirroring_context_->Initialize(x, text_bounds.width());
613  PaintMatch(canvas, x);
614}
615
616void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
617  Layout();
618  SchedulePaint();
619}
620