| // Copyright 2014 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. |
| |
| #include "ash/app_list/views/search_result_tile_item_view.h" |
| |
| #include <utility> |
| |
| #include "ash/app_list/app_list_metrics.h" |
| #include "ash/app_list/app_list_view_delegate.h" |
| #include "ash/app_list/model/app_list_model.h" |
| #include "ash/app_list/model/search/search_result.h" |
| #include "ash/app_list/pagination_model.h" |
| #include "ash/app_list/views/app_list_item_view.h" |
| #include "ash/public/cpp/app_list/app_list_config.h" |
| #include "ash/public/cpp/app_list/app_list_features.h" |
| #include "ash/public/cpp/app_list/vector_icons/vector_icons.h" |
| #include "ash/public/interfaces/app_list.mojom.h" |
| #include "ash/public/interfaces/app_list_view.mojom.h" |
| #include "base/bind.h" |
| #include "base/i18n/number_formatting.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/menu/menu_runner.h" |
| #include "ui/views/focus/focus_manager.h" |
| |
| namespace app_list { |
| |
| namespace { |
| |
| constexpr int kSearchTileWidth = 80; |
| constexpr int kSearchTileTopPadding = 4; |
| constexpr int kSearchTitleSpacing = 7; |
| constexpr int kSearchPriceSize = 37; |
| constexpr int kSearchRatingSize = 26; |
| constexpr int kSearchRatingStarSize = 12; |
| constexpr int kSearchRatingStarHorizontalSpacing = 1; |
| constexpr int kSearchRatingStarVerticalSpacing = 2; |
| // Text line height in the search result tile. |
| constexpr int kTileTextLineHeight = 16; |
| |
| // Delta applied to the font size of SearchResultTile title. |
| constexpr int kSearchResultTileTitleTextSizeDelta = 1; |
| |
| constexpr int kIconSelectedSize = 56; |
| constexpr int kIconSelectedCornerRadius = 4; |
| // Icon selected color, Google Grey 900 8%. |
| constexpr int kIconSelectedColor = SkColorSetA(gfx::kGoogleGrey900, 0x14); |
| |
| // Offset for centering star rating when there is no price. |
| constexpr int kSearchRatingCenteringOffset = |
| ((kSearchTileWidth - |
| (kSearchRatingSize + kSearchRatingStarHorizontalSpacing + |
| kSearchRatingStarSize)) / |
| 2); |
| |
| constexpr SkColor kSearchTitleColor = gfx::kGoogleGrey900; |
| constexpr SkColor kSearchAppRatingColor = gfx::kGoogleGrey700; |
| constexpr SkColor kSearchAppPriceColor = gfx::kGoogleGreen600; |
| constexpr SkColor kSearchRatingStarColor = gfx::kGoogleGrey700; |
| |
| } // namespace |
| |
| SearchResultTileItemView::SearchResultTileItemView( |
| AppListViewDelegate* view_delegate, |
| PaginationModel* pagination_model, |
| bool show_in_apps_page) |
| : view_delegate_(view_delegate), |
| pagination_model_(pagination_model), |
| is_play_store_app_search_enabled_( |
| app_list_features::IsPlayStoreAppSearchEnabled()), |
| is_app_reinstall_recommendation_enabled_( |
| app_list_features::IsAppReinstallZeroStateEnabled()), |
| show_in_apps_page_(show_in_apps_page), |
| weak_ptr_factory_(this) { |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| |
| // When |result_| is null, the tile is invisible. Calling SetSearchResult with |
| // a non-null item makes the tile visible. |
| SetVisible(false); |
| |
| // Prevent the icon view from interfering with our mouse events. |
| icon_ = new views::ImageView; |
| icon_->set_can_process_events_within_subtree(false); |
| icon_->SetVerticalAlignment(views::ImageView::LEADING); |
| AddChildView(icon_); |
| |
| badge_ = new views::ImageView; |
| badge_->set_can_process_events_within_subtree(false); |
| badge_->SetVerticalAlignment(views::ImageView::LEADING); |
| badge_->SetVisible(false); |
| AddChildView(badge_); |
| |
| title_ = new views::Label; |
| title_->SetAutoColorReadabilityEnabled(false); |
| title_->SetEnabledColor(AppListConfig::instance().grid_title_color()); |
| title_->SetLineHeight(kTileTextLineHeight); |
| title_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| title_->SetHandlesTooltips(false); |
| title_->SetAllowCharacterBreak(true); |
| AddChildView(title_); |
| |
| if (is_play_store_app_search_enabled_ || |
| is_app_reinstall_recommendation_enabled_) { |
| rating_ = new views::Label; |
| rating_->SetEnabledColor(kSearchAppRatingColor); |
| rating_->SetLineHeight(kTileTextLineHeight); |
| rating_->SetHorizontalAlignment(gfx::ALIGN_RIGHT); |
| rating_->SetVisible(false); |
| AddChildView(rating_); |
| |
| rating_star_ = new views::ImageView; |
| rating_star_->set_can_process_events_within_subtree(false); |
| rating_star_->SetVerticalAlignment(views::ImageView::LEADING); |
| rating_star_->SetImage(gfx::CreateVectorIcon( |
| kBadgeRatingIcon, kSearchRatingStarSize, kSearchRatingStarColor)); |
| rating_star_->SetVisible(false); |
| AddChildView(rating_star_); |
| |
| price_ = new views::Label; |
| price_->SetEnabledColor(kSearchAppPriceColor); |
| price_->SetLineHeight(kTileTextLineHeight); |
| price_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| price_->SetVisible(false); |
| AddChildView(price_); |
| } |
| |
| set_context_menu_controller(this); |
| } |
| |
| SearchResultTileItemView::~SearchResultTileItemView() { |
| ClearResult(); |
| } |
| |
| void SearchResultTileItemView::OnResultChanged() { |
| // Handle the case where this may be called from a nested run loop while its |
| // context menu is showing. This cancels the menu (it's for the old item). |
| context_menu_.reset(); |
| |
| SetVisible(!!result()); |
| |
| if (!result()) |
| return; |
| |
| SetTitle(result()->title()); |
| SetRating(result()->rating()); |
| SetPrice(result()->formatted_price()); |
| |
| const gfx::FontList& font = AppListConfig::instance().app_title_font(); |
| if (IsSuggestedAppTileShownInAppPage()) { |
| title_->SetFontList(font); |
| title_->SetEnabledColor(AppListConfig::instance().grid_title_color()); |
| } else { |
| // Set solid color background to avoid broken text. See crbug.com/746563. |
| if (rating_) { |
| rating_->SetBackground(views::CreateSolidBackground( |
| AppListConfig::instance().card_background_color())); |
| if (!IsSuggestedAppTile()) { |
| // App search results use different fonts than AppList apps. |
| rating_->SetFontList( |
| ui::ResourceBundle::GetSharedInstance().GetFontList( |
| AppListConfig::instance().search_result_title_font_style())); |
| } else { |
| rating_->SetFontList(font); |
| } |
| } |
| if (price_) { |
| price_->SetBackground(views::CreateSolidBackground( |
| AppListConfig::instance().card_background_color())); |
| if (!IsSuggestedAppTile()) { |
| // App search results use different fonts than AppList apps. |
| price_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( |
| AppListConfig::instance().search_result_title_font_style())); |
| } else { |
| price_->SetFontList(font); |
| } |
| } |
| title_->SetBackground(views::CreateSolidBackground( |
| AppListConfig::instance().card_background_color())); |
| if (!IsSuggestedAppTile()) { |
| // App search results use different fonts than AppList apps. |
| title_->SetFontList( |
| ui::ResourceBundle::GetSharedInstance() |
| .GetFontList( |
| AppListConfig::instance().search_result_title_font_style()) |
| .DeriveWithSizeDelta(kSearchResultTileTitleTextSizeDelta)); |
| } else { |
| title_->SetFontList(font); |
| } |
| title_->SetEnabledColor(kSearchTitleColor); |
| } |
| |
| title_->SetMaxLines(2); |
| title_->SetMultiLine( |
| (result()->display_type() == ash::SearchResultDisplayType::kTile || |
| (IsSuggestedAppTile() && !show_in_apps_page_)) && |
| result()->result_type() == ash::SearchResultType::kInstalledApp); |
| |
| // If the new icon is null, it's being decoded asynchronously. Not updating it |
| // now to prevent flickering from showing an empty icon while decoding. |
| if (!result()->icon().isNull()) |
| OnMetadataChanged(); |
| |
| base::string16 accessible_name; |
| if (!result()->accessible_name().empty()) |
| accessible_name = result()->accessible_name(); |
| else |
| accessible_name = title_->text(); |
| |
| if (rating_ && rating_->visible()) { |
| accessible_name += |
| base::UTF8ToUTF16(", ") + |
| l10n_util::GetStringFUTF16(IDS_APP_ACCESSIBILITY_STAR_RATING_ARC, |
| rating_->text()); |
| } |
| if (price_ && price_->visible()) |
| accessible_name += base::UTF8ToUTF16(", ") + price_->text(); |
| |
| if (result()->result_type() == |
| ash::SearchResultType::kPlayStoreReinstallApp) { |
| accessible_name += |
| base::UTF8ToUTF16(", ") + |
| l10n_util::GetStringUTF16(IDS_APP_ACCESSIBILITY_APP_RECOMMENDATION_ARC); |
| } |
| SetAccessibleName(accessible_name); |
| } |
| |
| void SearchResultTileItemView::SetIndexInItemListView(size_t index) { |
| index_in_item_list_view_ = index; |
| } |
| |
| void SearchResultTileItemView::SetParentBackgroundColor(SkColor color) { |
| parent_background_color_ = color; |
| UpdateBackgroundColor(); |
| } |
| |
| void SearchResultTileItemView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| ActivateResult(event.flags()); |
| } |
| |
| void SearchResultTileItemView::GetAccessibleNodeData( |
| ui::AXNodeData* node_data) { |
| views::Button::GetAccessibleNodeData(node_data); |
| // Specify |ax::mojom::StringAttribute::kDescription| with an empty string, so |
| // that long truncated names are not read twice. Details of this issue: - The |
| // Play Store app's name is shown in a label |title_|. - If the name is too |
| // long, it'll get truncated and the full name will |
| // go to the label's tooltip. |
| // - SearchResultTileItemView uses that label's tooltip as its tooltip. |
| // - If a view doesn't have |ax::mojom::StringAttribute::kDescription| defined |
| // in the |
| // |AXNodeData|, |AXViewObjWrapper::Serialize| will use the tooltip text |
| // as its description. |
| // - We're customizing this view's accessible name, so it get focused |
| // ChromeVox will read its accessible name and then its description. |
| node_data->AddStringAttribute(ax::mojom::StringAttribute::kDescription, ""); |
| } |
| |
| bool SearchResultTileItemView::OnKeyPressed(const ui::KeyEvent& event) { |
| // Return early if |result()| was deleted due to the search result list |
| // changing. see crbug.com/801142 |
| if (!result()) |
| return true; |
| |
| if (event.key_code() == ui::VKEY_RETURN) { |
| ActivateResult(event.flags()); |
| return true; |
| } |
| return false; |
| } |
| |
| void SearchResultTileItemView::OnFocus() { |
| if (pagination_model_ && IsSuggestedAppTile() && |
| view_delegate_->GetModel()->state() == ash::AppListState::kStateApps) { |
| // Go back to first page when app in suggestions container is focused. |
| pagination_model_->SelectPage(0, false); |
| } else { |
| ScrollRectToVisible(GetLocalBounds()); |
| } |
| SetBackgroundHighlighted(true); |
| UpdateBackgroundColor(); |
| } |
| |
| void SearchResultTileItemView::OnBlur() { |
| SetBackgroundHighlighted(false); |
| UpdateBackgroundColor(); |
| } |
| |
| void SearchResultTileItemView::StateChanged(ButtonState old_state) { |
| UpdateBackgroundColor(); |
| } |
| |
| void SearchResultTileItemView::PaintButtonContents(gfx::Canvas* canvas) { |
| if (!result() || !background_highlighted()) |
| return; |
| |
| gfx::Rect rect(GetContentsBounds()); |
| cc::PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| if (IsSuggestedAppTileShownInAppPage()) { |
| rect.ClampToCenteredSize(AppListConfig::instance().grid_focus_size()); |
| flags.setColor(AppListConfig::instance().grid_selected_color()); |
| canvas->DrawRoundRect(gfx::RectF(rect), |
| AppListConfig::instance().grid_focus_corner_radius(), |
| flags); |
| } else { |
| const int kLeftRightPadding = (rect.width() - kIconSelectedSize) / 2; |
| rect.Inset(kLeftRightPadding, 0); |
| rect.set_height(kIconSelectedSize); |
| flags.setColor(kIconSelectedColor); |
| canvas->DrawRoundRect(gfx::RectF(rect), kIconSelectedCornerRadius, flags); |
| } |
| } |
| |
| void SearchResultTileItemView::OnMetadataChanged() { |
| SetIcon(result()->icon()); |
| SetTitle(result()->title()); |
| SetBadgeIcon(result()->badge_icon()); |
| SetRating(result()->rating()); |
| SetPrice(result()->formatted_price()); |
| Layout(); |
| } |
| |
| void SearchResultTileItemView::ShowContextMenuForViewImpl( |
| views::View* source, |
| const gfx::Point& point, |
| ui::MenuSourceType source_type) { |
| // |result()| could be null when result list is changing. |
| if (!result()) |
| return; |
| |
| view_delegate_->GetSearchResultContextMenuModel( |
| result()->id(), |
| base::BindOnce(&SearchResultTileItemView::OnGetContextMenuModel, |
| weak_ptr_factory_.GetWeakPtr(), source, point, |
| source_type)); |
| } |
| |
| void SearchResultTileItemView::OnGetContextMenuModel( |
| views::View* source, |
| const gfx::Point& point, |
| ui::MenuSourceType source_type, |
| std::vector<ash::mojom::MenuItemPtr> menu) { |
| if (menu.empty() || (context_menu_ && context_menu_->IsShowingMenu())) |
| return; |
| |
| gfx::Rect anchor_rect = source->GetBoundsInScreen(); |
| // Anchor the menu to the same rect that is used for selection highlight. |
| anchor_rect.ClampToCenteredSize(AppListConfig::instance().grid_focus_size()); |
| |
| context_menu_ = std::make_unique<AppListMenuModelAdapter>( |
| result()->id(), this, source_type, this, GetAppType(), |
| base::BindOnce(&SearchResultTileItemView::OnMenuClosed, |
| weak_ptr_factory_.GetWeakPtr())); |
| context_menu_->Build(std::move(menu)); |
| context_menu_->Run(anchor_rect, views::MenuAnchorPosition::kBubbleRight, |
| views::MenuRunner::HAS_MNEMONICS | |
| views::MenuRunner::USE_TOUCHABLE_LAYOUT | |
| views::MenuRunner::CONTEXT_MENU | |
| views::MenuRunner::FIXED_ANCHOR); |
| source->RequestFocus(); |
| } |
| |
| void SearchResultTileItemView::OnMenuClosed() { |
| OnBlur(); |
| } |
| |
| void SearchResultTileItemView::ExecuteCommand(int command_id, int event_flags) { |
| if (result()) { |
| view_delegate_->SearchResultContextMenuItemSelected( |
| result()->id(), command_id, event_flags, |
| ash::mojom::AppListLaunchType::kAppSearchResult); |
| } |
| } |
| |
| void SearchResultTileItemView::ActivateResult(int event_flags) { |
| LogAppLaunchForSuggestedApp(); |
| |
| RecordSearchResultOpenSource(result(), view_delegate_->GetModel(), |
| view_delegate_->GetSearchModel()); |
| view_delegate_->OpenSearchResult( |
| result()->id(), event_flags, |
| ash::mojom::AppListLaunchedFrom::kLaunchedFromSearchBox, |
| ash::mojom::AppListLaunchType::kAppSearchResult, |
| index_in_item_list_view_); |
| view_delegate_->LogResultLaunchHistogram( |
| SearchResultLaunchLocation::kTileList, index_in_item_list_view_); |
| } |
| |
| void SearchResultTileItemView::SetIcon(const gfx::ImageSkia& icon) { |
| const int icon_size = AppListConfig::instance().search_tile_icon_dimension(); |
| gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage( |
| icon, skia::ImageOperations::RESIZE_BEST, |
| gfx::Size(icon_size, icon_size))); |
| icon_->SetImage(resized); |
| } |
| |
| void SearchResultTileItemView::SetBadgeIcon(const gfx::ImageSkia& badge_icon) { |
| if (badge_icon.isNull()) { |
| badge_->SetVisible(false); |
| return; |
| } |
| |
| gfx::ImageSkia resized_badge_icon( |
| gfx::ImageSkiaOperations::CreateResizedImage( |
| badge_icon, skia::ImageOperations::RESIZE_BEST, |
| AppListConfig::instance().search_tile_badge_icon_size())); |
| |
| gfx::ShadowValues shadow_values; |
| shadow_values.push_back( |
| gfx::ShadowValue(gfx::Vector2d(0, 1), 0, SkColorSetARGB(0x33, 0, 0, 0))); |
| shadow_values.push_back( |
| gfx::ShadowValue(gfx::Vector2d(0, 1), 2, SkColorSetARGB(0x33, 0, 0, 0))); |
| badge_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow( |
| resized_badge_icon, shadow_values)); |
| badge_->SetVisible(true); |
| } |
| |
| void SearchResultTileItemView::SetTitle(const base::string16& title) { |
| title_->SetText(title); |
| title_->NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true); |
| } |
| |
| void SearchResultTileItemView::SetRating(float rating) { |
| if (!rating_) |
| return; |
| |
| if (rating < 0) { |
| rating_->SetVisible(false); |
| rating_star_->SetVisible(false); |
| return; |
| } |
| |
| rating_->SetText(base::FormatDouble(rating, 1)); |
| rating_->SetVisible(true); |
| rating_star_->SetVisible(true); |
| } |
| |
| void SearchResultTileItemView::SetPrice(const base::string16& price) { |
| if (!price_) |
| return; |
| |
| if (price.empty()) { |
| price_->SetVisible(false); |
| return; |
| } |
| |
| price_->SetText(price); |
| price_->SetVisible(true); |
| } |
| |
| AppListMenuModelAdapter::AppListViewAppType |
| SearchResultTileItemView::GetAppType() const { |
| if (IsSuggestedAppTile()) { |
| if (view_delegate_->GetModel()->state_fullscreen() == |
| ash::mojom::AppListViewState::kPeeking) { |
| return AppListMenuModelAdapter::PEEKING_SUGGESTED; |
| } else { |
| return AppListMenuModelAdapter::FULLSCREEN_SUGGESTED; |
| } |
| } else { |
| if (view_delegate_->GetModel()->state_fullscreen() == |
| ash::mojom::AppListViewState::kHalf) { |
| return AppListMenuModelAdapter::HALF_SEARCH_RESULT; |
| } else if (view_delegate_->GetModel()->state_fullscreen() == |
| ash::mojom::AppListViewState::kFullscreenSearch) { |
| return AppListMenuModelAdapter::FULLSCREEN_SEARCH_RESULT; |
| } |
| } |
| NOTREACHED(); |
| return AppListMenuModelAdapter::APP_LIST_APP_TYPE_LAST; |
| } |
| |
| bool SearchResultTileItemView::IsSuggestedAppTile() const { |
| return result() && result()->display_type() == |
| ash::SearchResultDisplayType::kRecommendation; |
| } |
| |
| bool SearchResultTileItemView::IsSuggestedAppTileShownInAppPage() const { |
| return IsSuggestedAppTile() && show_in_apps_page_; |
| } |
| |
| void SearchResultTileItemView::LogAppLaunchForSuggestedApp() const { |
| // Only log the app launch if the class is being used as a suggested app. |
| if (!IsSuggestedAppTile()) |
| return; |
| |
| // We only need to record opening the installed app in zero state, no need to |
| // record the opening of a fast re-installed app, since the latter is already |
| // recorded in ArcAppReinstallAppResult::Open. |
| if (result()->result_type() != |
| ash::SearchResultType::kPlayStoreReinstallApp) { |
| base::RecordAction( |
| base::UserMetricsAction("AppList_ZeroStateOpenInstalledApp")); |
| } |
| } |
| |
| void SearchResultTileItemView::UpdateBackgroundColor() { |
| // Tell the label what color it will be drawn onto. It will use whether the |
| // background color is opaque or transparent to decide whether to use subpixel |
| // rendering. Does not actually set the label's background color. |
| title_->SetBackgroundColor(parent_background_color_); |
| SchedulePaint(); |
| } |
| |
| void SearchResultTileItemView::Layout() { |
| gfx::Rect rect(GetContentsBounds()); |
| if (rect.IsEmpty() || !result()) |
| return; |
| |
| if (IsSuggestedAppTileShownInAppPage()) { |
| icon_->SetBoundsRect(AppListItemView::GetIconBoundsForTargetViewBounds( |
| rect, icon_->GetImage().size())); |
| title_->SetBoundsRect(AppListItemView::GetTitleBoundsForTargetViewBounds( |
| rect, title_->GetPreferredSize())); |
| } else { |
| gfx::Rect icon_rect(rect); |
| icon_rect.ClampToCenteredSize(icon_->GetImage().size()); |
| icon_rect.set_y(kSearchTileTopPadding); |
| icon_->SetBoundsRect(icon_rect); |
| |
| const int badge_icon_dimension = |
| AppListConfig::instance().search_tile_badge_icon_dimension(); |
| const int badge_icon_offset = |
| AppListConfig::instance().search_tile_badge_icon_offset(); |
| const gfx::Rect badge_rect( |
| icon_rect.right() - badge_icon_dimension + badge_icon_offset, |
| icon_rect.bottom() - badge_icon_dimension + badge_icon_offset, |
| badge_icon_dimension, badge_icon_dimension); |
| badge_->SetBoundsRect(badge_rect); |
| |
| rect.set_y(icon_rect.bottom() + kSearchTitleSpacing); |
| rect.set_height(title_->GetPreferredSize().height()); |
| title_->SetBoundsRect(rect); |
| |
| // If there is no price set, we center the rating. |
| const bool center_rating = |
| rating_ && rating_star_ && price_ && price_->text().empty(); |
| const int rating_horizontal_offset = |
| center_rating ? kSearchRatingCenteringOffset : 0; |
| |
| if (rating_) { |
| gfx::Rect rating_rect(rect); |
| rating_rect.Inset(rating_horizontal_offset, |
| title_->GetPreferredSize().height(), 0, 0); |
| rating_rect.set_size(rating_->GetPreferredSize()); |
| rating_rect.set_width(kSearchRatingSize); |
| rating_->SetBoundsRect(rating_rect); |
| } |
| |
| if (rating_star_) { |
| gfx::Rect rating_star_rect(rect); |
| rating_star_rect.Inset(rating_horizontal_offset + kSearchRatingSize + |
| kSearchRatingStarHorizontalSpacing, |
| title_->GetPreferredSize().height() + |
| kSearchRatingStarVerticalSpacing, |
| 0, 0); |
| rating_star_rect.set_size(rating_star_->GetPreferredSize()); |
| rating_star_->SetBoundsRect(rating_star_rect); |
| } |
| |
| if (price_) { |
| gfx::Rect price_rect(rect); |
| price_rect.Inset(rect.width() - kSearchPriceSize, |
| title_->GetPreferredSize().height(), 0, 0); |
| price_rect.set_size(price_->GetPreferredSize()); |
| price_->SetBoundsRect(price_rect); |
| } |
| } |
| } |
| |
| const char* SearchResultTileItemView::GetClassName() const { |
| return "SearchResultTileItemView"; |
| } |
| |
| gfx::Size SearchResultTileItemView::CalculatePreferredSize() const { |
| if (!result()) |
| return gfx::Size(); |
| |
| if (IsSuggestedAppTileShownInAppPage()) { |
| return gfx::Size(AppListConfig::instance().grid_tile_width(), |
| AppListConfig::instance().grid_tile_height()); |
| } |
| |
| return gfx::Size(kSearchTileWidth, |
| AppListConfig::instance().search_tile_height()); |
| } |
| |
| base::string16 SearchResultTileItemView::GetTooltipText( |
| const gfx::Point& p) const { |
| // Use the label to generate a tooltip, so that it will consider its text |
| // truncation in making the tooltip. We do not want the label itself to have a |
| // tooltip, so we only temporarily enable it to get the tooltip text from the |
| // label, then disable it again. |
| title_->SetHandlesTooltips(true); |
| base::string16 tooltip = title_->GetTooltipText(p); |
| title_->SetHandlesTooltips(false); |
| return tooltip; |
| } |
| |
| } // namespace app_list |