blob: b8b92b8a6a019cd1c602abfa5ab2e4712a98ae9c [file] [log] [blame]
// 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.
#include "ui/app_list/search_result_view.h"
#include "ui/app_list/search_result.h"
#include "ui/app_list/search_result_list_view.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font.h"
#include "ui/gfx/render_text.h"
#include "ui/views/controls/image_view.h"
namespace {
const int kPreferredWidth = 300;
const int kPreferredHeight = 52;
const int kIconDimension = 32;
const int kIconPadding = 14;
const int kIconViewWidth = kIconDimension + 2 * kIconPadding;
const int kTextTrailPadding = kIconPadding;
const int kBorderSize = 1;
const SkColor kBorderColor = SkColorSetARGB(0x0F, 0, 0, 0);
const SkColor kDefaultTextColor = SkColorSetRGB(0x33, 0x33, 0x33);
const SkColor kDimmedTextColor = SkColorSetRGB(0x96, 0x96, 0x96);
const SkColor kURLTextColor = SkColorSetRGB(0x00, 0x99, 0x33);
const SkColor kSelectedBorderColor = kBorderColor;
const SkColor kSelectedBackgroundColor = SkColorSetARGB(0x0F, 0x4D, 0x90, 0xFE);
const SkColor kHoverAndPushedColor = SkColorSetARGB(0x05, 0, 0, 0);
// A non-interactive image view to display result icon.
class IconView : public views::ImageView {
public:
IconView() : ImageView() {}
virtual ~IconView() {}
private:
// views::View overrides:
virtual bool HitTest(const gfx::Point& l) const OVERRIDE {
return false;
}
DISALLOW_COPY_AND_ASSIGN(IconView);
};
// Creates a RenderText of given |text| and |styles|. Caller takes ownership
// of returned RenderText.
gfx::RenderText* CreateRenderText(const string16& text,
const app_list::SearchResult::Tags& tags) {
gfx::RenderText* render_text = gfx::RenderText::CreateInstance();
render_text->SetText(text);
gfx::StyleRange default_style;
default_style.foreground = kDefaultTextColor;
render_text->set_default_style(default_style);
render_text->ApplyDefaultStyle();
for (app_list::SearchResult::Tags::const_iterator it = tags.begin();
it != tags.end();
++it) {
// NONE means default style so do nothing.
if (it->styles == app_list::SearchResult::Tag::NONE)
continue;
gfx::StyleRange style;
style.range = it->range;
if (it->styles & app_list::SearchResult::Tag::MATCH)
style.font_style = gfx::Font::BOLD;
if (it->styles & app_list::SearchResult::Tag::URL)
style.foreground = kURLTextColor;
if (it->styles & app_list::SearchResult::Tag::DIM)
style.foreground = kDimmedTextColor;
render_text->ApplyStyleRange(style);
}
return render_text;
}
} // namespace
namespace app_list {
// static
const char SearchResultView::kViewClassName[] = "ui/app_list/SearchResultView";
SearchResultView::SearchResultView(SearchResultListView* list_view,
views::ButtonListener* listener)
: views::CustomButton(listener),
result_(NULL),
list_view_(list_view),
icon_(new IconView) {
AddChildView(icon_);
}
SearchResultView::~SearchResultView() {
ClearResultNoRepaint();
}
void SearchResultView::SetResult(SearchResult* result) {
ClearResultNoRepaint();
result_ = result;
if (result_)
result_->AddObserver(this);
OnIconChanged();
UpdateTitleText();
UpdateDetailsText();
SchedulePaint();
}
void SearchResultView::ClearResultNoRepaint() {
if (result_)
result_->RemoveObserver(this);
result_ = NULL;
}
void SearchResultView::UpdateTitleText() {
if (!result_ || result_->title().empty()) {
title_text_.reset();
} else {
title_text_.reset(CreateRenderText(result_->title(),
result_->title_tags()));
}
}
void SearchResultView::UpdateDetailsText() {
if (!result_ || result_->details().empty()) {
details_text_.reset();
} else {
details_text_.reset(CreateRenderText(result_->details(),
result_->details_tags()));
}
}
std::string SearchResultView::GetClassName() const {
return kViewClassName;
}
gfx::Size SearchResultView::GetPreferredSize() {
return gfx::Size(kPreferredWidth, kPreferredHeight);
}
void SearchResultView::Layout() {
gfx::Rect rect(GetContentsBounds());
if (rect.IsEmpty())
return;
gfx::Rect icon_bounds(rect);
icon_bounds.set_width(kIconViewWidth);
icon_bounds.Inset(kIconPadding, (rect.height() - kIconDimension) / 2);
icon_->SetBoundsRect(icon_bounds.Intersect(rect));
}
void SearchResultView::OnPaint(gfx::Canvas* canvas) {
gfx::Rect rect(GetContentsBounds());
if (rect.IsEmpty())
return;
gfx::Rect content_rect(rect);
content_rect.set_height(rect.height() - kBorderSize);
bool selected = list_view_->IsResultViewSelected(this);
if (selected) {
canvas->FillRect(content_rect, kSelectedBackgroundColor);
} else if (state() == BS_HOT || state() == BS_PUSHED) {
canvas->FillRect(content_rect, kHoverAndPushedColor);
}
gfx::Rect border_bottom = rect.Subtract(content_rect);
canvas->FillRect(border_bottom,
selected ? kSelectedBorderColor : kBorderColor);
gfx::Rect text_bounds(rect);
text_bounds.set_x(kIconViewWidth);
text_bounds.set_width(rect.width() - kIconViewWidth - kTextTrailPadding);
if (title_text_.get() && details_text_.get()) {
gfx::Size title_size(text_bounds.width(),
title_text_->GetStringSize().height());
gfx::Size details_size(text_bounds.width(),
details_text_->GetStringSize().height());
int total_height = title_size.height() + + details_size.height();
int y = text_bounds.y() + (text_bounds.height() - total_height) / 2;
title_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
title_size));
title_text_->Draw(canvas);
y += title_size.height();
details_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
details_size));
details_text_->Draw(canvas);
} else if (title_text_.get()) {
gfx::Size title_size(text_bounds.width(),
title_text_->GetStringSize().height());
title_text_->SetDisplayRect(text_bounds.Center(title_size));
title_text_->Draw(canvas);
}
}
void SearchResultView::OnIconChanged() {
gfx::ImageSkia image(result_ ? result_->icon() : gfx::ImageSkia());
// Note this might leave the view with an old icon. But it is needed to avoid
// flash when a SearchResult's icon is loaded asynchronously. In this case, it
// looks nicer to keep the stale icon for a little while on screen instead of
// clearing it out. It should work correctly as long as the SearchResult does
// not forget to SetIcon when it's ready.
if (image.empty())
return;
// Scales down big icons but leave small ones unchanged.
if (image.width() > kIconDimension || image.height() > kIconDimension)
icon_->SetImageSize(gfx::Size(kIconDimension, kIconDimension));
else
icon_->ResetImageSize();
icon_->SetImage(image);
}
} // namespace app_list