910e62b5创建于 1月15日历史提交
// 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/layer/tab_handle_layer.h"

#include <math.h>

#include <vector>

#include "cc/slim/layer.h"
#include "cc/slim/nine_patch_layer.h"
#include "chrome/browser/android/compositor/decoration_tab_title.h"
#include "chrome/browser/android/compositor/layer_title_cache.h"
#include "ui/android/resources/nine_patch_resource.h"
#include "ui/base/l10n/l10n_util_android.h"

namespace android {

// static
scoped_refptr<TabHandleLayer> TabHandleLayer::Create(
    LayerTitleCache* layer_title_cache) {
  return base::WrapRefCounted(new TabHandleLayer(layer_title_cache));
}

void TabHandleLayer::SetProperties(
    int id,
    ui::Resource* close_button_resource,
    ui::Resource* close_button_background_resource,
    bool is_close_keyboard_focused,
    ui::Resource* close_button_keyboard_focus_ring_resource,
    ui::Resource* divider_resource,
    ui::NinePatchResource* tab_handle_resource,
    ui::NinePatchResource* tab_handle_outline_resource,
    bool foreground,
    bool is_pinned,
    bool shouldShowTabOutline,
    bool close_pressed,
    bool should_hide_favicon,
    bool should_show_media_indicator,
    ui::Resource* media_indicator_resource,
    float media_indicator_width,
    float toolbar_width,
    float x,
    float y,
    float width,
    float height,
    float content_offset_y,
    float divider_offset_x,
    float bottom_margin,
    float top_margin,
    float close_button_padding,
    float close_button_alpha,
    bool is_start_divider_visible,
    bool is_end_divider_visible,
    bool is_loading,
    float spinner_rotation,
    float opacity,
    bool is_keyboard_focused,
    ui::NinePatchResource* keyboard_focus_ring_drawable,
    int keyboard_focus_ring_offset,
    int stroke_width,
    float folio_foot_length,
    float width_to_hide_tab_title) {
  if (foreground != foreground_ || opacity != opacity_ ||
      is_pinned != is_pinned_) {
    foreground_ = foreground;
    opacity_ = opacity;
    is_pinned_ = is_pinned;
  }

  y += top_margin;

  bool is_rtl = l10n_util::IsLayoutRtl();

  float margin_width = tab_handle_resource->size().width() -
                       tab_handle_resource->aperture().width();
  float margin_height = tab_handle_resource->size().height() -
                        tab_handle_resource->aperture().height();

  // In the inset case, the |decoration_tab_| nine-patch layer should not have a
  // margin that is greater than the content size of the layer.  This case can
  // happen at initialization.  The sizes used below are just temp values until
  // the real content sizes arrive.
  if (margin_width >= width) {
    // Shift the left edge over by the adjusted amount for more
    // aesthetic animations.
    x = x - (margin_width - width);
    width = margin_width;
  }
  if (margin_height >= height) {
    y = y - (margin_height - height);
    height = margin_height;
  }
  height -= top_margin;
  height = ceil(height);
  gfx::Size tab_bounds(width, height - bottom_margin);

  layer_->SetPosition(gfx::PointF(x, y));

  DecorationTabTitle* title_layer = nullptr;
  // Only pull if tab id is valid.
  if (layer_title_cache_ && id != -1) {
    title_layer = layer_title_cache_->GetTitleLayer(id);
  }

  if (title_layer) {
    unsigned expected_children = 5;
    title_layer_ = title_layer->layer();
    if (tab_->children().size() < expected_children) {
      tab_->AddChild(title_layer_);
    } else if (tab_->children()[expected_children - 1]->id() !=
               title_layer_->id()) {
      tab_->ReplaceChild((tab_->children()[expected_children - 1]).get(),
                         title_layer_);
    }
    title_layer->SetUIResourceIds();
  } else if (title_layer_.get()) {
    title_layer_->RemoveFromParent();
    title_layer_ = nullptr;
  }

  decoration_tab_->SetUIResourceId(tab_handle_resource->ui_resource()->id());
  decoration_tab_->SetAperture(tab_handle_resource->aperture());
  decoration_tab_->SetFillCenter(true);
  decoration_tab_->SetBounds(tab_bounds);
  decoration_tab_->SetBorder(
      tab_handle_resource->Border(decoration_tab_->bounds()));
  decoration_tab_->SetOpacity(opacity_);

  tab_outline_->SetUIResourceId(
      tab_handle_outline_resource->ui_resource()->id());
  tab_outline_->SetAperture(tab_handle_outline_resource->aperture());
  tab_outline_->SetFillCenter(true);
  tab_outline_->SetBounds(tab_bounds);
  tab_outline_->SetBorder(
      tab_handle_outline_resource->Border(tab_outline_->bounds()));

  // Display the tab outline only for the currently selected tab in group when
  // TabGroupIndicator is enabled.
  if (shouldShowTabOutline) {
    tab_outline_->SetIsDrawable(true);
  } else {
    tab_outline_->SetIsDrawable(false);
  }

  close_button_->SetUIResourceId(close_button_resource->ui_resource()->id());
  close_button_->SetBounds(close_button_resource->size());

  close_button_hover_highlight_->SetUIResourceId(
      close_button_background_resource->ui_resource()->id());
  close_button_hover_highlight_->SetBounds(
      close_button_background_resource->size());

  const float padding_right = tab_handle_resource->size().width() -
                              tab_handle_resource->padding().right();
  const float padding_left = tab_handle_resource->padding().x();

  float close_width = close_button_->bounds().width() - close_button_padding;

  // If close button is not shown, fill
  // the remaining space with the title text
  if (close_button_alpha == 0.f) {
    close_width = 0.f;
  }

  int divider_y = content_offset_y;
  int divider_width = divider_resource->size().width();

  if (!is_start_divider_visible) {
    start_divider_->SetIsDrawable(false);
  } else {
    start_divider_->SetIsDrawable(true);
    start_divider_->SetUIResourceId(divider_resource->ui_resource()->id());
    start_divider_->SetBounds(divider_resource->size());
    int divider_x =
        is_rtl ? width - divider_width - divider_offset_x : divider_offset_x;
    start_divider_->SetPosition(gfx::PointF(divider_x, divider_y));
  }

  if (!is_end_divider_visible) {
    end_divider_->SetIsDrawable(false);
  } else {
    end_divider_->SetIsDrawable(true);
    end_divider_->SetUIResourceId(divider_resource->ui_resource()->id());
    end_divider_->SetBounds(divider_resource->size());
    int divider_x =
        is_rtl ? divider_offset_x : width - divider_width - divider_offset_x;
    end_divider_->SetPosition(gfx::PointF(divider_x, divider_y));
  }

  float media_indicator_size = 0.f;
  if (should_show_media_indicator && media_indicator_resource) {
    media_indicator_size = media_indicator_width;
    media_indicator_layer_->SetIsDrawable(true);
    media_indicator_layer_->SetUIResourceId(
        media_indicator_resource->ui_resource()->id());
    media_indicator_layer_->SetBounds(
        gfx::Size(media_indicator_size, media_indicator_size));
    media_indicator_layer_->SetOpacity(1.0f);
  } else {
    media_indicator_layer_->SetIsDrawable(false);
  }

  if (title_layer) {
    int title_y;
    float title_y_offset_mid = (tab_handle_resource->padding().y() + height -
                                title_layer->size().height()) /
                               2;
    // 8dp top padding for folio.
    title_y = std::min(content_offset_y, title_y_offset_mid);

    // Hide tab title text when it reached threshold.
    title_layer->SetShouldHideTitleText(width <= width_to_hide_tab_title);

    // Hide tab favicon if necessary.
    title_layer->SetShouldHideIcon(should_hide_favicon);

    int title_x = is_rtl ? padding_left + close_width + media_indicator_size
                         : padding_left;
    int title_width = width - padding_left - padding_right - close_width -
                      media_indicator_size;
    title_layer->setBounds(gfx::Size(title_width, height));
    title_layer->layer()->SetPosition(gfx::PointF(title_x, title_y));
    if (is_loading) {
      title_layer->SetIsLoading(true);
      title_layer->SetSpinnerRotation(spinner_rotation);
    } else {
      title_layer->SetIsLoading(false);
    }
  }

  // Pull this out of the close-button-related block so it can be used for
  // keyboard focus ring calculation as well.
  int close_y;

  // Close button image is larger than divider image, so close button will
  // appear slightly lower even the close_y are set in the same value as
  // divider_y. Thus need this offset to account for the effect of image
  // size difference has on close_y.
  int close_y_offset_tsr = std::max(0, (close_button_resource->size().height() -
                                        divider_resource->size().height()) /
                                           2);
  close_y = content_offset_y - std::abs(close_y_offset_tsr);

  // The keyboard focus ring should not be drawn by default. Make sure this
  // happens whether the parent tab is selected or unselected.
  close_keyboard_focus_ring_->SetIsDrawable(false);
  if (close_button_alpha == 0.f) {
    close_button_->SetIsDrawable(false);
    close_button_hover_highlight_->SetIsDrawable(false);
  } else {
    close_button_->SetIsDrawable(true);
    close_button_hover_highlight_->SetIsDrawable(true);

    int close_x = is_rtl ? padding_left - close_button_padding
                         : width - padding_right - close_width;

    float background_left_offset =
        (close_button_background_resource->size().width() -
         close_button_resource->size().width()) /
        2;
    float background_top_offset =
        (close_button_background_resource->size().height() -
         close_button_resource->size().height()) /
        2;
    close_button_->SetPosition(
        gfx::PointF(background_left_offset, background_top_offset));
    close_button_->SetOpacity(close_button_alpha);
    close_button_hover_highlight_->SetPosition(gfx::PointF(close_x, close_y));
    if (is_close_keyboard_focused) {
      close_keyboard_focus_ring_->SetIsDrawable(true);
      close_keyboard_focus_ring_->SetUIResourceId(
          close_button_keyboard_focus_ring_resource->ui_resource()->id());
      // We need to make sure that the keyboard focus ring's position is
      // int-aligned. That is, we want the final position of the focus ring to
      // be (close_x + std::round(x), close_y + std::round(y)). Because
      // close_keyboard_focus_ring is a child of layer, the final position of
      // the ring will be whatever coordinates we use in SetPosition plus
      // (x, y). Therefore we just use the coordinates we want,
      // (close_x + std::round(x), close_y + std::round(y)), and subtract
      // (x, y).
      close_keyboard_focus_ring_->SetPosition(gfx::PointF(
          close_x + std::round(x) - x, close_y + std::round(y) - y));
      close_keyboard_focus_ring_->SetBounds(gfx::Size(
          close_button_keyboard_focus_ring_resource->size().width(),
          close_button_keyboard_focus_ring_resource->size().height()));
    }
  }

  if (media_indicator_size > 0) {
    float right_aligned_icon_x = is_rtl ? padding_left - close_button_padding
                                        : width - padding_right - close_width;
    if (close_button_alpha == 0.f) {
      right_aligned_icon_x = is_rtl ? padding_left : width - padding_right;
    }

    float media_x = is_rtl ? right_aligned_icon_x + close_width
                           : right_aligned_icon_x - media_indicator_size;
    float media_y =
        close_y + std::round((close_button_resource->size().height() -
                              media_indicator_size) /
                             2);
    media_indicator_layer_->SetPosition(gfx::PointF(media_x, media_y));
  }

  if (is_keyboard_focused) {
    keyboard_focus_ring_->SetIsDrawable(true);
    keyboard_focus_ring_->SetUIResourceId(
        keyboard_focus_ring_drawable->ui_resource()->id());
    keyboard_focus_ring_->SetAperture(keyboard_focus_ring_drawable->aperture());

    float close_button_center_y =
        close_y + close_button_resource->size().height() / 2;
    if (shouldShowTabOutline) {
      float keyboard_focus_ring_y = keyboard_focus_ring_offset + stroke_width;
      keyboard_focus_ring_->SetPosition(
          gfx::PointF(folio_foot_length + keyboard_focus_ring_offset,
                      keyboard_focus_ring_y));
      keyboard_focus_ring_->SetBounds(gfx::Size(
          width - folio_foot_length * 2 - keyboard_focus_ring_offset * 2,
          (close_button_center_y - keyboard_focus_ring_y) * 2));
    } else {
      float keyboard_focus_ring_y = 0;
      keyboard_focus_ring_->SetPosition(
          gfx::PointF(folio_foot_length, keyboard_focus_ring_y));
      keyboard_focus_ring_->SetBounds(
          gfx::Size(width - folio_foot_length * 2,
                    (close_button_center_y - keyboard_focus_ring_y) * 2));
    }

    keyboard_focus_ring_->SetFillCenter(true);
    keyboard_focus_ring_->SetBorder(
        keyboard_focus_ring_drawable->Border(keyboard_focus_ring_->bounds()));
  } else {
    // Clean up the keyboard focus ring if it was previously showing but
    // shouldn't be showing now.
    keyboard_focus_ring_->SetIsDrawable(false);
  }
}

bool TabHandleLayer::foreground() {
  return foreground_;
}

bool TabHandleLayer::is_pinned() {
  return is_pinned_;
}

scoped_refptr<cc::slim::Layer> TabHandleLayer::layer() {
  return layer_;
}

TabHandleLayer::TabHandleLayer(LayerTitleCache* layer_title_cache)
    : layer_title_cache_(layer_title_cache),
      layer_(cc::slim::Layer::Create()),
      tab_(cc::slim::Layer::Create()),
      close_button_(cc::slim::UIResourceLayer::Create()),
      close_button_hover_highlight_(cc::slim::UIResourceLayer::Create()),
      close_keyboard_focus_ring_(cc::slim::UIResourceLayer::Create()),
      start_divider_(cc::slim::UIResourceLayer::Create()),
      end_divider_(cc::slim::UIResourceLayer::Create()),
      media_indicator_layer_(cc::slim::UIResourceLayer::Create()),
      decoration_tab_(cc::slim::NinePatchLayer::Create()),
      tab_outline_(cc::slim::NinePatchLayer::Create()),
      keyboard_focus_ring_(cc::slim::NinePatchLayer::Create()),
      foreground_(false) {
  decoration_tab_->SetIsDrawable(true);

  tab_->AddChild(decoration_tab_);
  tab_->AddChild(tab_outline_);
  tab_->AddChild(close_button_hover_highlight_);
  tab_->AddChild(media_indicator_layer_);
  close_button_hover_highlight_->AddChild(close_button_);

  decoration_tab_->SetPosition(gfx::PointF(0, 0));
  tab_outline_->SetPosition(gfx::PointF(0, 0));

  // The divider is added as a separate child so its opacity can be controlled
  // separately from the other tab items.
  layer_->AddChild(tab_);
  layer_->AddChild(start_divider_);
  layer_->AddChild(end_divider_);
  layer_->AddChild(close_keyboard_focus_ring_);
  layer_->AddChild(keyboard_focus_ring_);
}

TabHandleLayer::~TabHandleLayer() = default;

}  // namespace android