// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/compositor/layer/contextual_search_layer.h"
#include "cc/resources/scoped_ui_resource.h"
#include "cc/slim/layer.h"
#include "cc/slim/nine_patch_layer.h"
#include "cc/slim/solid_color_layer.h"
#include "cc/slim/ui_resource_layer.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/android/resources/nine_patch_resource.h"
#include "ui/android/resources/resource_manager.h"
#include "ui/base/l10n/l10n_util_android.h"
#include "ui/gfx/color_utils.h"
namespace {
const SkColor kSearchBackgroundColor = SkColorSetRGB(0xee, 0xee, 0xee);
const SkColor kTouchHighlightColor = SkColorSetARGB(0x33, 0x99, 0x99, 0x99);
} // namespace
namespace android {
// static
scoped_refptr<ContextualSearchLayer> ContextualSearchLayer::Create(
ui::ResourceManager* resource_manager) {
return base::WrapRefCounted(new ContextualSearchLayer(resource_manager));
}
void ContextualSearchLayer::SetProperties(
int panel_shadow_resource_id,
int search_bar_background_color,
int search_context_resource_id,
int search_term_resource_id,
int search_caption_resource_id,
int search_bar_shadow_resource_id,
int search_provider_icon_resource_id,
int quick_action_icon_resource_id,
int drag_handlebar_resource_id,
int open_tab_icon_resource_id,
int close_icon_resource_id,
int progress_bar_background_resource_id,
int progress_bar_background_tint,
int progress_bar_resource_id,
int progress_bar_tint,
int search_promo_resource_id,
float dp_to_px,
const scoped_refptr<cc::slim::Layer>& content_layer,
bool search_promo_visible,
float search_promo_height,
float search_promo_opacity,
int search_promo_background_color,
// Related Searches
int related_searches_in_bar_resource_id,
bool related_searches_in_bar_visible,
float related_searches_in_bar_height,
float related_searches_in_bar_redundant_padding,
// Position etc
float search_panel_x,
float search_panel_y,
float search_panel_width,
float search_panel_height,
float search_bar_margin_side,
float search_bar_margin_top,
float search_bar_margin_bottom,
float search_bar_height,
float search_context_opacity,
float search_text_layer_min_height,
float search_term_opacity,
float search_term_caption_spacing,
float search_caption_animation_percentage,
bool search_caption_visible,
bool search_bar_border_visible,
float search_bar_border_height,
bool quick_action_icon_visible,
bool thumbnail_visible,
float custom_image_visibility_percentage,
int bar_image_size,
int icon_color,
int drag_handlebar_color,
float close_icon_opacity,
bool progress_bar_visible,
float progress_bar_height,
float progress_bar_opacity,
float progress_bar_completion,
bool touch_highlight_visible,
float touch_highlight_x_offset,
float touch_highlight_width,
int rounded_bar_top_resource_id,
int separator_line_color) {
// Round values to avoid pixel gap between layers.
search_bar_height = floor(search_bar_height);
float search_bar_top = 0.f;
float search_bar_bottom = search_bar_top + search_bar_height;
bool should_render_progress_bar =
progress_bar_visible && progress_bar_opacity > 0.f;
OverlayPanelLayer::SetResourceIds(
search_term_resource_id, panel_shadow_resource_id,
rounded_bar_top_resource_id, search_bar_shadow_resource_id,
search_provider_icon_resource_id, drag_handlebar_resource_id,
open_tab_icon_resource_id, close_icon_resource_id);
// TODO(donnd): Update when moving Related Searches.
float content_view_top = search_bar_bottom + search_promo_height;
float should_render_bar_border = search_bar_border_visible
&& !should_render_progress_bar;
// -----------------------------------------------------------------
// Overlay Panel
// -----------------------------------------------------------------
OverlayPanelLayer::SetProperties(
dp_to_px, content_layer, content_view_top, search_panel_x, search_panel_y,
search_panel_width, search_panel_height, search_bar_background_color,
search_bar_margin_side, search_bar_margin_top, search_bar_margin_bottom,
search_bar_height, search_bar_top, search_term_opacity,
should_render_bar_border, search_bar_border_height, icon_color,
drag_handlebar_color, close_icon_opacity, separator_line_color,
related_searches_in_bar_height);
// -----------------------------------------------------------------
// Content setup, to center in space below drag handle.
// -----------------------------------------------------------------
int content_height = search_bar_height - search_bar_margin_top -
related_searches_in_bar_height -
search_bar_margin_bottom;
int content_top = search_bar_top + search_bar_margin_top;
// ---------------------------------------------------------------------------
// Search Term, Context and Search Caption
// ---------------------------------------------------------------------------
int text_layer_height =
SetupTextLayer(content_top, content_height, search_text_layer_min_height,
search_caption_resource_id, search_caption_visible,
search_caption_animation_percentage, search_term_opacity,
search_context_resource_id, search_context_opacity,
search_term_caption_spacing);
// Tracks the top of the next section to draw.
int next_section_top = search_bar_bottom;
// ---------------------------------------------------------------------------
// Related Searches In-Bar Control
// ---------------------------------------------------------------------------
if (related_searches_in_bar_visible) {
// Grabs the Related Searches in-bar resource.
ui::Resource* related_searches_resource = resource_manager_->GetResource(
ui::ANDROID_RESOURCE_TYPE_DYNAMIC, related_searches_in_bar_resource_id);
DCHECK(related_searches_resource);
if (related_searches_resource) {
gfx::Size related_searches_size(
search_panel_width, related_searches_resource->size().height());
if (related_searches_in_bar_->parent() != layer_) {
layer_->AddChild(related_searches_in_bar_);
}
related_searches_in_bar_->SetUIResourceId(
related_searches_resource->ui_resource()->id());
related_searches_in_bar_->SetBounds(related_searches_size);
int related_searches_top =
search_bar_bottom - related_searches_in_bar_height -
related_searches_in_bar_redundant_padding - search_bar_margin_bottom;
related_searches_in_bar_->SetPosition(
gfx::PointF(0.f, related_searches_top));
}
} else if (related_searches_in_bar_.get() &&
related_searches_in_bar_->parent()) {
related_searches_in_bar_->RemoveFromParent();
}
// ---------------------------------------------------------------------------
// Search Promo
// ---------------------------------------------------------------------------
if (search_promo_visible) {
// Grabs the Search Opt Out Promo resource.
ui::Resource* search_promo_resource = resource_manager_->GetResource(
ui::ANDROID_RESOURCE_TYPE_DYNAMIC, search_promo_resource_id);
// Search Promo Container
if (search_promo_container_->parent() != layer_) {
// NOTE(donnd): This layer can appear just below the Bar so it should be
// always placed before the Search Bar Shadow to make sure it won't
// occlude the shadow. Since layer 0 is the shadow for the sheet itself,
// this needs to be layer 1.
layer_->InsertChild(search_promo_container_, 1);
}
if (search_promo_resource) {
int search_promo_content_height = search_promo_resource->size().height();
gfx::Size search_promo_size(search_panel_width, search_promo_height);
search_promo_container_->SetBounds(search_promo_size);
search_promo_container_->SetPosition(gfx::PointF(0.f, next_section_top));
search_promo_container_->SetMasksToBounds(true);
// TODO(crbug.com/40219248): Remove FromColor and make all SkColor4f.
search_promo_container_->SetBackgroundColor(
SkColor4f::FromColor(search_promo_background_color));
// Search Promo
if (search_promo_->parent() != search_promo_container_)
search_promo_container_->AddChild(search_promo_);
search_promo_->SetUIResourceId(
search_promo_resource->ui_resource()->id());
search_promo_->SetBounds(search_promo_resource->size());
// Align promo at the bottom of the container so the confirmation button
// is not clipped when resizing the promo.
search_promo_->SetPosition(
gfx::PointF(0.f, search_promo_height - search_promo_content_height));
search_promo_->SetOpacity(search_promo_opacity);
// Next section goes beyond this section.
next_section_top += search_promo_content_height;
}
} else {
// Search Promo Container
if (search_promo_container_.get() && search_promo_container_->parent())
search_promo_container_->RemoveFromParent();
}
// ---------------------------------------------------------------------------
// Progress Bar
// ---------------------------------------------------------------------------
OverlayPanelLayer::SetProgressBar(
progress_bar_background_resource_id, progress_bar_background_tint,
progress_bar_resource_id, progress_bar_tint, progress_bar_visible,
search_bar_bottom, progress_bar_height, progress_bar_opacity,
progress_bar_completion, search_panel_width);
// ---------------------------------------------------------------------------
// Touch Highlight Layer
// ---------------------------------------------------------------------------
if (touch_highlight_visible) {
if (touch_highlight_layer_->parent() != layer_)
layer_->AddChild(touch_highlight_layer_);
// In the new layout don't highlight the whole bar due to rounded corners.
int highlight_height = text_layer_height;
int highlight_top = content_top;
highlight_top += (content_height - text_layer_height) / 2;
gfx::Size background_size(touch_highlight_width, highlight_height);
touch_highlight_layer_->SetBounds(background_size);
touch_highlight_layer_->SetPosition(
gfx::PointF(touch_highlight_x_offset, highlight_top));
} else {
touch_highlight_layer_->RemoveFromParent();
}
// ---------------------------------------------------------------------------
// Icon Layer
// ---------------------------------------------------------------------------
bar_image_size_ = bar_image_size;
SetupIconLayer(search_provider_icon_resource_id, quick_action_icon_visible,
quick_action_icon_resource_id, thumbnail_visible,
custom_image_visibility_percentage);
}
scoped_refptr<cc::slim::Layer> ContextualSearchLayer::GetIconLayer() {
return icon_layer_;
}
void ContextualSearchLayer::SetupIconLayer(
int search_provider_icon_resource_id,
bool quick_action_icon_visible,
int quick_action_icon_resource_id,
bool thumbnail_visible,
float custom_image_visibility_percentage) {
icon_layer_->SetBounds(gfx::Size(bar_image_size_, bar_image_size_));
icon_layer_->SetMasksToBounds(true);
if (quick_action_icon_visible) {
if (quick_action_icon_layer_->parent() != icon_layer_)
icon_layer_->AddChild(quick_action_icon_layer_);
ui::Resource* quick_action_icon_resource = resource_manager_->GetResource(
ui::ANDROID_RESOURCE_TYPE_DYNAMIC, quick_action_icon_resource_id);
if (quick_action_icon_resource) {
quick_action_icon_layer_->SetUIResourceId(
quick_action_icon_resource->ui_resource()->id());
quick_action_icon_layer_->SetBounds(
gfx::Size(bar_image_size_, bar_image_size_));
SetCustomImageProperties(quick_action_icon_layer_, 0, 0,
custom_image_visibility_percentage);
}
} else if (quick_action_icon_layer_->parent()) {
quick_action_icon_layer_->RemoveFromParent();
}
// Thumbnail
if (!quick_action_icon_visible && thumbnail_visible) {
if (thumbnail_layer_->parent() != icon_layer_)
icon_layer_->AddChild(thumbnail_layer_);
SetCustomImageProperties(thumbnail_layer_, thumbnail_top_margin_,
thumbnail_side_margin_,
custom_image_visibility_percentage);
} else if (thumbnail_layer_->parent()) {
thumbnail_layer_->RemoveFromParent();
}
// Search Provider Icon
if (search_provider_icon_layer_->parent() != icon_layer_)
icon_layer_->AddChild(search_provider_icon_layer_);
ui::Resource* search_provider_icon_resource = resource_manager_->GetResource(
ui::ANDROID_RESOURCE_TYPE_STATIC, search_provider_icon_resource_id);
if (search_provider_icon_resource) {
gfx::Size icon_size = search_provider_icon_resource->size();
search_provider_icon_layer_->SetUIResourceId(
search_provider_icon_resource->ui_resource()->id());
search_provider_icon_layer_->SetBounds(icon_size);
search_provider_icon_layer_->SetOpacity(1.f -
custom_image_visibility_percentage);
// Determine x and y offsets to center the icon in its parent layer
float icon_x_offset = (bar_image_size_ - icon_size.width()) / 2;
float icon_y_offset = (bar_image_size_ - icon_size.height()) / 2;
// Determine extra y-offset if thumbnail or quick action are visible.
icon_y_offset -= (bar_image_size_ * custom_image_visibility_percentage);
search_provider_icon_layer_->SetPosition(
gfx::PointF(icon_x_offset, icon_y_offset));
}
}
void ContextualSearchLayer::SetCustomImageProperties(
scoped_refptr<cc::slim::UIResourceLayer> custom_image_layer,
float top_margin,
float side_margin,
float visibility_percentage) {
custom_image_layer->SetOpacity(visibility_percentage);
// When animating, the custom image and search provider icon slide through
// |icon_layer_|. This effect is achieved by changing the y-offset
// for each child layer.
// If the custom image has a height less than |bar_image_size_|, it will
// have a top margin that needs to be accounted for while running the
// animation. The final |custom_image_y_offset| should be equal to
// |tpp_margin|.
float custom_image_y_offset =
(bar_image_size_ * (1.f - visibility_percentage)) + top_margin;
custom_image_layer->SetPosition(
gfx::PointF(side_margin, custom_image_y_offset));
}
int ContextualSearchLayer::SetupTextLayer(float content_top,
float content_height,
float search_text_layer_min_height,
int caption_resource_id,
bool caption_visible,
float animation_percentage,
float search_term_opacity,
int context_resource_id,
float context_opacity,
float term_caption_spacing) {
// ---------------------------------------------------------------------------
// Setup the Drawing Hierarchy
// ---------------------------------------------------------------------------
// Search Term
DCHECK(text_layer_.get());
DCHECK(bar_text_.get());
DCHECK(search_caption_.get());
bool bar_text_visible = search_term_opacity > 0.0f;
if (bar_text_visible && bar_text_->parent() != text_layer_)
text_layer_->AddChild(bar_text_);
// Search Context
ui::Resource* context_resource = resource_manager_->GetResource(
ui::ANDROID_RESOURCE_TYPE_DYNAMIC, context_resource_id);
if (context_resource) {
search_context_->SetUIResourceId(context_resource->ui_resource()->id());
search_context_->SetBounds(context_resource->size());
}
// Search Caption
ui::Resource* caption_resource = nullptr;
if (caption_visible) {
// Grabs the dynamic Search Caption resource so we can get a snapshot.
caption_resource = resource_manager_->GetResource(
ui::ANDROID_RESOURCE_TYPE_DYNAMIC, caption_resource_id);
}
if (caption_visible && animation_percentage != 0.f) {
if (search_caption_->parent() != text_layer_) {
text_layer_->AddChild(search_caption_);
}
if (caption_resource) {
search_caption_->SetUIResourceId(caption_resource->ui_resource()->id());
search_caption_->SetBounds(caption_resource->size());
}
} else if (search_caption_->parent()) {
search_caption_->RemoveFromParent();
}
// ---------------------------------------------------------------------------
// Calculate Text Layer Size
// ---------------------------------------------------------------------------
// If space allows, the Term, Context and Caption should occupy a Text
// section of fixed size.
// We may not be able to fit these inside the ideal size as the user may have
// their Font Size set to large.
// The Term might not be visible or initialized yet, so set up main_text with
// whichever main bar text seems appropriate.
scoped_refptr<cc::slim::UIResourceLayer> main_text =
(bar_text_visible ? bar_text_ : search_context_);
// The search_caption_ may not have had it's resource set by this point, if so
// the bounds will be zero and everything will still work.
float term_height = main_text->bounds().height();
float caption_height = search_caption_->bounds().height();
float layer_height = std::max(search_text_layer_min_height,
term_height + caption_height + term_caption_spacing);
float layer_width =
std::max(main_text->bounds().width(), search_caption_->bounds().width());
float layer_top = content_top + (content_height - layer_height) / 2;
text_layer_->SetBounds(gfx::Size(layer_width, layer_height));
text_layer_->SetPosition(gfx::PointF(0.f, layer_top));
text_layer_->SetMasksToBounds(true);
// ---------------------------------------------------------------------------
// Layout Text Layer
// ---------------------------------------------------------------------------
// ---Top of Search Bar--- <- bar_top
//
// ---Top of Text Layer--- <- layer_top
// } remaining_height / 2
// Term & Context } term_height
// } term_caption_spacing
// Caption } caption_height
// } remaining_height / 2
// --Bottom of Text Layer-
//
// --Bottom of Search Bar-
// If the Caption is not visible the Term is centered in this space, when
// the Caption becomes visible it is animated sliding up into it's position
// with the spacings determined by UI.
// The Term and the Context are assumed to be the same height and will be
// positioned one on top of the other. When the Context is resolved it will
// fade out and the Term will fade in.
search_context_->SetOpacity(context_opacity);
bar_text_->SetOpacity(search_term_opacity);
// If there is no caption, just vertically center the Search Term.
float term_top = (layer_height - term_height) / 2;
// If we aren't displaying the caption we're done.
if (!caption_visible || animation_percentage == 0.f || !caption_resource) {
bar_text_->SetPosition(gfx::PointF(0.f, term_top));
search_context_->SetPosition(gfx::PointF(0.f, term_top));
return layer_height;
}
// Calculate the positions for the Term and Caption when the Caption
// animation is complete.
float remaining_height = layer_height
- term_height
- term_caption_spacing
- caption_height;
float term_top_end = remaining_height / 2;
float caption_top_end = term_top_end + term_height + term_caption_spacing;
// Interpolate between the animation start and end positions (short cut
// if the animation is at the end or start).
term_top = term_top * (1.f - animation_percentage)
+ term_top_end * animation_percentage;
// The Caption starts off the bottom of the Text Layer.
float caption_top = layer_height * (1.f - animation_percentage)
+ caption_top_end * animation_percentage;
bar_text_->SetPosition(gfx::PointF(0.f, term_top));
search_context_->SetPosition(gfx::PointF(0.f, term_top));
search_caption_->SetPosition(gfx::PointF(0.f, caption_top));
return layer_height;
}
void ContextualSearchLayer::SetThumbnail(const SkBitmap* thumbnail) {
// Determine the scaled thumbnail width and height. If both the height and
// width of |thumbnail| are larger than |bar_image_size_|, the thumbnail
// will be scaled down by a call to Layer::SetBounds() below.
int min_dimension = std::min(thumbnail->width(), thumbnail->height());
int scaled_thumbnail_width = thumbnail->width();
int scaled_thumbnail_height = thumbnail->height();
if (min_dimension > bar_image_size_) {
scaled_thumbnail_width =
scaled_thumbnail_width * bar_image_size_ / min_dimension;
scaled_thumbnail_height =
scaled_thumbnail_height * bar_image_size_ / min_dimension;
}
// Determine the UV transform coordinates. This will crop the thumbnail.
// (0, 0) is the default top left corner. (1, 1) is the default bottom
// right corner.
float top_left_x = 0;
float top_left_y = 0;
float bottom_right_x = 1;
float bottom_right_y = 1;
if (scaled_thumbnail_width > bar_image_size_) {
// Crop an even amount on the left and right sides of the thumbnail.
float top_left_x_px = (scaled_thumbnail_width - bar_image_size_) / 2.f;
float bottom_right_x_px = top_left_x_px + bar_image_size_;
top_left_x = top_left_x_px / scaled_thumbnail_width;
bottom_right_x = bottom_right_x_px / scaled_thumbnail_width;
} else if (scaled_thumbnail_height > bar_image_size_) {
// Crop an even amount on the top and bottom of the thumbnail.
float top_left_y_px = (scaled_thumbnail_height - bar_image_size_) / 2.f;
float bottom_right_y_px = top_left_y_px + bar_image_size_;
top_left_y = top_left_y_px / scaled_thumbnail_height;
bottom_right_y = bottom_right_y_px / scaled_thumbnail_height;
}
// If the original |thumbnail| height or width is smaller than
// |bar_image_size_| determine the side and top margins needed to center
// the thumbnail.
thumbnail_side_margin_ = 0;
thumbnail_top_margin_ = 0;
if (scaled_thumbnail_width < bar_image_size_) {
thumbnail_side_margin_ = (bar_image_size_ - scaled_thumbnail_width) / 2.f;
}
if (scaled_thumbnail_height < bar_image_size_) {
thumbnail_top_margin_ = (bar_image_size_ - scaled_thumbnail_height) / 2.f;
}
// Determine the layer bounds. This will down scale the thumbnail if
// necessary and ensure it is displayed at |bar_image_size_|. If
// either the original |thumbnail| height or width is smaller than
// |bar_image_size_|, the thumbnail will not be scaled.
int layer_width = std::min(bar_image_size_, scaled_thumbnail_width);
int layer_height = std::min(bar_image_size_, scaled_thumbnail_height);
// UIResourceLayer requires an immutable copy of the input |thumbnail|.
SkBitmap thumbnail_copy;
if (thumbnail->isImmutable()) {
thumbnail_copy = *thumbnail;
} else {
if (thumbnail_copy.tryAllocPixels(thumbnail->info())) {
thumbnail->readPixels(thumbnail_copy.info(), thumbnail_copy.getPixels(),
thumbnail_copy.rowBytes(), 0, 0);
}
thumbnail_copy.setImmutable();
}
thumbnail_layer_->SetBitmap(thumbnail_copy);
thumbnail_layer_->SetBounds(gfx::Size(layer_width, layer_height));
thumbnail_layer_->SetPosition(
gfx::PointF(thumbnail_side_margin_, thumbnail_top_margin_));
thumbnail_layer_->SetUV(gfx::PointF(top_left_x, top_left_y),
gfx::PointF(bottom_right_x, bottom_right_y));
}
ContextualSearchLayer::ContextualSearchLayer(
ui::ResourceManager* resource_manager)
: OverlayPanelLayer(resource_manager),
search_context_(cc::slim::UIResourceLayer::Create()),
icon_layer_(cc::slim::Layer::Create()),
search_provider_icon_layer_(cc::slim::UIResourceLayer::Create()),
thumbnail_layer_(cc::slim::UIResourceLayer::Create()),
quick_action_icon_layer_(cc::slim::UIResourceLayer::Create()),
search_promo_(cc::slim::UIResourceLayer::Create()),
search_promo_container_(cc::slim::SolidColorLayer::Create()),
related_searches_in_bar_(cc::slim::UIResourceLayer::Create()),
search_caption_(cc::slim::UIResourceLayer::Create()),
text_layer_(cc::slim::UIResourceLayer::Create()),
touch_highlight_layer_(cc::slim::SolidColorLayer::Create()) {
// Search Bar Text
search_context_->SetIsDrawable(true);
// Search Bar Caption
search_caption_->SetIsDrawable(true);
// Search Opt Out Promo
search_promo_container_->SetIsDrawable(true);
search_promo_container_->SetBackgroundColor(
SkColor4f::FromColor(kSearchBackgroundColor));
search_promo_->SetIsDrawable(true);
// Related Searches sections
related_searches_in_bar_->SetIsDrawable(true);
// Icon - holds thumbnail, search provider icon and/or quick action icon
icon_layer_->SetIsDrawable(true);
layer_->AddChild(icon_layer_);
// Search provider icon
search_provider_icon_layer_->SetIsDrawable(true);
// Thumbnail
thumbnail_layer_->SetIsDrawable(true);
// Quick action icon
quick_action_icon_layer_->SetIsDrawable(true);
// Content layer
text_layer_->SetIsDrawable(true);
// NOTE(mdjones): This can be called multiple times to add other text layers.
AddBarTextLayer(text_layer_);
text_layer_->AddChild(search_context_);
// Touch Highlight Layer
touch_highlight_layer_->SetIsDrawable(true);
touch_highlight_layer_->SetBackgroundColor(
SkColor4f::FromColor(kTouchHighlightColor));
}
ContextualSearchLayer::~ContextualSearchLayer() {
}
} // namespace android