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.
5 #include "ui/app_list/search_result_view.h"
7 #include "ui/app_list/app_list_constants.h"
8 #include "ui/app_list/search_result.h"
9 #include "ui/app_list/search_result_list_view.h"
10 #include "ui/gfx/canvas.h"
11 #include "ui/gfx/font.h"
12 #include "ui/gfx/image/image_skia_operations.h"
13 #include "ui/gfx/render_text.h"
14 #include "ui/views/controls/button/image_button.h"
15 #include "ui/views/controls/image_view.h"
19 const int kPreferredWidth
= 300;
20 const int kPreferredHeight
= 52;
21 const int kIconDimension
= 32;
22 const int kIconPadding
= 14;
23 const int kIconViewWidth
= kIconDimension
+ 2 * kIconPadding
;
24 const int kTextTrailPadding
= kIconPadding
;
25 const int kBorderSize
= 1;
27 // Maximum dimensions of a search result's action icons.
28 const int kActionIconDimension
= 24;
30 // Total space (including padding) used for each action icon's button.
31 const int kActionButtonWidth
= 32;
33 // Extra margin at the right of the rightmost action icon.
34 const int kActionButtonRightMargin
= 8;
36 const SkColor kBorderColor
= SkColorSetARGB(0x0F, 0, 0, 0);
38 const SkColor kDefaultTextColor
= SkColorSetRGB(0x33, 0x33, 0x33);
39 const SkColor kDimmedTextColor
= SkColorSetRGB(0x96, 0x96, 0x96);
40 const SkColor kURLTextColor
= SkColorSetRGB(0x00, 0x99, 0x33);
42 const SkColor kSelectedBorderColor
= kBorderColor
;
43 const SkColor kSelectedBackgroundColor
= SkColorSetARGB(0x0F, 0x4D, 0x90, 0xFE);
44 const SkColor kHoverAndPushedColor
= SkColorSetARGB(0x05, 0, 0, 0);
46 // A non-interactive image view to display result icon.
47 class IconView
: public views::ImageView
{
49 IconView() : ImageView() {}
50 virtual ~IconView() {}
53 // views::View overrides:
54 virtual bool HitTestRect(const gfx::Rect
& rect
) const OVERRIDE
{
58 DISALLOW_COPY_AND_ASSIGN(IconView
);
61 // Creates a RenderText of given |text| and |styles|. Caller takes ownership
62 // of returned RenderText.
63 gfx::RenderText
* CreateRenderText(const string16
& text
,
64 const app_list::SearchResult::Tags
& tags
) {
65 gfx::RenderText
* render_text
= gfx::RenderText::CreateInstance();
66 render_text
->SetText(text
);
68 gfx::StyleRange default_style
;
69 default_style
.foreground
= kDefaultTextColor
;
70 render_text
->set_default_style(default_style
);
71 render_text
->ApplyDefaultStyle();
73 for (app_list::SearchResult::Tags::const_iterator it
= tags
.begin();
76 // NONE means default style so do nothing.
77 if (it
->styles
== app_list::SearchResult::Tag::NONE
)
80 gfx::StyleRange style
;
81 style
.range
= it
->range
;
83 if (it
->styles
& app_list::SearchResult::Tag::MATCH
)
84 style
.font_style
= gfx::Font::BOLD
;
85 if (it
->styles
& app_list::SearchResult::Tag::URL
)
86 style
.foreground
= kURLTextColor
;
87 if (it
->styles
& app_list::SearchResult::Tag::DIM
)
88 style
.foreground
= kDimmedTextColor
;
90 render_text
->ApplyStyleRange(style
);
101 const char SearchResultView::kViewClassName
[] = "ui/app_list/SearchResultView";
103 SearchResultView::SearchResultView(SearchResultListView
* list_view
,
104 SearchResultViewDelegate
* delegate
)
105 : views::CustomButton(this),
107 list_view_(list_view
),
109 icon_(new IconView
) {
113 SearchResultView::~SearchResultView() {
114 ClearResultNoRepaint();
117 void SearchResultView::SetResult(SearchResult
* result
) {
118 ClearResultNoRepaint();
122 result_
->AddObserver(this);
125 OnActionIconsChanged();
131 void SearchResultView::ClearResultNoRepaint() {
133 result_
->RemoveObserver(this);
137 void SearchResultView::UpdateTitleText() {
138 if (!result_
|| result_
->title().empty()) {
141 title_text_
.reset(CreateRenderText(result_
->title(),
142 result_
->title_tags()));
146 void SearchResultView::UpdateDetailsText() {
147 if (!result_
|| result_
->details().empty()) {
148 details_text_
.reset();
150 details_text_
.reset(CreateRenderText(result_
->details(),
151 result_
->details_tags()));
155 std::string
SearchResultView::GetClassName() const {
156 return kViewClassName
;
159 gfx::Size
SearchResultView::GetPreferredSize() {
160 return gfx::Size(kPreferredWidth
, kPreferredHeight
);
163 void SearchResultView::Layout() {
164 gfx::Rect
rect(GetContentsBounds());
168 gfx::Rect
icon_bounds(rect
);
169 icon_bounds
.set_width(kIconViewWidth
);
170 icon_bounds
.Inset(kIconPadding
, (rect
.height() - kIconDimension
) / 2);
171 icon_bounds
.Intersect(rect
);
172 icon_
->SetBoundsRect(icon_bounds
);
174 size_t num_buttons
= action_buttons_
.size();
175 for (size_t i
= 0; i
< num_buttons
; ++i
) {
176 views::ImageButton
* button
= action_buttons_
[i
];
177 gfx::Rect
button_bounds(
178 rect
.right() - kActionButtonRightMargin
-
179 (num_buttons
- i
) * kActionButtonWidth
,
180 (rect
.y() + rect
.height() - kActionIconDimension
) / 2,
181 kActionButtonWidth
, kActionIconDimension
);
182 button
->SetBoundsRect(button_bounds
);
186 void SearchResultView::OnPaint(gfx::Canvas
* canvas
) {
187 gfx::Rect
rect(GetContentsBounds());
191 gfx::Rect
content_rect(rect
);
192 content_rect
.set_height(rect
.height() - kBorderSize
);
194 canvas
->FillRect(content_rect
, kContentsBackgroundColor
);
196 bool selected
= list_view_
->IsResultViewSelected(this);
198 canvas
->FillRect(content_rect
, kSelectedBackgroundColor
);
199 } else if (state() == STATE_HOVERED
|| state() == STATE_PRESSED
) {
200 canvas
->FillRect(content_rect
, kHoverAndPushedColor
);
203 gfx::Rect border_bottom
= gfx::SubtractRects(rect
, content_rect
);
204 canvas
->FillRect(border_bottom
,
205 selected
? kSelectedBorderColor
: kBorderColor
);
207 gfx::Rect
text_bounds(rect
);
208 text_bounds
.set_x(kIconViewWidth
);
209 text_bounds
.set_width(
210 rect
.width() - kIconViewWidth
- kTextTrailPadding
-
211 action_buttons_
.size() * kActionButtonWidth
-
212 (!action_buttons_
.empty() ? kActionButtonRightMargin
: 0));
213 text_bounds
.set_x(GetMirroredXWithWidthInView(text_bounds
.x(),
214 text_bounds
.width()));
216 if (title_text_
.get() && details_text_
.get()) {
217 gfx::Size
title_size(text_bounds
.width(),
218 title_text_
->GetStringSize().height());
219 gfx::Size
details_size(text_bounds
.width(),
220 details_text_
->GetStringSize().height());
221 int total_height
= title_size
.height() + + details_size
.height();
222 int y
= text_bounds
.y() + (text_bounds
.height() - total_height
) / 2;
224 title_text_
->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds
.x(), y
),
226 title_text_
->Draw(canvas
);
228 y
+= title_size
.height();
229 details_text_
->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds
.x(), y
),
231 details_text_
->Draw(canvas
);
232 } else if (title_text_
.get()) {
233 gfx::Size
title_size(text_bounds
.width(),
234 title_text_
->GetStringSize().height());
235 gfx::Rect
centered_title_rect(text_bounds
);
236 centered_title_rect
.ClampToCenteredSize(title_size
);
237 title_text_
->SetDisplayRect(centered_title_rect
);
238 title_text_
->Draw(canvas
);
242 void SearchResultView::ButtonPressed(views::Button
* sender
,
243 const ui::Event
& event
) {
244 if (sender
== this) {
245 delegate_
->SearchResultActivated(this, event
);
249 for (size_t i
= 0; i
< action_buttons_
.size(); ++i
) {
250 if (sender
== action_buttons_
[i
]) {
251 delegate_
->SearchResultActionActivated(this, i
, event
);
255 NOTREACHED() << "Got unexpected button press on " << sender
;
258 void SearchResultView::OnIconChanged() {
259 gfx::ImageSkia
image(result_
? result_
->icon() : gfx::ImageSkia());
260 // Note this might leave the view with an old icon. But it is needed to avoid
261 // flash when a SearchResult's icon is loaded asynchronously. In this case, it
262 // looks nicer to keep the stale icon for a little while on screen instead of
263 // clearing it out. It should work correctly as long as the SearchResult does
264 // not forget to SetIcon when it's ready.
268 // Scales down big icons but leave small ones unchanged.
269 if (image
.width() > kIconDimension
|| image
.height() > kIconDimension
) {
270 image
= gfx::ImageSkiaOperations::CreateResizedImage(
272 skia::ImageOperations::RESIZE_BEST
,
273 gfx::Size(kIconDimension
, kIconDimension
));
275 icon_
->ResetImageSize();
278 icon_
->SetImage(image
);
281 void SearchResultView::OnActionIconsChanged() {
282 while (action_buttons_
.size() >
283 (result_
? result_
->action_icons().size() : 0)) {
284 RemoveChildView(action_buttons_
.back());
285 action_buttons_
.pop_back();
289 while (action_buttons_
.size() < result_
->action_icons().size()) {
290 views::ImageButton
* button
= new views::ImageButton(this);
291 button
->SetImageAlignment(views::ImageButton::ALIGN_CENTER
,
292 views::ImageButton::ALIGN_MIDDLE
);
293 AddChildView(button
);
294 action_buttons_
.push_back(button
);
297 const SearchResult::ActionIconSets
& icons
= result_
->action_icons();
298 for (size_t i
= 0; i
< icons
.size(); ++i
) {
299 const SearchResult::ActionIconSet
& icon
= icons
.at(i
);
300 views::ImageButton
* button
= action_buttons_
[i
];
301 button
->SetImage(views::CustomButton::STATE_NORMAL
, &icon
.base_image
);
302 button
->SetImage(views::CustomButton::STATE_HOVERED
, &icon
.hover_image
);
303 button
->SetImage(views::CustomButton::STATE_PRESSED
, &icon
.pressed_image
);
304 button
->SetTooltipText(icon
.tooltip_text
);
309 } // namespace app_list