Skip to content

Commit

Permalink
tab-slider: Support Icon+Label button in TabSlider
Browse files Browse the repository at this point in the history
Create a new class IconLabelTabSliderButton.

Use the new Icon + Label buttons in the vc bubble.

In TabSlider:
The VC Controls spec requests that TabSlider gives extra space
to child buttons evenly, so support this.

Convert from BoxLayout to TableLayout to allow this, and
do some refactoring to further support TableLayout.

https://screenshot.googleplex.com/BfeyHcPS8HYCPBq.png

NOTE: Currently box BoxLayout and FlexLayout distribute
space in proportion to the views preferred size. This
won't work for VC controls because this means a button
with lots of text will get more space than a smaller
button when there is extra space.

The option to "justify" or spread space evenly has been
a requested feature for BoxLayout and FlexLayout for years,
but only TableLayout supports this.

Bug: b:254513459
Change-Id: I06ac8bf2fd78cf8016a0e5152e7ed2bd5a7a96b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4192351
Commit-Queue: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: Xiaodan Zhu <zxdan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1097553}
  • Loading branch information
Alex Newcomer authored and Chromium LUCI CQ committed Jan 26, 2023
1 parent 30a0b23 commit 109925d
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 96 deletions.
111 changes: 78 additions & 33 deletions ash/style/tab_slider.cc
Expand Up @@ -6,12 +6,14 @@

#include "ash/style/style_util.h"
#include "ash/style/tab_slider_button.h"
#include "base/functional/callback_helpers.h"
#include "base/time/time.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/layout/table_layout.h"
#include "ui/views/view_class_properties.h"

namespace ash {
Expand Down Expand Up @@ -88,9 +90,12 @@ class TabSlider::SelectorView : public views::View {
//------------------------------------------------------------------------------
// TabSlider:

TabSlider::TabSlider(bool has_background, bool has_selector_animation)
: selector_view_(AddChildView(
std::make_unique<SelectorView>(has_selector_animation))) {
TabSlider::TabSlider(bool has_background,
bool has_selector_animation,
bool distribute_space_evenly)
: selector_view_(
AddChildView(std::make_unique<SelectorView>(has_selector_animation))),
distribute_space_evenly_(distribute_space_evenly) {
// Add a fully rounded rect background if needed.
if (has_background) {
SetPaintToLayer();
Expand All @@ -99,25 +104,18 @@ TabSlider::TabSlider(bool has_background, bool has_selector_animation)
kSliderBackgroundColorId));
}

// Make the selector view ignored by layout, since the selector bounds should
// be in sync with the selected button's bounds.
selector_view_->SetProperty(views::kViewIgnoredByLayoutKey, true);

// Center the slider buttons within the container's box layout.
SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter);

enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
&TabSlider::OnEnabledStateChanged, base::Unretained(this)));
}

TabSlider::~TabSlider() = default;

void TabSlider::SetCustomLayout(const LayoutParams& layout_params) {
use_button_recommended_layout_ = false;

// Configure the layout with the custom layout parameters.
custom_layout_params_ = layout_params;
SetInsideBorderInsets(
gfx::Insets(custom_layout_params_->internal_border_padding));
SetBetweenChildSpacing(custom_layout_params_->between_buttons_spacing);
UpdateLayout();
}

