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