#include "ash/style/tab_slider.h"
#include <cstddef>
#include "ash/style/style_util.h"
#include "ash/style/tab_slider_button.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/view_class_properties.h"
namespace ash {
namespace {
constexpr ui::ColorId kSliderBackgroundColorId =
cros_tokens::kCrosSysSystemOnBase;
constexpr ui::ColorId kSelectorBackgroundColorId =
cros_tokens::kCrosSysSystemPrimaryContainer;
constexpr base::TimeDelta kSelectorAnimationDuration = base::Milliseconds(150);
}
class TabSlider::SelectorView : public views::View {
METADATA_HEADER(SelectorView, views::View)
public:
explicit SelectorView(bool has_animation) : has_animation_(has_animation) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
SetBackground(StyleUtil::CreateThemedFullyRoundedRectBackground(
kSelectorBackgroundColorId));
}
SelectorView(const SelectorView&) = delete;
SelectorView& operator=(const SelectorView&) = delete;
~SelectorView() override = default;
void MoveToSelectedButton(TabSliderButton* button) {
DCHECK(button);
DCHECK(button->selected());
if (button_ == button) {
return;
}
TabSliderButton* previous_button = button_;
button_ = button;
SetBoundsRect(button_->bounds());
if (!previous_button || !has_animation_) {
return;
}
auto* view_layer = layer();
gfx::Transform reverse_transform = gfx::TransformBetweenRects(
gfx::RectF(button_->GetMirroredBounds()),
gfx::RectF(previous_button->GetMirroredBounds()));
view_layer->SetTransform(reverse_transform);
ui::ScopedLayerAnimationSettings settings(view_layer->GetAnimator());
settings.SetTransitionDuration(kSelectorAnimationDuration);
view_layer->SetTransform(gfx::Transform());
}
private:
const bool has_animation_;
raw_ptr<TabSliderButton> button_ = nullptr;
};
BEGIN_METADATA(TabSlider, SelectorView)
END_METADATA
TabSlider::TabSlider(size_t max_tab_num, const InitParams& params)
: max_tab_num_(max_tab_num),
params_(params),
selector_view_(AddChildView(
std::make_unique<SelectorView>(params.has_selector_animation))) {
if (params_.has_background) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
SetBackground(StyleUtil::CreateThemedFullyRoundedRectBackground(
kSliderBackgroundColorId));
}
Init();
selector_view_->SetProperty(views::kViewIgnoredByLayoutKey, true);
enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
&TabSlider::OnEnabledStateChanged, base::Unretained(this)));
}
TabSlider::~TabSlider() = default;
views::View* TabSlider::GetSelectorView() {
return selector_view_;
}
TabSliderButton* TabSlider::GetButtonAtIndex(size_t index) {
CHECK(index < buttons_.size());
return buttons_[index];
}
void TabSlider::OnButtonSelected(TabSliderButton* button) {
DCHECK(button);
DCHECK(base::Contains(buttons_, button));
DCHECK(button->selected());
bool has_focus = false;
for (ash::TabSliderButton* b : buttons_) {
b->SetSelected(b == button);
has_focus |= b->HasFocus();
}
selector_view_->MoveToSelectedButton(button);
if (has_focus) {
GetFocusManager()->SetFocusedView(button);
}
}
void TabSlider::Layout(PassKey) {
LayoutSuperclass<views::View>(this);
auto it =
std::find_if(buttons_.begin(), buttons_.end(),
[](TabSliderButton* button) { return button->selected(); });
if (it == buttons_.end()) {
return;
}
selector_view_->SetBoundsRect((*it)->bounds());
}
void TabSlider::Init() {
const int internal_border_padding = params_.internal_border_padding;
AddPaddingRow(views::TableLayout::kFixedSize, internal_border_padding);
AddRows(1, views::TableLayout::kFixedSize);
AddPaddingRow(views::TableLayout::kFixedSize, internal_border_padding);
AddPaddingColumn(views::TableLayout::kFixedSize, internal_border_padding);
std::vector<size_t> columns_containing_buttons;
for (size_t i = 0; i < max_tab_num_; ++i) {
AddColumn(views::LayoutAlignment::kStretch, views::LayoutAlignment::kCenter,
1.0f, views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
columns_containing_buttons.push_back(2 * i + 1);
if (i != max_tab_num_ - 1) {
AddPaddingColumn(views::TableLayout::kFixedSize,
params_.between_buttons_spacing);
}
}
AddPaddingColumn(views::TableLayout::kFixedSize, internal_border_padding);
if (params_.distribute_space_evenly) {
LinkColumnSizes(columns_containing_buttons);
}
}
void TabSlider::AddButtonInternal(TabSliderButton* button) {
CHECK(button);
CHECK_LT(buttons_.size(), max_tab_num_)
<< "Number of buttons reaches the limit";
AddChildViewRaw(button);
buttons_.emplace_back(button);
button->AddedToSlider(this);
}
void TabSlider::OnEnabledStateChanged() {
const bool enabled = GetEnabled();
for (ash::TabSliderButton* b : buttons_) {
b->SetEnabled(enabled);
}
selector_view_->SetEnabled(enabled);
SchedulePaint();
}
BEGIN_METADATA(TabSlider)
END_METADATA
}