void TabSlider::OnButtonSelected(TabSliderButton* button) {
Expand All @@ -135,15 +133,16 @@ void TabSlider::OnButtonSelected(TabSliderButton* button) {
}

void TabSlider::Layout() {
BoxLayoutView::Layout();
views::View::Layout();

// Synchronize the selector bounds with selected button's bounds.
for (auto* b : buttons_) {
if (b->selected()) {
selector_view_->SetBoundsRect(b->bounds());
return;
}
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::AddButtonInternal(TabSliderButton* button) {
Expand All @@ -159,9 +158,13 @@ void TabSlider::AddButtonInternal(TabSliderButton* button) {
void TabSlider::OnButtonAdded(TabSliderButton* button) {
DCHECK(button);

// When adding a button, the slider's layout should be updated according to
// the button's recommended slider layout if the custom layout is not set.
if (custom_layout_params_) {
// Always update the layout, at a minimum a new column will need to be added.
base::ScopedClosureRunner scoped_runner(
base::BindOnce(&TabSlider::UpdateLayout, base::Unretained(this)));

// `SetCustomLayout()` results in child button's requested layout being
// ignored.
if (!use_button_recommended_layout_) {
return;
}

Expand All @@ -170,19 +173,61 @@ void TabSlider::OnButtonAdded(TabSliderButton* button) {
return;
}

// Update the inside border, if the spacing between the button and the slider
// is less than the recommended inner border padding.
const int current_internal_border_padding =
(GetPreferredSize().height() - button->GetPreferredSize().height()) / 2;
if (current_internal_border_padding <
recommended_layout->internal_border_padding) {
SetInsideBorderInsets(
gfx::Insets(recommended_layout->internal_border_padding));
custom_layout_params_.internal_border_padding =
std::max(recommended_layout->internal_border_padding,
custom_layout_params_.internal_border_padding);
custom_layout_params_.between_buttons_spacing =
std::max(recommended_layout->between_buttons_spacing,
custom_layout_params_.between_buttons_spacing);
}

void TabSlider::UpdateLayout() {
// Update the layout based on how many buttons exist, `custom_layout_params_`,
// and `distribute_space_evenly_`.
auto* table_layout = SetLayoutManager(std::make_unique<views::TableLayout>());

// Explicitly mark this view as ignored because
// `views::kViewIgnoredByLayoutKey` is not supported by `views::TableLayout`.
table_layout->SetChildViewIgnoredByLayout(selector_view_, /*ignored=*/true);

size_t column_index = 0;
table_layout
->AddPaddingRow(views::TableLayout::kFixedSize,
custom_layout_params_.internal_border_padding)
.AddPaddingColumn(views::TableLayout::kFixedSize,
custom_layout_params_.internal_border_padding);
column_index++;

// Keep track of columns with buttons so their sizes can be linked if
// necessary.
std::vector<size_t> columns_containing_buttons;
for (size_t i = 0; i < buttons_.size(); ++i) {
columns_containing_buttons.push_back(column_index);
table_layout->AddColumn(
views::LayoutAlignment::kStretch, views::LayoutAlignment::kCenter, 1.0f,
views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
column_index++;
if (i < buttons_.size() - 1) {
table_layout->AddPaddingColumn(
views::TableLayout::kFixedSize,
custom_layout_params_.between_buttons_spacing);
column_index++;
}
}

// Add the row buttons will live in.
table_layout->AddRows(1, views::TableLayout::kFixedSize);
if (distribute_space_evenly_) {
// Ensure extra space is spread evenly between the button containing
// columns.
table_layout->LinkColumnSizes(columns_containing_buttons);
}

// Update the between buttons spacing, if it is less than the recommended one.
SetBetweenChildSpacing(std::max(recommended_layout->between_buttons_spacing,
GetBetweenChildSpacing()));
table_layout
->AddPaddingRow(views::TableLayout::kFixedSize,
custom_layout_params_.internal_border_padding)
.AddPaddingColumn(views::TableLayout::kFixedSize,
custom_layout_params_.internal_border_padding);
}

void TabSlider::OnEnabledStateChanged() {
Expand Down
38 changes: 26 additions & 12 deletions ash/style/tab_slider.h
Expand Up @@ -7,7 +7,7 @@

#include "ash/ash_export.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/view.h"

namespace ash {

Expand All @@ -19,22 +19,26 @@ class TabSliderButton;
// rounded rectangle shows behind the selected button. When another button is
// selected, the selector will move from the position of previously selected
// button to the position of currently selected button.
class ASH_EXPORT TabSlider : public views::BoxLayoutView {
class ASH_EXPORT TabSlider : public views::View {
public:
METADATA_HEADER(TabSlider);

// The layout parameters used to configure the box layout of the button
// The layout parameters used to configure the layout of the button
// container.
struct LayoutParams {
int internal_border_padding;
int between_buttons_spacing;
int internal_border_padding = 0;
int between_buttons_spacing = 0;
};

// `has_background` indicates if there is a fully rounded rect background for
// tab slider. `has_selector_animation` indicates whether an animation should
// be shown when the selector moves between buttons.
// `has_background` indicates whether there is a fully rounded rect
// background for the tab slider.
// `has_selector_animation` indicates whether an animation should be shown
// when the selector moves between buttons.
// `distribute_space_evenly` indicates whether the extra space should be
// distributed evenly between buttons.
explicit TabSlider(bool has_background = true,
bool has_selector_animation = true);
bool has_selector_animation = true,
bool distribute_space_evenly = false);
TabSlider(const TabSlider&) = delete;
TabSlider& operator=(const TabSlider&) = delete;
~TabSlider() override;
Expand Down Expand Up @@ -77,16 +81,26 @@ class ASH_EXPORT TabSlider : public views::BoxLayoutView {
// Called when a button is added to the slider.
void OnButtonAdded(TabSliderButton* button);

// Updates the LayoutManager based on how many views exist,
// `distribute_space_evenly_`, and `custom_layout_params_`.
void UpdateLayout();

// Called when the enabled state is changed.
void OnEnabledStateChanged();

// Owned by view hierarchy.
SelectorView* selector_view_;
std::vector<TabSliderButton*> buttons_;

// Parameters for a custom layout. When it is not empty, it takes precedence
// over the button's recommended slider layout.
absl::optional<LayoutParams> custom_layout_params_;
// Parameters for a custom layout. Set by either individual buttons, or
// through `SetCustomLayout()`.
LayoutParams custom_layout_params_;

// Whether child buttons should be forced to evenly share space.
const bool distribute_space_evenly_;

// By default, respect buttons recommended layout.
bool use_button_recommended_layout_ = true;

base::CallbackListSubscription enabled_changed_subscription_;
};
Expand Down

0 comments on commit 109925d

Please sign in to comment.