| // Copyright (c) 2012 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. |
| |
| // For WinDDK ATL compatibility, these ATL headers must come first. |
| #include "build/build_config.h" |
| |
| #if defined(OS_WIN) |
| #include <atlbase.h> // NOLINT |
| #include <atlwin.h> // NOLINT |
| #endif |
| |
| #include "chrome/browser/ui/views/omnibox/omnibox_result_view.h" |
| |
| #include <limits.h> |
| |
| #include <algorithm> // NOLINT |
| |
| #include "base/feature_list.h" |
| #include "base/i18n/bidi_line_iterator.h" |
| #include "base/macros.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/ui/layout_constants.h" |
| #include "chrome/browser/ui/omnibox/omnibox_theme.h" |
| #include "chrome/browser/ui/views/location_bar/background_with_1_px_border.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h" |
| #include "chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.h" |
| #include "chrome/browser/ui/views/omnibox/omnibox_text_view.h" |
| #include "chrome/browser/ui/views/omnibox/rounded_omnibox_results_frame.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/omnibox/browser/omnibox_field_trial.h" |
| #include "components/omnibox/browser/omnibox_popup_model.h" |
| #include "components/omnibox/browser/vector_icons.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/material_design/material_design_controller.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/theme_provider.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/range/range.h" |
| #include "ui/gfx/render_text.h" |
| #include "ui/gfx/text_utils.h" |
| #include "ui/native_theme/native_theme.h" |
| |
| using ui::NativeTheme; |
| |
| namespace { |
| |
| // The vertical margin that should be used above and below each suggestion. |
| static const int kVerticalMargin = 1; |
| |
| // The vertical padding to provide each RenderText in addition to the height of |
| // the font. Where possible, RenderText uses this additional space to vertically |
| // center the cap height of the font instead of centering the entire font. |
| static const int kVerticalPadding = 4; |
| |
| // TODO(dschuyler): Perhaps this should be based on the font size |
| // instead of hardcoded to 2 dp (e.g. by adding a space in an |
| // appropriate font to the beginning of the description, then reducing |
| // the additional padding here to zero). |
| static const int kAnswerIconToTextPadding = 2; |
| |
| // A mapping from OmniboxResultView's ResultViewState/ColorKind types to |
| // NativeTheme colors. |
| struct TranslationTable { |
| ui::NativeTheme::ColorId id; |
| OmniboxResultView::ResultViewState state; |
| OmniboxResultView::ColorKind kind; |
| } static const kTranslationTable[] = { |
| { NativeTheme::kColorId_ResultsTableNormalText, |
| OmniboxResultView::NORMAL, OmniboxResultView::TEXT }, |
| { NativeTheme::kColorId_ResultsTableHoveredText, |
| OmniboxResultView::HOVERED, OmniboxResultView::TEXT }, |
| { NativeTheme::kColorId_ResultsTableSelectedText, |
| OmniboxResultView::SELECTED, OmniboxResultView::TEXT }, |
| { NativeTheme::kColorId_ResultsTableNormalDimmedText, |
| OmniboxResultView::NORMAL, OmniboxResultView::DIMMED_TEXT }, |
| { NativeTheme::kColorId_ResultsTableHoveredDimmedText, |
| OmniboxResultView::HOVERED, OmniboxResultView::DIMMED_TEXT }, |
| { NativeTheme::kColorId_ResultsTableSelectedDimmedText, |
| OmniboxResultView::SELECTED, OmniboxResultView::DIMMED_TEXT }, |
| { NativeTheme::kColorId_ResultsTableNormalUrl, |
| OmniboxResultView::NORMAL, OmniboxResultView::URL }, |
| { NativeTheme::kColorId_ResultsTableHoveredUrl, |
| OmniboxResultView::HOVERED, OmniboxResultView::URL }, |
| { NativeTheme::kColorId_ResultsTableSelectedUrl, |
| OmniboxResultView::SELECTED, OmniboxResultView::URL }, |
| }; |
| |
| // Whether to use the two-line layout. |
| bool IsTwoLineLayout() { |
| return base::FeatureList::IsEnabled(omnibox::kUIExperimentVerticalLayout) || |
| ui::MaterialDesignController::IsTouchOptimizedUiEnabled(); |
| } |
| |
| // Creates a views::Background for the current result style. |
| std::unique_ptr<views::Background> CreateBackgroundWithColor(SkColor bg_color) { |
| return ui::MaterialDesignController::IsTouchOptimizedUiEnabled() |
| ? views::CreateSolidBackground(bg_color) |
| : std::make_unique<BackgroundWith1PxBorder>(bg_color, bg_color); |
| } |
| |
| // Returns the horizontal offset that ensures icons align vertically with the |
| // Omnibox icon. |
| int GetIconAlignmentOffset() { |
| // The horizontal bounds of a result is the width of the selection highlight |
| // (i.e. the views::Background). The traditional popup is designed with its |
| // selection shape mimicking the internal shape of the omnibox border. Inset |
| // to be consistent with the border drawn in BackgroundWith1PxBorder. |
| int offset = BackgroundWith1PxBorder::kLocationBarBorderThicknessDip; |
| |
| // The touch-optimized popup selection always fills the results frame. So to |
| // align icons, inset additionally by the frame alignment inset on the left. |
| if (ui::MaterialDesignController::IsTouchOptimizedUiEnabled()) |
| offset += RoundedOmniboxResultsFrame::kLocationBarAlignmentInsets.left(); |
| return offset; |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // OmniboxResultView, public: |
| |
| OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView* model, |
| int model_index, |
| const gfx::FontList& font_list) |
| : model_(model), |
| model_index_(model_index), |
| is_hovered_(false), |
| font_height_(std::max( |
| font_list.GetHeight(), |
| font_list.DeriveWithWeight(gfx::Font::Weight::BOLD).GetHeight())), |
| animation_(new gfx::SlideAnimation(this)), |
| icon_view_(AddImageView()), |
| image_view_(AddImageView()), |
| keyword_icon_view_(AddImageView()), |
| content_view_(AddOmniboxTextView(font_list)), |
| description_view_(AddOmniboxTextView(font_list)), |
| keyword_content_view_(AddOmniboxTextView(font_list)), |
| keyword_description_view_(AddOmniboxTextView(font_list)), |
| separator_view_(AddOmniboxTextView(font_list)) { |
| CHECK_GE(model_index, 0); |
| |
| keyword_icon_view_->EnableCanvasFlippingForRTLUI(true); |
| keyword_icon_view_->SetImage(gfx::CreateVectorIcon( |
| omnibox::kKeywordSearchIcon, GetLayoutConstant(LOCATION_BAR_ICON_SIZE), |
| GetVectorIconColor())); |
| keyword_icon_view_->SizeToPreferredSize(); |
| |
| tab_switch_button_ = new OmniboxTabSwitchButton(this); |
| AddChildView(tab_switch_button_); |
| tab_switch_button_->SetVisible(false); |
| } |
| |
| OmniboxResultView::~OmniboxResultView() {} |
| |
| SkColor OmniboxResultView::GetColor( |
| ResultViewState state, |
| ColorKind kind) const { |
| if (kind == INVISIBLE_TEXT) |
| return SK_ColorTRANSPARENT; |
| for (size_t i = 0; i < arraysize(kTranslationTable); ++i) { |
| if (kTranslationTable[i].state == state && |
| kTranslationTable[i].kind == kind) { |
| return GetNativeTheme()->GetSystemColor(kTranslationTable[i].id); |
| } |
| } |
| |
| NOTREACHED(); |
| return gfx::kPlaceholderColor; |
| } |
| |
| void OmniboxResultView::SetMatch(const AutocompleteMatch& match) { |
| match_ = match.GetMatchWithContentsAndDescriptionPossiblySwapped(); |
| animation_->Reset(); |
| is_hovered_ = false; |
| icon_view_->SetImage(GetIcon().ToImageSkia()); |
| image_view_->SetVisible(false); // Until SetAnswerImage is called. |
| |
| keyword_icon_view_->SetVisible(match_.associated_keyword.get()); |
| if (OmniboxFieldTrial::InTabSwitchSuggestionWithButtonTrial()) { |
| tab_switch_button_->SetVisible(match.type == |
| AutocompleteMatchType::TAB_SEARCH); |
| } |
| Invalidate(); |
| if (GetWidget()) |
| Layout(); |
| } |
| |
| void OmniboxResultView::ShowKeyword(bool show_keyword) { |
| if (show_keyword) |
| animation_->Show(); |
| else |
| animation_->Hide(); |
| } |
| |
| void OmniboxResultView::Invalidate() { |
| const ResultViewState state = GetState(); |
| if (state == NORMAL) { |
| SetBackground(nullptr); |
| } else { |
| SkColor color = GetOmniboxColor(OmniboxPart::RESULTS_BACKGROUND, GetTint(), |
| GetThemeState()); |
| SetBackground(CreateBackgroundWithColor(color)); |
| } |
| |
| if (match_.answer) { |
| content_view_->SetText(match_.answer->first_line()); |
| description_view_->SetText(match_.answer->second_line()); |
| } else { |
| content_view_->SetText(match_.contents, match_.contents_class); |
| description_view_->SetText(match_.description, match_.description_class); |
| } |
| |
| const base::string16& separator = |
| l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR); |
| separator_view_->SetText(separator); |
| separator_view_->Dim(); |
| |
| AutocompleteMatch* keyword_match = match_.associated_keyword.get(); |
| keyword_content_view_->SetVisible(keyword_match); |
| keyword_description_view_->SetVisible(keyword_match); |
| if (keyword_match) { |
| keyword_content_view_->SetText(keyword_match->contents, |
| keyword_match->contents_class); |
| keyword_description_view_->SetText(keyword_match->description, |
| keyword_match->description_class); |
| keyword_description_view_->Dim(); |
| } |
| |
| // TODO(dschuyler): without this Layout call the text will shift slightly when |
| // hovered. Look into removing this call (without the text shifting). |
| Layout(); |
| } |
| |
| void OmniboxResultView::OnSelected() { |
| DCHECK_EQ(SELECTED, GetState()); |
| |
| // The text is also accessible via text/value change events in the omnibox but |
| // this selection event allows the screen reader to get more details about the |
| // list and the user's position within it. |
| NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true); |
| } |
| |
| OmniboxResultView::ResultViewState OmniboxResultView::GetState() const { |
| if (model_->IsSelectedIndex(model_index_)) |
| return SELECTED; |
| return is_hovered_ ? HOVERED : NORMAL; |
| } |
| |
| OmniboxPartState OmniboxResultView::GetThemeState() const { |
| if (model_->IsSelectedIndex(model_index_)) { |
| return is_hovered_ ? OmniboxPartState::HOVERED_AND_SELECTED |
| : OmniboxPartState::SELECTED; |
| } |
| return is_hovered_ ? OmniboxPartState::HOVERED : OmniboxPartState::NORMAL; |
| } |
| |
| OmniboxTint OmniboxResultView::GetTint() const { |
| return model_->GetTint(); |
| } |
| |
| void OmniboxResultView::OnMatchIconUpdated() { |
| // The new icon will be fetched during repaint. |
| SchedulePaint(); |
| } |
| |
| void OmniboxResultView::SetAnswerImage(const gfx::ImageSkia& image) { |
| image_view_->SetImage(image); |
| image_view_->SetVisible(true); |
| Layout(); |
| SchedulePaint(); |
| } |
| |
| void OmniboxResultView::OpenMatch(WindowOpenDisposition disposition) { |
| model_->OpenMatch(model_index_, disposition); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // OmniboxResultView, views::View overrides: |
| |
| bool OmniboxResultView::OnMousePressed(const ui::MouseEvent& event) { |
| if (event.IsOnlyLeftMouseButton()) |
| model_->SetSelectedLine(model_index_); |
| return true; |
| } |
| |
| bool OmniboxResultView::OnMouseDragged(const ui::MouseEvent& event) { |
| if (HitTestPoint(event.location())) { |
| // When the drag enters or remains within the bounds of this view, either |
| // set the state to be selected or hovered, depending on the mouse button. |
| if (event.IsOnlyLeftMouseButton()) { |
| if (GetState() != SELECTED) |
| model_->SetSelectedLine(model_index_); |
| if (tab_switch_button_->visible()) { |
| gfx::Point point_in_child_coords(event.location()); |
| View::ConvertPointToTarget(this, tab_switch_button_, |
| &point_in_child_coords); |
| if (tab_switch_button_->HitTestPoint(point_in_child_coords)) { |
| SetMouseHandler(tab_switch_button_); |
| return false; |
| } |
| } |
| } else { |
| SetHovered(true); |
| } |
| return true; |
| } |
| |
| // When the drag leaves the bounds of this view, cancel the hover state and |
| // pass control to the popup view. |
| SetHovered(false); |
| SetMouseHandler(model_); |
| return false; |
| } |
| |
| void OmniboxResultView::OnMouseReleased(const ui::MouseEvent& event) { |
| if (event.IsOnlyMiddleMouseButton() || event.IsOnlyLeftMouseButton()) { |
| OpenMatch(event.IsOnlyLeftMouseButton() |
| ? WindowOpenDisposition::CURRENT_TAB |
| : WindowOpenDisposition::NEW_BACKGROUND_TAB); |
| } |
| } |
| |
| void OmniboxResultView::OnMouseMoved(const ui::MouseEvent& event) { |
| SetHovered(true); |
| } |
| |
| void OmniboxResultView::OnMouseExited(const ui::MouseEvent& event) { |
| SetHovered(false); |
| } |
| |
| void OmniboxResultView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| // Get the label without the ", n of m" positional text appended. |
| // The positional info is provided via |
| // ax::mojom::IntAttribute::kPosInSet/SET_SIZE and providing it via text as |
| // well would result in duplicate announcements. |
| node_data->SetName( |
| AutocompleteMatchType::ToAccessibilityLabel(match_, match_.contents)); |
| |
| node_data->role = ax::mojom::Role::kListBoxOption; |
| node_data->AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, |
| model_index_ + 1); |
| node_data->AddIntAttribute(ax::mojom::IntAttribute::kSetSize, |
| model_->child_count()); |
| |
| node_data->AddState(ax::mojom::State::kSelectable); |
| switch (GetState()) { |
| case SELECTED: |
| node_data->AddState(ax::mojom::State::kSelected); |
| break; |
| case HOVERED: |
| node_data->AddState(ax::mojom::State::kHovered); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| gfx::Size OmniboxResultView::CalculatePreferredSize() const { |
| int height = GetTextHeight() + (2 * GetVerticalMargin()); |
| if (match_.answer) |
| height += GetAnswerHeight(); |
| else if (IsTwoLineLayout()) |
| height += GetTextHeight(); |
| return gfx::Size(0, height); |
| } |
| |
| void OmniboxResultView::OnNativeThemeChanged(const ui::NativeTheme* theme) { |
| Invalidate(); |
| SchedulePaint(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // OmniboxResultView, private: |
| |
| views::ImageView* OmniboxResultView::AddImageView() { |
| views::ImageView* view = new views::ImageView(); |
| AddChildView(view); |
| return view; |
| } |
| |
| OmniboxTextView* OmniboxResultView::AddOmniboxTextView( |
| const gfx::FontList& font_list) { |
| OmniboxTextView* view = new OmniboxTextView(this, font_list); |
| AddChildView(view); |
| return view; |
| } |
| |
| int OmniboxResultView::GetTextHeight() const { |
| return font_height_ + kVerticalPadding; |
| } |
| |
| gfx::Image OmniboxResultView::GetIcon() const { |
| return model_->GetMatchIcon(match_, GetVectorIconColor()); |
| } |
| |
| SkColor OmniboxResultView::GetVectorIconColor() const { |
| // For selected rows, paint the icon the same color as the text. |
| SkColor color = GetColor(GetState(), TEXT); |
| if (GetState() != SELECTED) |
| color = color_utils::DeriveDefaultIconColor(color); |
| return color; |
| } |
| |
| bool OmniboxResultView::ShowOnlyKeywordMatch() const { |
| return match_.associated_keyword && |
| (keyword_icon_view_->x() <= (icon_view_->x() + icon_view_->width())); |
| } |
| |
| int OmniboxResultView::GetAnswerHeight() const { |
| const int horizontal_padding = |
| GetLayoutConstant(LOCATION_BAR_PADDING) + |
| GetLayoutConstant(LOCATION_BAR_ICON_INTERIOR_PADDING); |
| const gfx::Image icon = GetIcon(); |
| int icon_width = icon.Width(); |
| int answer_icon_size = image_view_->visible() |
| ? image_view_->height() + kAnswerIconToTextPadding |
| : 0; |
| // TODO(dschuyler): The GetIconAlignmentOffset() is applied an extra time to |
| // match the math in Layout(). This seems like a (minor) mistake. |
| int deduction = (GetIconAlignmentOffset() * 2) + icon_width + |
| (horizontal_padding * 3) + answer_icon_size; |
| int description_width = std::max(width() - deduction, 0); |
| return description_view_->GetHeightForWidth(description_width) + |
| kVerticalPadding; |
| } |
| |
| int OmniboxResultView::GetVerticalMargin() const { |
| // Regardless of the text size, we ensure a minimum size for the content line |
| // here. This minimum is larger for hybrid mouse/touch devices to ensure an |
| // adequately sized touch target. |
| using Md = ui::MaterialDesignController; |
| const int kIconVerticalPad = base::GetFieldTrialParamByFeatureAsInt( |
| omnibox::kUIExperimentVerticalMargin, |
| OmniboxFieldTrial::kUIVerticalMarginParam, |
| Md::GetMode() == Md::MATERIAL_HYBRID ? 8 : 4); |
| const int min_height = |
| GetLayoutConstant(LOCATION_BAR_ICON_SIZE) + (kIconVerticalPad * 2); |
| |
| return std::max(kVerticalMargin, (min_height - GetTextHeight()) / 2); |
| } |
| |
| void OmniboxResultView::SetHovered(bool hovered) { |
| if (is_hovered_ != hovered) { |
| is_hovered_ = hovered; |
| Invalidate(); |
| SchedulePaint(); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // OmniboxResultView, views::View overrides, private: |
| |
| void OmniboxResultView::Layout() { |
| views::View::Layout(); |
| const int horizontal_padding = |
| GetLayoutConstant(LOCATION_BAR_PADDING) + |
| GetLayoutConstant(LOCATION_BAR_ICON_INTERIOR_PADDING); |
| const int start_x = GetIconAlignmentOffset() + horizontal_padding; |
| int end_x = width() - start_x; |
| |
| int text_height = GetTextHeight(); |
| int row_height = text_height; |
| if (IsTwoLineLayout()) |
| row_height += match_.answer ? GetAnswerHeight() : GetTextHeight(); |
| |
| const gfx::Image icon = GetIcon(); |
| const int icon_y = GetVerticalMargin() + (row_height - icon.Height()) / 2; |
| icon_view_->SetBounds(start_x, icon_y, icon.Width(), icon.Height()); |
| |
| icon_view_->SetVisible(!ShowOnlyKeywordMatch()); |
| separator_view_->SetVisible(false); |
| |
| // TODO(dschuyler): Refactor these if/else's into separate pieces of code to |
| // improve readability/maintainability. |
| |
| AutocompleteMatch* keyword_match = match_.associated_keyword.get(); |
| if (keyword_match) { |
| // NOTE: While animating the keyword match, both matches may be visible. |
| const int max_kw_x = end_x - keyword_icon_view_->width(); |
| int kw_x = animation_->CurrentValueBetween(max_kw_x, start_x); |
| end_x = kw_x; |
| int y = GetVerticalMargin(); |
| kw_x += BackgroundWith1PxBorder::kLocationBarBorderThicknessDip; |
| keyword_icon_view_->SetPosition( |
| gfx::Point(kw_x, (height() - keyword_icon_view_->height()) / 2)); |
| kw_x += keyword_icon_view_->width() + horizontal_padding; |
| |
| keyword_content_view_->SizeToPreferredSize(); |
| int first_width = keyword_content_view_->GetContentsBounds().width(); |
| keyword_description_view_->SizeToPreferredSize(); |
| int second_width = |
| keyword_description_view_ |
| ? keyword_description_view_->GetContentsBounds().width() |
| : 0; |
| OmniboxPopupModel::ComputeMatchMaxWidths( |
| first_width, separator_view_->width(), second_width, width(), |
| /*description_on_separate_line=*/false, |
| !AutocompleteMatch::IsSearchType(match_.type), &first_width, |
| &second_width); |
| keyword_content_view_->SetBounds(kw_x, y, first_width, text_height); |
| if (second_width != 0) { |
| kw_x += keyword_content_view_->width(); |
| separator_view_->SetVisible(true); |
| separator_view_->SetBounds(kw_x, y, separator_view_->width(), |
| text_height); |
| kw_x += separator_view_->width(); |
| keyword_description_view_->SetBounds(kw_x, y, second_width, text_height); |
| } else if (IsTwoLineLayout()) { |
| keyword_content_view_->SetSize(gfx::Size(first_width, text_height * 2)); |
| } |
| } |
| |
| if (OmniboxFieldTrial::InTabSwitchSuggestionWithButtonTrial() && |
| match_.type == AutocompleteMatchType::TAB_SEARCH) { |
| const int ts_button_width = tab_switch_button_->GetPreferredSize().width(); |
| const int ts_button_height = height(); |
| tab_switch_button_->SetSize(gfx::Size(ts_button_width, ts_button_height)); |
| |
| const int ts_x = end_x - ts_button_width + horizontal_padding; |
| end_x = ts_x - start_x - horizontal_padding; |
| tab_switch_button_->SetPosition(gfx::Point(ts_x, 0)); |
| } |
| |
| // NOTE: While animating the keyword match, both matches may be visible. |
| if (!ShowOnlyKeywordMatch()) { |
| description_view_->SizeToPreferredSize(); |
| int x = start_x; |
| x += icon.Width() + horizontal_padding; |
| int y = GetVerticalMargin(); |
| if (match_.answer) { |
| content_view_->SetBounds(x, y, end_x - x, text_height); |
| y += text_height; |
| if (image_view_->visible()) { |
| // The description may be multi-line. Using the view height results in |
| // an image that's too large, so we use the line height here instead. |
| int image_edge_length = description_view_->GetLineHeight(); |
| image_view_->SetBounds( |
| start_x + icon_view_->width() + horizontal_padding, |
| y + (kVerticalPadding / 2), image_edge_length, image_edge_length); |
| image_view_->SetImageSize( |
| gfx::Size(image_edge_length, image_edge_length)); |
| x += image_view_->width() + kAnswerIconToTextPadding; |
| } |
| int description_width = end_x - x; |
| description_view_->SetBounds( |
| x, y, description_width, |
| description_view_->GetHeightForWidth(description_width) + |
| kVerticalPadding); |
| } else if (IsTwoLineLayout()) { |
| if (!!description_view_->GetContentsBounds().width()) { |
| // A description is present. |
| content_view_->SetBounds(x, y, end_x - x, GetTextHeight()); |
| y += GetTextHeight(); |
| int description_width = end_x - x; |
| description_view_->SetBounds( |
| x, y, description_width, |
| description_view_->GetHeightForWidth(description_width) + |
| kVerticalPadding); |
| } else { |
| // For no description, shift down halfway to draw contents in middle. |
| y += GetTextHeight() / 2; |
| content_view_->SetBounds(x, y, end_x - x, GetTextHeight()); |
| } |
| } else { |
| content_view_->SizeToPreferredSize(); |
| int first_width = content_view_->GetContentsBounds().width(); |
| int second_width = description_view_ |
| ? description_view_->GetContentsBounds().width() |
| : 0; |
| OmniboxPopupModel::ComputeMatchMaxWidths( |
| first_width, separator_view_->width(), second_width, end_x - x, |
| /*description_on_separate_line=*/false, |
| !AutocompleteMatch::IsSearchType(match_.type), &first_width, |
| &second_width); |
| OmniboxTextView* first_view = content_view_; |
| OmniboxTextView* second_view = description_view_; |
| first_view->SetBounds(x, y, first_width, text_height); |
| x += first_width; |
| if (second_width) { |
| separator_view_->SetVisible(true); |
| separator_view_->SetBounds(x, y, separator_view_->width(), text_height); |
| x += separator_view_->width(); |
| } |
| second_view->SetBounds(x, y, second_width, text_height); |
| } |
| } |
| } |
| |
| const char* OmniboxResultView::GetClassName() const { |
| return "OmniboxResultView"; |
| } |
| |
| void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| animation_->SetSlideDuration(width() / 4); |
| Layout(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // OmniboxResultView, gfx::AnimationProgressed overrides, private: |
| |
| void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) { |
| Layout(); |
| SchedulePaint(); |
| } |