omnibox_result_view.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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 "chrome/browser/ui/omnibox/omnibox_popup_model.h" 19#include "chrome/browser/ui/views/location_bar/location_bar_view.h" 20#include "chrome/browser/ui/views/omnibox/omnibox_result_view_model.h" 21#include "grit/generated_resources.h" 22#include "grit/theme_resources.h" 23#include "ui/base/l10n/l10n_util.h" 24#include "ui/base/text/text_elider.h" 25#include "ui/base/theme_provider.h" 26#include "ui/gfx/canvas.h" 27#include "ui/gfx/color_utils.h" 28#include "ui/gfx/image/image.h" 29#include "ui/gfx/render_text.h" 30#include "ui/native_theme/native_theme.h" 31 32#if defined(OS_WIN) 33#include "ui/native_theme/native_theme_win.h" 34#endif 35 36#if defined(USE_AURA) 37#include "ui/native_theme/native_theme_aura.h" 38#endif 39 40namespace { 41 42const char16 kEllipsis[] = { 0x2026, 0x0 }; 43 44// The minimum distance between the top and bottom of the {icon|text} and the 45// top or bottom of the row. 46const int kMinimumIconVerticalPadding = 2; 47const int kMinimumTextVerticalPadding = 3; 48 49} // namespace 50 51//////////////////////////////////////////////////////////////////////////////// 52// OmniboxResultView, public: 53 54// Precalculated data used to draw a complete visual run within the match. 55// This will include all or part of at least one, and possibly several, 56// classifications. 57struct OmniboxResultView::RunData { 58 RunData() : run_start(0), visual_order(0), is_rtl(false), pixel_width(0) {} 59 60 size_t run_start; // Offset within the match text where this run begins. 61 int visual_order; // Where this run occurs in visual order. The earliest 62 // run drawn is run 0. 63 bool is_rtl; 64 int pixel_width; 65 66 // Styled text classification pieces within this run, in logical order. 67 Classifications classifications; 68}; 69 70// This class is a utility class for calculations affected by whether the result 71// view is horizontally mirrored. The drawing functions can be written as if 72// all drawing occurs left-to-right, and then use this class to get the actual 73// coordinates to begin drawing onscreen. 74class OmniboxResultView::MirroringContext { 75 public: 76 MirroringContext() : center_(0), right_(0) {} 77 78 // Tells the mirroring context to use the provided range as the physical 79 // bounds of the drawing region. When coordinate mirroring is needed, the 80 // mirror point will be the center of this range. 81 void Initialize(int x, int width) { 82 center_ = x + width / 2; 83 right_ = x + width; 84 } 85 86 // Given a logical range within the drawing region, returns the coordinate of 87 // the possibly-mirrored "left" side. (This functions exactly like 88 // View::MirroredLeftPointForRect().) 89 int mirrored_left_coord(int left, int right) const { 90 return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left; 91 } 92 93 // Given a logical coordinate within the drawing region, returns the remaining 94 // width available. 95 int remaining_width(int x) const { 96 return right_ - x; 97 } 98 99 private: 100 int center_; 101 int right_; 102 103 DISALLOW_COPY_AND_ASSIGN(MirroringContext); 104}; 105 106OmniboxResultView::OmniboxResultView( 107 OmniboxResultViewModel* model, 108 int model_index, 109 views::View* location_bar, 110 const gfx::Font& font) 111 : edge_item_padding_(LocationBarView::GetItemPadding()), 112 item_padding_(LocationBarView::GetItemPadding()), 113 minimum_text_vertical_padding_(kMinimumTextVerticalPadding), 114 model_(model), 115 model_index_(model_index), 116 location_bar_(location_bar), 117 font_(font), 118 font_height_(std::max(font.GetHeight(), 119 font.DeriveFont(0, gfx::BOLD).GetHeight())), 120 ellipsis_width_(font.GetStringWidth(string16(kEllipsis))), 121 mirroring_context_(new MirroringContext()), 122 keyword_icon_(new views::ImageView()), 123 animation_(new ui::SlideAnimation(this)) { 124 CHECK_GE(model_index, 0); 125 if (default_icon_size_ == 0) { 126 default_icon_size_ = 127 location_bar_->GetThemeProvider()->GetImageSkiaNamed( 128 AutocompleteMatch::TypeToIcon( 129 AutocompleteMatchType::URL_WHAT_YOU_TYPED))->width(); 130 } 131 keyword_icon_->set_owned_by_client(); 132 keyword_icon_->EnableCanvasFlippingForRTLUI(true); 133 keyword_icon_->SetImage(GetKeywordIcon()); 134 keyword_icon_->SizeToPreferredSize(); 135} 136 137OmniboxResultView::~OmniboxResultView() { 138} 139 140SkColor OmniboxResultView::GetColor( 141 ResultViewState state, 142 ColorKind kind) const { 143 const ui::NativeTheme* theme = GetNativeTheme(); 144#if defined(OS_WIN) 145 if (theme == ui::NativeThemeWin::instance()) { 146 static bool win_initialized = false; 147 static SkColor win_colors[NUM_STATES][NUM_KINDS]; 148 if (!win_initialized) { 149 win_colors[NORMAL][BACKGROUND] = color_utils::GetSysSkColor(COLOR_WINDOW); 150 win_colors[SELECTED][BACKGROUND] = 151 color_utils::GetSysSkColor(COLOR_HIGHLIGHT); 152 win_colors[NORMAL][TEXT] = color_utils::GetSysSkColor(COLOR_WINDOWTEXT); 153 win_colors[SELECTED][TEXT] = 154 color_utils::GetSysSkColor(COLOR_HIGHLIGHTTEXT); 155 CommonInitColors(theme, win_colors); 156 win_initialized = true; 157 } 158 return win_colors[state][kind]; 159 } 160#endif 161 static bool initialized = false; 162 static SkColor colors[NUM_STATES][NUM_KINDS]; 163 if (!initialized) { 164 colors[NORMAL][BACKGROUND] = theme->GetSystemColor( 165 ui::NativeTheme::kColorId_TextfieldDefaultBackground); 166 colors[NORMAL][TEXT] = theme->GetSystemColor( 167 ui::NativeTheme::kColorId_TextfieldDefaultColor); 168 colors[NORMAL][URL] = SkColorSetARGB(0xff, 0x00, 0x99, 0x33); 169 colors[SELECTED][BACKGROUND] = theme->GetSystemColor( 170 ui::NativeTheme::kColorId_TextfieldSelectionBackgroundFocused); 171 colors[SELECTED][TEXT] = theme->GetSystemColor( 172 ui::NativeTheme::kColorId_TextfieldSelectionColor); 173 colors[SELECTED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22); 174 colors[HOVERED][URL] = SkColorSetARGB(0xff, 0x00, 0x66, 0x22); 175 CommonInitColors(theme, colors); 176 initialized = true; 177 } 178 return colors[state][kind]; 179} 180 181void OmniboxResultView::SetMatch(const AutocompleteMatch& match) { 182 match_ = match; 183 animation_->Reset(); 184 185 if (match.associated_keyword.get()) { 186 keyword_icon_->SetImage(GetKeywordIcon()); 187 188 if (!keyword_icon_->parent()) 189 AddChildView(keyword_icon_.get()); 190 } else if (keyword_icon_->parent()) { 191 RemoveChildView(keyword_icon_.get()); 192 } 193 194 Layout(); 195} 196 197void OmniboxResultView::ShowKeyword(bool show_keyword) { 198 if (show_keyword) 199 animation_->Show(); 200 else 201 animation_->Hide(); 202} 203 204void OmniboxResultView::Invalidate() { 205 keyword_icon_->SetImage(GetKeywordIcon()); 206 SchedulePaint(); 207} 208 209gfx::Size OmniboxResultView::GetPreferredSize() { 210 return gfx::Size(0, std::max( 211 default_icon_size_ + (kMinimumIconVerticalPadding * 2), 212 GetTextHeight() + (minimum_text_vertical_padding_ * 2))); 213} 214 215//////////////////////////////////////////////////////////////////////////////// 216// OmniboxResultView, protected: 217 218OmniboxResultView::ResultViewState OmniboxResultView::GetState() const { 219 if (model_->IsSelectedIndex(model_index_)) 220 return SELECTED; 221 return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL; 222} 223 224void OmniboxResultView::PaintMatch(gfx::Canvas* canvas, 225 const AutocompleteMatch& match, 226 int x) { 227 x = DrawString(canvas, match.contents, match.contents_class, false, x, 228 text_bounds_.y()); 229 230 // Paint the description. 231 // TODO(pkasting): Because we paint in multiple separate pieces, we can wind 232 // up with no space even for an ellipsis for one or both of these pieces. 233 // Instead, we should paint the entire match as a single long string. This 234 // would also let us use a more properly-localizable string than we get with 235 // just the IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR. 236 if (!match.description.empty()) { 237 string16 separator = 238 l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); 239 ACMatchClassifications classifications; 240 classifications.push_back( 241 ACMatchClassification(0, ACMatchClassification::NONE)); 242 x = DrawString(canvas, separator, classifications, true, x, 243 text_bounds_.y()); 244 245 DrawString(canvas, match.description, match.description_class, true, x, 246 text_bounds_.y()); 247 } 248} 249 250int OmniboxResultView::GetTextHeight() const { 251 return font_height_; 252} 253 254// static 255void OmniboxResultView::CommonInitColors(const ui::NativeTheme* theme, 256 SkColor colors[][NUM_KINDS]) { 257 colors[HOVERED][BACKGROUND] = 258 color_utils::AlphaBlend(colors[SELECTED][BACKGROUND], 259 colors[NORMAL][BACKGROUND], 64); 260 colors[HOVERED][TEXT] = colors[NORMAL][TEXT]; 261#if defined(USE_AURA) 262 const bool is_aura = theme == ui::NativeThemeAura::instance(); 263#else 264 const bool is_aura = false; 265#endif 266 for (int i = 0; i < NUM_STATES; ++i) { 267 if (is_aura) { 268 colors[i][TEXT] = 269 color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xdd); 270 colors[i][DIMMED_TEXT] = 271 color_utils::AlphaBlend(SK_ColorBLACK, colors[i][BACKGROUND], 0xbb); 272 } else { 273 colors[i][DIMMED_TEXT] = 274 color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 128); 275 colors[i][URL] = color_utils::GetReadableColor(SkColorSetRGB(0, 128, 0), 276 colors[i][BACKGROUND]); 277 } 278 279 // TODO(joi): Programmatically draw the dropdown border using 280 // this color as well. (Right now it's drawn as black with 25% 281 // alpha.) 282 colors[i][DIVIDER] = 283 color_utils::AlphaBlend(colors[i][TEXT], colors[i][BACKGROUND], 0x34); 284 } 285} 286 287// static 288bool OmniboxResultView::SortRunsLogically(const RunData& lhs, 289 const RunData& rhs) { 290 return lhs.run_start < rhs.run_start; 291} 292 293// static 294bool OmniboxResultView::SortRunsVisually(const RunData& lhs, 295 const RunData& rhs) { 296 return lhs.visual_order < rhs.visual_order; 297} 298 299// static 300int OmniboxResultView::default_icon_size_ = 0; 301 302gfx::ImageSkia OmniboxResultView::GetIcon() const { 303 const gfx::Image image = model_->GetIconIfExtensionMatch(model_index_); 304 if (!image.IsEmpty()) 305 return image.AsImageSkia(); 306 307 int icon = match_.starred ? 308 IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type); 309 if (GetState() == SELECTED) { 310 switch (icon) { 311 case IDR_OMNIBOX_EXTENSION_APP: 312 icon = IDR_OMNIBOX_EXTENSION_APP_SELECTED; 313 break; 314 case IDR_OMNIBOX_HTTP: 315 icon = IDR_OMNIBOX_HTTP_SELECTED; 316 break; 317 case IDR_OMNIBOX_SEARCH: 318 icon = IDR_OMNIBOX_SEARCH_SELECTED; 319 break; 320 case IDR_OMNIBOX_STAR: 321 icon = IDR_OMNIBOX_STAR_SELECTED; 322 break; 323 default: 324 NOTREACHED(); 325 break; 326 } 327 } 328 return *(location_bar_->GetThemeProvider()->GetImageSkiaNamed(icon)); 329} 330 331const gfx::ImageSkia* OmniboxResultView::GetKeywordIcon() const { 332 // NOTE: If we ever begin returning icons of varying size, then callers need 333 // to ensure that |keyword_icon_| is resized each time its image is reset. 334 return location_bar_->GetThemeProvider()->GetImageSkiaNamed( 335 (GetState() == SELECTED) ? IDR_OMNIBOX_TTS_SELECTED : IDR_OMNIBOX_TTS); 336} 337 338int OmniboxResultView::DrawString( 339 gfx::Canvas* canvas, 340 const string16& text, 341 const ACMatchClassifications& classifications, 342 bool force_dim, 343 int x, 344 int y) { 345 if (text.empty()) 346 return x; 347 348 // Check whether or not this text is a URL. URLs are always displayed LTR 349 // regardless of locale. 350 bool is_url = true; 351 for (ACMatchClassifications::const_iterator i(classifications.begin()); 352 i != classifications.end(); ++i) { 353 if (!(i->style & ACMatchClassification::URL)) { 354 is_url = false; 355 break; 356 } 357 } 358 359 // Split the text into visual runs. We do this first so that we don't need to 360 // worry about whether our eliding might change the visual display in 361 // unintended ways, e.g. by removing directional markings or by adding an 362 // ellipsis that's not enclosed in appropriate markings. 363 base::i18n::BiDiLineIterator bidi_line; 364 if (!bidi_line.Open(text, base::i18n::IsRTL(), is_url)) 365 return x; 366 const int num_runs = bidi_line.CountRuns(); 367 ScopedVector<gfx::RenderText> render_texts; 368 Runs runs; 369 for (int run = 0; run < num_runs; ++run) { 370 int run_start_int = 0, run_length_int = 0; 371 // The index we pass to GetVisualRun corresponds to the position of the run 372 // in the displayed text. For example, the string "Google in HEBREW" (where 373 // HEBREW is text in the Hebrew language) has two runs: "Google in " which 374 // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the 375 // run "Google in " has the index 0 (since it is the leftmost run 376 // displayed). In an RTL context, the same run has the index 1 because it 377 // is the rightmost run. This is why the order in which we traverse the 378 // runs is different depending on the locale direction. 379 const UBiDiDirection run_direction = bidi_line.GetVisualRun( 380 (base::i18n::IsRTL() && !is_url) ? (num_runs - run - 1) : run, 381 &run_start_int, &run_length_int); 382 DCHECK_GT(run_length_int, 0); 383 runs.push_back(RunData()); 384 RunData* current_run = &runs.back(); 385 current_run->run_start = run_start_int; 386 const size_t run_end = current_run->run_start + run_length_int; 387 current_run->visual_order = run; 388 current_run->is_rtl = !is_url && (run_direction == UBIDI_RTL); 389 390 // Compute classifications for this run. 391 for (size_t i = 0; i < classifications.size(); ++i) { 392 const size_t text_start = 393 std::max(classifications[i].offset, current_run->run_start); 394 if (text_start >= run_end) 395 break; // We're past the last classification in the run. 396 397 const size_t text_end = (i < (classifications.size() - 1)) ? 398 std::min(classifications[i + 1].offset, run_end) : run_end; 399 if (text_end <= current_run->run_start) 400 continue; // We haven't reached the first classification in the run. 401 402 render_texts.push_back(gfx::RenderText::CreateInstance()); 403 gfx::RenderText* render_text = render_texts.back(); 404 current_run->classifications.push_back(render_text); 405 render_text->SetText(text.substr(text_start, text_end - text_start)); 406 render_text->SetFont(font_); 407 408 // Calculate style-related data. 409 if (classifications[i].style & ACMatchClassification::MATCH) 410 render_text->SetStyle(gfx::BOLD, true); 411 const ResultViewState state = GetState(); 412 if (classifications[i].style & ACMatchClassification::URL) 413 render_text->SetColor(GetColor(state, URL)); 414 else if (classifications[i].style & ACMatchClassification::DIM) 415 render_text->SetColor(GetColor(state, DIMMED_TEXT)); 416 else 417 render_text->SetColor(GetColor(state, force_dim ? DIMMED_TEXT : TEXT)); 418 419 current_run->pixel_width += render_text->GetStringSize().width(); 420 } 421 DCHECK(!current_run->classifications.empty()); 422 } 423 DCHECK(!runs.empty()); 424 425 // Sort into logical order so we can elide logically. 426 std::sort(runs.begin(), runs.end(), &SortRunsLogically); 427 428 // Now determine what to elide, if anything. Several subtle points: 429 // * Because we have the run data, we can get edge cases correct, like 430 // whether to place an ellipsis before or after the end of a run when the 431 // text needs to be elided at the run boundary. 432 // * The "or one before it" comments below refer to cases where an earlier 433 // classification fits completely, but leaves too little space for an 434 // ellipsis that turns out to be needed later. These cases are commented 435 // more completely in Elide(). 436 int remaining_width = mirroring_context_->remaining_width(x); 437 for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) { 438 if (i->pixel_width > remaining_width) { 439 // This run or one before it needs to be elided. 440 for (Classifications::iterator j(i->classifications.begin()); 441 j != i->classifications.end(); ++j) { 442 const int width = (*j)->GetStringSize().width(); 443 if (width > remaining_width) { 444 // This classification or one before it needs to be elided. Erase all 445 // further classifications and runs so Elide() can simply reverse- 446 // iterate over everything to find the specific classification to 447 // elide. 448 i->classifications.erase(++j, i->classifications.end()); 449 runs.erase(++i, runs.end()); 450 Elide(&runs, remaining_width); 451 break; 452 } 453 remaining_width -= width; 454 } 455 break; 456 } 457 remaining_width -= i->pixel_width; 458 } 459 460 // Sort back into visual order so we can display the runs correctly. 461 std::sort(runs.begin(), runs.end(), &SortRunsVisually); 462 463 // Draw the runs. 464 for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) { 465 const bool reverse_visible_order = (i->is_rtl != base::i18n::IsRTL()); 466 if (reverse_visible_order) 467 std::reverse(i->classifications.begin(), i->classifications.end()); 468 for (Classifications::const_iterator j(i->classifications.begin()); 469 j != i->classifications.end(); ++j) { 470 const gfx::Size size = (*j)->GetStringSize(); 471 // Align the text runs to a common baseline. 472 const gfx::Rect rect( 473 mirroring_context_->mirrored_left_coord(x, x + size.width()), 474 y + font_.GetBaseline() - (*j)->GetBaseline(), 475 size.width(), size.height()); 476 (*j)->SetDisplayRect(rect); 477 (*j)->Draw(canvas); 478 x += size.width(); 479 } 480 } 481 482 return x; 483} 484 485void OmniboxResultView::Elide(Runs* runs, int remaining_width) const { 486 // The complexity of this function is due to edge cases like the following: 487 // We have 100 px of available space, an initial classification that takes 86 488 // px, and a font that has a 15 px wide ellipsis character. Now if the first 489 // classification is followed by several very narrow classifications (e.g. 3 490 // px wide each), we don't know whether we need to elide or not at the time we 491 // see the first classification -- it depends on how many subsequent 492 // classifications follow, and some of those may be in the next run (or 493 // several runs!). This is why instead we let our caller move forward until 494 // we know we definitely need to elide, and then in this function we move 495 // backward again until we find a string that we can successfully do the 496 // eliding on. 497 bool first_classification = true; 498 for (Runs::reverse_iterator i(runs->rbegin()); i != runs->rend(); ++i) { 499 for (Classifications::reverse_iterator j(i->classifications.rbegin()); 500 j != i->classifications.rend(); ++j) { 501 if (!first_classification) { 502 // We also add this classification's width (sans ellipsis) back to the 503 // available width since we want to consider the available space we'll 504 // have when we draw this classification. 505 remaining_width += (*j)->GetStringSize().width(); 506 507 // For all but the first classification we consider, we need to append 508 // an ellipsis, since there isn't enough room to draw it after this 509 // classification. 510 (*j)->SetText((*j)->text() + kEllipsis); 511 } 512 first_classification = false; 513 514 // Can we fit at least an ellipsis? 515 string16 elided_text = ui::ElideText((*j)->text(), (*j)->GetFont(), 516 remaining_width, ui::ELIDE_AT_END); 517 Classifications::reverse_iterator prior(j + 1); 518 const bool on_first_classification = (prior == i->classifications.rend()); 519 if (elided_text.empty() && (remaining_width >= ellipsis_width_) && 520 on_first_classification) { 521 // Edge case: This classification is bold, we can't fit a bold ellipsis 522 // but we can fit a normal one, and this is the first classification in 523 // the run. We should display a lone normal ellipsis, because appending 524 // one to the end of the previous run might put it in the wrong visual 525 // location (if the previous run is reversed from the normal visual 526 // order). 527 // NOTE: If this isn't the first classification in the run, we don't 528 // need to bother with this; see note below. 529 elided_text = kEllipsis; 530 } 531 if (!elided_text.empty()) { 532 // Success. Elide this classification and stop. 533 (*j)->SetText(elided_text); 534 535 // If we could only fit an ellipsis, then only make it bold if there was 536 // an immediate prior classification in this run that was also bold, or 537 // it will look orphaned. 538 if ((((*j)->GetFont().GetStyle() & gfx::BOLD) != 0) && 539 (elided_text.length() == 1) && 540 (on_first_classification || 541 (((*prior)->GetFont().GetStyle() & gfx::BOLD) == 0))) { 542 (*j)->SetStyle(gfx::BOLD, false); 543 } 544 545 // Erase any other classifications that come after the elided one. 546 i->classifications.erase(j.base(), i->classifications.end()); 547 runs->erase(i.base(), runs->end()); 548 return; 549 } 550 551 // We couldn't fit an ellipsis. Move back one classification, 552 // append an ellipsis, and try again. 553 // NOTE: In the edge case that a bold ellipsis doesn't fit but a 554 // normal one would, and we reach here, then there is a previous 555 // classification in this run, and so either: 556 // * It's normal, and will be able to draw successfully with the 557 // ellipsis we'll append to it, or 558 // * It is also bold, in which case we don't want to fall back 559 // to a normal ellipsis anyway (see comment above). 560 } 561 } 562 563 // We couldn't draw anything. 564 runs->clear(); 565} 566 567void OmniboxResultView::Layout() { 568 const gfx::ImageSkia icon = GetIcon(); 569 570 icon_bounds_.SetRect(edge_item_padding_ + 571 ((icon.width() == default_icon_size_) ? 572 0 : LocationBarView::kIconInternalPadding), 573 (height() - icon.height()) / 2, icon.width(), icon.height()); 574 575 int text_x = edge_item_padding_ + default_icon_size_ + item_padding_; 576 int text_height = GetTextHeight(); 577 int text_width; 578 579 if (match_.associated_keyword.get()) { 580 const int kw_collapsed_size = 581 keyword_icon_->width() + edge_item_padding_; 582 const int max_kw_x = width() - kw_collapsed_size; 583 const int kw_x = 584 animation_->CurrentValueBetween(max_kw_x, edge_item_padding_); 585 const int kw_text_x = kw_x + keyword_icon_->width() + item_padding_; 586 587 text_width = kw_x - text_x - item_padding_; 588 keyword_text_bounds_.SetRect(kw_text_x, 0, 589 std::max(width() - kw_text_x - edge_item_padding_, 0), text_height); 590 keyword_icon_->SetPosition(gfx::Point(kw_x, 591 (height() - keyword_icon_->height()) / 2)); 592 } else { 593 text_width = width() - text_x - edge_item_padding_; 594 } 595 596 text_bounds_.SetRect(text_x, std::max(0, (height() - text_height) / 2), 597 std::max(text_width, 0), text_height); 598} 599 600void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) { 601 animation_->SetSlideDuration(width() / 4); 602} 603 604void OmniboxResultView::OnPaint(gfx::Canvas* canvas) { 605 const ResultViewState state = GetState(); 606 if (state != NORMAL) 607 canvas->DrawColor(GetColor(state, BACKGROUND)); 608 609 if (!match_.associated_keyword.get() || 610 keyword_icon_->x() > icon_bounds_.right()) { 611 // Paint the icon. 612 canvas->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_), 613 icon_bounds_.y()); 614 615 // Paint the text. 616 int x = GetMirroredXForRect(text_bounds_); 617 mirroring_context_->Initialize(x, text_bounds_.width()); 618 PaintMatch(canvas, match_, x); 619 } 620 621 if (match_.associated_keyword.get()) { 622 // Paint the keyword text. 623 int x = GetMirroredXForRect(keyword_text_bounds_); 624 mirroring_context_->Initialize(x, keyword_text_bounds_.width()); 625 PaintMatch(canvas, *match_.associated_keyword.get(), x); 626 } 627} 628 629void OmniboxResultView::AnimationProgressed(const ui::Animation* animation) { 630 Layout(); 631 SchedulePaint(); 632} 633