// Copyright 2015 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/scene_layer/tab_strip_scene_layer.h"
#include "base/android/jni_android.h"
#include "base/feature_list.h"
#include "cc/resources/scoped_ui_resource.h"
#include "cc/slim/layer.h"
#include "cc/slim/solid_color_layer.h"
#include "cc/slim/ui_resource_layer.h"
#include "chrome/browser/android/compositor/decoration_title.h"
#include "chrome/browser/android/compositor/layer/tab_handle_layer.h"
#include "chrome/browser/android/compositor/layer_title_cache.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "ui/android/resources/nine_patch_resource.h"
#include "ui/android/resources/resource_manager_impl.h"
#include "ui/base/l10n/l10n_util_android.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/transform.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/chrome_jni_headers/TabStripSceneLayer_jni.h"
using base::android::JavaParamRef;
using base::android::JavaRef;
namespace android {
TabStripSceneLayer::TabStripSceneLayer(JNIEnv* env,
const JavaRef<jobject>& jobj)
: SceneLayer(env, jobj),
tab_strip_layer_(cc::slim::SolidColorLayer::Create()),
scrollable_strip_layer_(cc::slim::Layer::Create()),
group_indicator_layer_(cc::slim::Layer::Create()),
new_tab_button_(cc::slim::UIResourceLayer::Create()),
new_tab_button_background_(cc::slim::UIResourceLayer::Create()),
left_fade_(cc::slim::UIResourceLayer::Create()),
right_fade_(cc::slim::UIResourceLayer::Create()),
left_padding_layer_(cc::slim::SolidColorLayer::Create()),
right_padding_layer_(cc::slim::SolidColorLayer::Create()),
model_selector_button_(cc::slim::UIResourceLayer::Create()),
model_selector_button_background_(cc::slim::UIResourceLayer::Create()),
scrim_layer_(cc::slim::SolidColorLayer::Create()),
content_tree_(nullptr) {
new_tab_button_->SetIsDrawable(true);
new_tab_button_background_->SetIsDrawable(true);
model_selector_button_->SetIsDrawable(true);
model_selector_button_background_->SetIsDrawable(true);
left_fade_->SetIsDrawable(true);
right_fade_->SetIsDrawable(true);
scrim_layer_->SetIsDrawable(true);
left_padding_layer_->SetIsDrawable(true);
right_padding_layer_->SetIsDrawable(true);
// When the ScrollingStripStacker is used, the new tab button and tabs scroll,
// while the incognito button and left/right fade stay fixed. Put the new tab
// button and tabs in a separate layer placed visually below the others. Put
// tab group indicators in a separate layer placed visually below the tabs.
group_indicator_layer_->SetIsDrawable(true);
scrollable_strip_layer_->SetIsDrawable(true);
tab_strip_layer_->SetIsDrawable(true);
tab_strip_layer_->AddChild(group_indicator_layer_);
tab_strip_layer_->AddChild(scrollable_strip_layer_);
tab_strip_layer_->AddChild(left_fade_);
tab_strip_layer_->AddChild(right_fade_);
tab_strip_layer_->AddChild(left_padding_layer_);
tab_strip_layer_->AddChild(right_padding_layer_);
tab_strip_layer_->AddChild(model_selector_button_background_);
tab_strip_layer_->AddChild(new_tab_button_background_);
tab_strip_layer_->AddChild(model_selector_button_);
tab_strip_layer_->AddChild(new_tab_button_);
tab_strip_layer_->AddChild(scrim_layer_);
layer()->AddChild(tab_strip_layer_);
}
TabStripSceneLayer::~TabStripSceneLayer() = default;
void TabStripSceneLayer::SetContentTree(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
const JavaParamRef<jobject>& jcontent_tree) {
SceneLayer* content_tree = FromJavaObject(env, jcontent_tree);
if (content_tree_ &&
(!content_tree_->layer()->parent() ||
content_tree_->layer()->parent()->id() != layer()->id()))
content_tree_ = nullptr;
if (content_tree != content_tree_) {
if (content_tree_)
content_tree_->layer()->RemoveFromParent();
content_tree_ = content_tree;
if (content_tree) {
layer()->InsertChild(content_tree->layer(), 0);
content_tree->layer()->SetPosition(
gfx::PointF(0, -layer()->position().y()));
}
}
}
void TabStripSceneLayer::BeginBuildingFrame(JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jboolean visible) {
write_index_ = 0;
group_write_index_ = 0;
tab_strip_layer_->SetHideLayerAndSubtree(!visible);
}
void TabStripSceneLayer::FinishBuildingFrame(
JNIEnv* env,
const JavaParamRef<jobject>& jobj) {
if (tab_strip_layer_->hide_layer_and_subtree())
return;
for (unsigned i = write_index_; i < tab_handle_layers_.size(); ++i) {
tab_handle_layers_[i]->layer()->RemoveFromParent();
}
for (unsigned i = group_write_index_; i < group_title_layers_.size(); ++i) {
group_title_layers_[i]->RemoveFromParent();
}
for (unsigned i = group_write_index_; i < group_bottom_layers_.size(); ++i) {
group_bottom_layers_[i]->RemoveFromParent();
}
tab_handle_layers_.erase(tab_handle_layers_.begin() + write_index_,
tab_handle_layers_.end());
group_title_layers_.erase(group_title_layers_.begin() + group_write_index_,
group_title_layers_.end());
group_bottom_layers_.erase(group_bottom_layers_.begin() + group_write_index_,
group_bottom_layers_.end());
}
void TabStripSceneLayer::UpdateOffsetTag(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobj,
const JavaParamRef<jobject>& joffset_tag) {
viz::OffsetTag tag = cc::android::FromJavaOffsetTag(env, joffset_tag);
layer()->SetOffsetTag(tag);
}
void TabStripSceneLayer::UpdateTabStripLayer(JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jint width,
jint height,
jfloat y_offset,
jint background_color,
jint scrim_color,
jfloat scrim_opacity,
jfloat left_padding,
jfloat right_padding,
jfloat top_padding) {
gfx::RectF content(0, y_offset, width, height);
layer()->SetPosition(gfx::PointF(0, y_offset));
tab_strip_layer_->SetBounds(gfx::Size(width, height));
tab_strip_layer_->SetBackgroundColor(SkColor4f::FromColor(background_color));
float scrollable_strip_height = height - top_padding;
scrollable_strip_layer_->SetBounds(gfx::Size(width, scrollable_strip_height));
scrollable_strip_layer_->SetPosition(gfx::PointF(0, top_padding));
group_indicator_layer_->SetBounds(gfx::Size(width, scrollable_strip_height));
group_indicator_layer_->SetPosition(gfx::PointF(0, top_padding));
// Content tree should not be affected by tab strip scene layer visibility.
if (content_tree_)
content_tree_->layer()->SetPosition(gfx::PointF(0, -y_offset));
// Update left and right padding layers as required.
if (left_padding == 0) {
left_padding_layer_->SetHideLayerAndSubtree(true);
} else {
left_padding_layer_->SetHideLayerAndSubtree(false);
left_padding_layer_->SetBounds(gfx::Size(left_padding, height));
left_padding_layer_->SetBackgroundColor(
SkColor4f::FromColor(background_color));
}
if (right_padding == 0) {
right_padding_layer_->SetHideLayerAndSubtree(true);
} else {
right_padding_layer_->SetHideLayerAndSubtree(false);
right_padding_layer_->SetBounds(gfx::Size(right_padding, height));
right_padding_layer_->SetPosition(gfx::PointF(width - right_padding, 0));
right_padding_layer_->SetBackgroundColor(
SkColor4f::FromColor(background_color));
}
// Hide scrim layer if it's not visible.
if (scrim_opacity == 0.f) {
scrim_layer_->SetHideLayerAndSubtree(true);
return;
}
// Set opacity and color
scrim_layer_->SetOpacity(scrim_opacity);
scrim_layer_->SetBounds(tab_strip_layer_->bounds());
scrim_layer_->SetBackgroundColor(SkColor4f::FromColor(scrim_color));
// Ensure layer is visible.
scrim_layer_->SetHideLayerAndSubtree(false);
}
void TabStripSceneLayer::UpdateNewTabButton(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jint resource_id,
jint bg_resource_id,
jfloat x,
jfloat y,
jfloat top_padding,
jfloat touch_target_offset,
jboolean visible,
jboolean should_apply_hover_highlight,
jint tint,
jint background_tint,
jfloat button_alpha,
const JavaParamRef<jobject>& jresource_manager) {
ui::ResourceManager* resource_manager =
ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
ui::Resource* button_resource =
resource_manager->GetStaticResourceWithTint(resource_id, tint);
ui::Resource* background_resource =
resource_manager->GetStaticResourceWithTint(bg_resource_id,
background_tint, true);
x += touch_target_offset;
y += top_padding;
UpdateCompositorButton(new_tab_button_, new_tab_button_background_,
button_resource, background_resource, x, y, visible,
should_apply_hover_highlight, button_alpha);
}
void TabStripSceneLayer::UpdateModelSelectorButton(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jint resource_id,
jint bg_resource_id,
jfloat x,
jfloat y,
jboolean visible,
jboolean should_apply_hover_highlight,
jint tint,
jint background_tint,
jfloat button_alpha,
const JavaParamRef<jobject>& jresource_manager) {
ui::ResourceManager* resource_manager =
ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
ui::Resource* button_resource =
resource_manager->GetStaticResourceWithTint(resource_id, tint);
ui::Resource* background_resource =
resource_manager->GetStaticResourceWithTint(bg_resource_id,
background_tint, true);
UpdateCompositorButton(model_selector_button_,
model_selector_button_background_, button_resource,
background_resource, x, y, visible,
should_apply_hover_highlight, button_alpha);
}
void TabStripSceneLayer::UpdateCompositorButton(
scoped_refptr<cc::slim::UIResourceLayer> button,
scoped_refptr<cc::slim::UIResourceLayer> background,
ui::Resource* button_resource,
ui::Resource* background_resource,
float x,
float y,
bool visible,
bool should_apply_hover_highlight,
float button_alpha) {
button->SetUIResourceId(button_resource->ui_resource()->id());
button->SetBounds(button_resource->size());
button->SetHideLayerAndSubtree(!visible);
button->SetOpacity(button_alpha);
gfx::Size background_size = background_resource->size();
gfx::Size button_size = button_resource->size();
float x_offset = (background_size.width() - button_size.width()) / 2;
float y_offset = (background_size.height() - button_size.height()) / 2;
button->SetPosition(gfx::PointF(x + x_offset, y + y_offset));
if (!should_apply_hover_highlight) {
background->SetHideLayerAndSubtree(true);
} else {
background->SetUIResourceId(background_resource->ui_resource()->id());
background->SetPosition(gfx::PointF(x, y));
background->SetBounds(background_resource->size());
background->SetHideLayerAndSubtree(!visible);
background->SetOpacity(button_alpha);
}
}
void TabStripSceneLayer::UpdateTabStripLeftFade(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jint resource_id,
jfloat opacity,
const JavaParamRef<jobject>& jresource_manager,
jint left_fade_color,
jfloat left_padding) {
// Hide layer if it's not visible.
if (opacity == 0.f) {
left_fade_->SetHideLayerAndSubtree(true);
return;
}
// Set UI resource.
ui::ResourceManager* resource_manager =
ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
ui::Resource* fade_resource = resource_manager->GetStaticResourceWithTint(
resource_id, left_fade_color);
left_fade_->SetUIResourceId(fade_resource->ui_resource()->id());
// The same resource is used for both left and right fade, so the
// resource must be mirrored for the left fade.
gfx::Transform fade_transform = gfx::Transform::MakeScale(-1.0f, 1.0f);
left_fade_->SetTransform(fade_transform);
// Set opacity.
left_fade_->SetOpacity(opacity);
// Set bounds. Use the parent layer height so the 1px fade resource is
// stretched vertically.
float height = tab_strip_layer_->bounds().height();
left_fade_->SetBounds(gfx::Size(fade_resource->size().width(), height));
// Set position. The rotation set above requires the layer to be offset
// by its width in order to display on the left edge.
left_fade_->SetPosition(
gfx::PointF(fade_resource->size().width() + left_padding, 0));
// Ensure layer is visible.
left_fade_->SetHideLayerAndSubtree(false);
}
void TabStripSceneLayer::UpdateTabStripRightFade(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jint resource_id,
jfloat opacity,
const JavaParamRef<jobject>& jresource_manager,
jint right_fade_color,
jfloat right_padding) {
// Hide layer if it's not visible.
if (opacity == 0.f) {
right_fade_->SetHideLayerAndSubtree(true);
return;
}
// Set UI resource.
ui::ResourceManager* resource_manager =
ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
ui::Resource* fade_resource = resource_manager->GetStaticResourceWithTint(
resource_id, right_fade_color);
right_fade_->SetUIResourceId(fade_resource->ui_resource()->id());
// Set opacity.
right_fade_->SetOpacity(opacity);
// Set bounds. Use the parent layer height so the 1px fade resource is
// stretched vertically.
float height = tab_strip_layer_->bounds().height();
right_fade_->SetBounds(gfx::Size(fade_resource->size().width(), height));
// Set position. The right fade is positioned at the end of the tab strip.
float x = scrollable_strip_layer_->bounds().width() -
fade_resource->size().width() - right_padding;
right_fade_->SetPosition(gfx::PointF(x, 0));
// Ensure layer is visible.
right_fade_->SetHideLayerAndSubtree(false);
}
void TabStripSceneLayer::PutStripTabLayer(
JNIEnv* env,
const JavaParamRef<jobject>& jobj,
jint id,
jint close_resource_id,
jint close_hover_bg_resource_id,
jint divider_resource_id,
jint handle_resource_id,
jint handle_outline_resource_id,
jint close_tint,
jint close_hover_bg_tint,
jint divider_tint,
jint handle_tint,
jint handle_outline_tint,
jboolean foreground,
jboolean shouldShowTabOutline,
jboolean close_pressed,
jfloat toolbar_width,
jfloat x,
jfloat y,
jfloat width,
jfloat height,
jfloat content_offset_y,
jfloat divider_offset_x,
jfloat bottom_margin,
jfloat top_margin,
jfloat close_button_padding,
jfloat close_button_alpha,
jboolean is_start_divider_visible,
jboolean is_end_divider_visible,
jboolean is_loading,
jfloat spinner_rotation,
jfloat opacity,
const JavaParamRef<jobject>& jlayer_title_cache,
const JavaParamRef<jobject>& jresource_manager) {
LayerTitleCache* layer_title_cache =
LayerTitleCache::FromJavaObject(jlayer_title_cache);
ui::ResourceManager* resource_manager =
ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
scoped_refptr<TabHandleLayer> layer = GetNextLayer(layer_title_cache);
ui::NinePatchResource* tab_handle_resource =
ui::NinePatchResource::From(resource_manager->GetStaticResourceWithTint(
handle_resource_id, handle_tint, true));
ui::NinePatchResource* tab_handle_outline_resource =
ui::NinePatchResource::From(resource_manager->GetStaticResourceWithTint(
handle_outline_resource_id, handle_outline_tint));
ui::Resource* close_button_resource =
resource_manager->GetStaticResourceWithTint(close_resource_id,
close_tint);
ui::Resource* close_button_hover_resource =
resource_manager->GetStaticResourceWithTint(close_hover_bg_resource_id,
close_hover_bg_tint, true);
ui::Resource* divider_resource = resource_manager->GetStaticResourceWithTint(
divider_resource_id, divider_tint, true);
layer->SetProperties(
id, close_button_resource, close_button_hover_resource, divider_resource,
tab_handle_resource, tab_handle_outline_resource, foreground,
shouldShowTabOutline, close_pressed, toolbar_width, x, y, width, height,
content_offset_y, divider_offset_x, bottom_margin, top_margin,
close_button_padding, close_button_alpha, is_start_divider_visible,
is_end_divider_visible, is_loading, spinner_rotation, opacity);
}
void TabStripSceneLayer::PutGroupIndicatorLayer(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobj,
jboolean incognito,
jint id,
jint tint,
jfloat x,
jfloat y,
jfloat width,
jfloat height,
jfloat title_text_padding,
jfloat corner_radius,
jfloat bottom_indicator_width,
jfloat bottom_indicator_height,
const JavaParamRef<jobject>& jlayer_title_cache) {
LayerTitleCache* layer_title_cache =
LayerTitleCache::FromJavaObject(jlayer_title_cache);
// Reuse existing layer if it exists.
scoped_refptr<cc::slim::SolidColorLayer> title_indicator_layer =
GetNextGroupTitleLayer();
scoped_refptr<cc::slim::SolidColorLayer> bottom_indicator_layer =
GetNextGroupBottomLayer();
group_write_index_++;
// Set title indicator container properties.
title_indicator_layer->SetPosition(gfx::PointF(x, y));
title_indicator_layer->SetBounds(gfx::Size(width, height));
title_indicator_layer->SetRoundedCorner(gfx::RoundedCornersF(
corner_radius, corner_radius, corner_radius, corner_radius));
title_indicator_layer->SetBackgroundColor(SkColor4f::FromColor(tint));
// Set title.
DecorationTitle* title_layer =
layer_title_cache->GetGroupTitleLayer(id, incognito);
if (title_layer) {
// Ensure we're using the updated title bitmap prior to accessing/updating
// any properties.
title_layer->SetUIResourceIds();
float title_y = (height - title_layer->size().height()) / 2.f;
title_layer->setOpacity(1.0f);
title_layer->setBounds(gfx::Size(width - (title_text_padding * 2), height));
title_layer->layer()->SetPosition(gfx::PointF(title_text_padding, title_y));
if (title_indicator_layer->children().size() == 0) {
title_indicator_layer->AddChild(title_layer->layer());
} else {
title_indicator_layer->ReplaceChild(
title_indicator_layer->children()[0].get(), title_layer->layer());
}
} else {
title_indicator_layer->RemoveAllChildren();
}
// Set bottom indicator properties.
float bottom_indicator_x = x;
float bottom_indicator_y =
group_indicator_layer_->bounds().height() - bottom_indicator_height;
if (l10n_util::IsLayoutRtl()) {
bottom_indicator_x -= (bottom_indicator_width - width);
}
// Use ceiling value to prevent height float from getting truncated, otherwise
// it could result in bottom indicator looks thinner than intended in certain
// screen densities.
bottom_indicator_layer->SetBounds(
gfx::Size(bottom_indicator_width, ceil(bottom_indicator_height)));
// Use the floor value to position vertically to prevent bottom indicator from
// getting cut off in certain screen densities.
bottom_indicator_layer->SetPosition(
gfx::PointF(bottom_indicator_x, floor(bottom_indicator_y)));
bottom_indicator_layer->SetBackgroundColor(SkColor4f::FromColor(tint));
}
scoped_refptr<TabHandleLayer> TabStripSceneLayer::GetNextLayer(
LayerTitleCache* layer_title_cache) {
if (write_index_ < tab_handle_layers_.size())
return tab_handle_layers_[write_index_++];
scoped_refptr<TabHandleLayer> layer_tree =
TabHandleLayer::Create(layer_title_cache);
tab_handle_layers_.push_back(layer_tree);
scrollable_strip_layer_->AddChild(layer_tree->layer());
write_index_++;
return layer_tree;
}
scoped_refptr<cc::slim::SolidColorLayer>
TabStripSceneLayer::GetNextGroupTitleLayer() {
if (group_write_index_ < group_title_layers_.size()) {
return group_title_layers_[group_write_index_];
}
scoped_refptr<cc::slim::SolidColorLayer> layer =
cc::slim::SolidColorLayer::Create();
layer->SetIsDrawable(true);
group_title_layers_.push_back(layer);
group_indicator_layer_->AddChild(layer);
return layer;
}
scoped_refptr<cc::slim::SolidColorLayer>
TabStripSceneLayer::GetNextGroupBottomLayer() {
if (group_write_index_ < group_bottom_layers_.size()) {
return group_bottom_layers_[group_write_index_];
}
scoped_refptr<cc::slim::SolidColorLayer> layer =
cc::slim::SolidColorLayer::Create();
layer->SetIsDrawable(true);
group_bottom_layers_.push_back(layer);
group_indicator_layer_->AddChild(layer);
return layer;
}
bool TabStripSceneLayer::ShouldShowBackground() {
if (content_tree_)
return content_tree_->ShouldShowBackground();
return SceneLayer::ShouldShowBackground();
}
SkColor TabStripSceneLayer::GetBackgroundColor() {
if (content_tree_)
return content_tree_->GetBackgroundColor();
return SceneLayer::GetBackgroundColor();
}
static jlong JNI_TabStripSceneLayer_Init(JNIEnv* env,
const JavaParamRef<jobject>& jobj) {
// This will automatically bind to the Java object and pass ownership there.
TabStripSceneLayer* scene_layer = new TabStripSceneLayer(env, jobj);
return reinterpret_cast<intptr_t>(scene_layer);
}
} // namespace android