| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/style/pagination_view.h" |
| |
| #include <optional> |
| #include <utility> |
| |
| #include "ash/public/cpp/pagination/pagination_model.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/style_util.h" |
| #include "base/i18n/number_formatting.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/models/image_model.h" |
| #include "ui/chromeos/styles/cros_tokens_color_mappings.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/rounded_corners_f.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/layout/box_layout_view.h" |
| #include "ui/views/view_class_properties.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The minimum number of pages to show the pagination view. |
| constexpr int kMinNumPages = 2; |
| |
| // Attributes of arrow buttons. |
| constexpr int kArrowButtonIconSize = 20; |
| constexpr ui::ColorId kArrowButtonColorId = cros_tokens::kCrosSysSecondary; |
| constexpr int kArrowIndicatorSpacing = 2; |
| |
| // Attributes of indicator. |
| constexpr int kIndicatorButtonSize = 20; |
| constexpr int kIndicatorRadius = 4; |
| constexpr int kIndicatorStrokeWidth = 1; |
| constexpr int kIndicatorSpacing = 2; |
| constexpr ui::ColorId kIndicatorColorId = cros_tokens::kCrosSysPrimary; |
| constexpr int kMaxNumVisibleIndicators = 5; |
| |
| // Get the width of the indicator container. |
| int GetIndicatorContainerWidth(int total_pages) { |
| if (total_pages < kMinNumPages) { |
| return 0; |
| } |
| |
| const int visible_num = std::min(total_pages, kMaxNumVisibleIndicators); |
| return visible_num * kIndicatorButtonSize + |
| (visible_num - 1) * kIndicatorSpacing; |
| } |
| |
| // A structure holds the info needed by interpolation. |
| template <typename T> |
| struct InterpolationInterval { |
| // The start time and value. |
| double start_time; |
| T start_value; |
| // The end time and value. |
| double end_time; |
| T target_value; |
| }; |
| |
| // IndicatorButton: |
| // A button with a hollow circle in the center. |
| class IndicatorButton : public views::Button { |
| METADATA_HEADER(IndicatorButton, views::Button) |
| |
| public: |
| IndicatorButton(PressedCallback callback, |
| const std::u16string& accessible_name) |
| : views::Button(std::move(callback)) { |
| SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY); |
| GetViewAccessibility().SetName(accessible_name); |
| } |
| |
| IndicatorButton(const IndicatorButton&) = delete; |
| IndicatorButton& operator=(const IndicatorButton&) = delete; |
| ~IndicatorButton() override = default; |
| |
| // Gets the bounds of the circle in the center. |
| gfx::Rect GetIndicatorBounds() const { |
| gfx::Rect indicator_bounds = bounds(); |
| indicator_bounds.Inset( |
| gfx::Insets(0.5 * kIndicatorButtonSize - kIndicatorRadius)); |
| return indicator_bounds; |
| } |
| |
| // views::Button: |
| gfx::Size CalculatePreferredSize( |
| const views::SizeBounds& available_size) const override { |
| return gfx::Size(kIndicatorButtonSize, kIndicatorButtonSize); |
| } |
| |
| void PaintButtonContents(gfx::Canvas* canvas) override { |
| cc::PaintFlags flags; |
| flags.setAntiAlias(true); |
| flags.setColor(GetColorProvider()->GetColor(kIndicatorColorId)); |
| flags.setStyle(cc::PaintFlags::kStroke_Style); |
| flags.setStrokeWidth(kIndicatorStrokeWidth); |
| // Do inner stroke. |
| canvas->DrawCircle(GetLocalBounds().CenterPoint(), |
| kIndicatorRadius - 0.5f * kIndicatorStrokeWidth, flags); |
| } |
| }; |
| |
| BEGIN_METADATA(IndicatorButton) |
| END_METADATA |
| |
| } // namespace |
| |
| //------------------------------------------------------------------------------ |
| // PaginationView::SelectorDotView: |
| // A solid circle that performs deformation with the pace of page transition. |
| class PaginationView::SelectorDotView : public views::View { |
| METADATA_HEADER(SelectorDotView, views::View) |
| |
| public: |
| using DeformInterval = InterpolationInterval<gfx::Rect>; |
| |
| SelectorDotView() { |
| SetBackground( |
| StyleUtil::CreateThemedFullyRoundedRectBackground(kIndicatorColorId)); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| // Set selector dot ignored by layout since it will follow selected |
| // indicator and deform on page transition. |
| SetProperty(views::kViewIgnoredByLayoutKey, true); |
| } |
| |
| SelectorDotView(const SelectorDotView&) = delete; |
| SelectorDotView& operator=(const SelectorDotView&) = delete; |
| ~SelectorDotView() override = default; |
| |
| // Adds a new deform interval. |
| void AddDeformInterval(DeformInterval interval) { |
| DCHECK_LT(interval.start_time, interval.end_time); |
| deform_intervals_.push_back(interval); |
| // Sort the intervals according to the start time in ascending order. |
| std::sort( |
| deform_intervals_.begin(), deform_intervals_.end(), |
| [](const DeformInterval& interval_1, const DeformInterval& interval_2) { |
| return interval_1.start_time < interval_2.start_time; |
| }); |
| } |
| |
| // Performs deformation according to the given progress within deform |
| // intervals. |
| void Deform(double progress) { |
| if (deform_intervals_.empty()) { |
| return; |
| } |
| |
| auto iter = std::find_if(deform_intervals_.begin(), deform_intervals_.end(), |
| [&](DeformInterval& interval) { |
| return interval.start_time <= progress && |
| interval.end_time >= progress; |
| }); |
| |
| if (iter == deform_intervals_.end()) { |
| return; |
| } |
| |
| // Get intermediate bounds by interpolating the origin and target bounds. |
| const gfx::Rect intermediate_bounds = gfx::Tween::RectValueBetween( |
| (progress - iter->start_time) / (iter->end_time - iter->start_time), |
| iter->start_value, iter->target_value); |
| SetBoundsRect(intermediate_bounds); |
| } |
| |
| void ResetDeform(bool canceled) { |
| if (!deform_intervals_.empty()) { |
| SetBoundsRect(canceled ? deform_intervals_.front().start_value |
| : deform_intervals_.back().target_value); |
| } |
| deform_intervals_.clear(); |
| } |
| |
| // Returns true if deformation is still in progress. |
| bool DeformingInProgress() const { return !deform_intervals_.empty(); } |
| |
| private: |
| std::vector<DeformInterval> deform_intervals_; |
| }; |
| |
| BEGIN_METADATA(PaginationView, SelectorDotView) |
| END_METADATA |
| |
| //------------------------------------------------------------------------------ |
| // PaginationView::IndicatorContainer: |
| // The container of indicators. If the indicator to be selected is not visible, |
| // the container will scroll with the pace of pagination transition. |
| class PaginationView::IndicatorContainer : public views::BoxLayoutView { |
| METADATA_HEADER(IndicatorContainer, views::BoxLayoutView) |
| |
| public: |
| explicit IndicatorContainer(views::BoxLayout::Orientation orientation) { |
| SetOrientation(orientation); |
| SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kCenter); |
| SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter); |
| SetBetweenChildSpacing(kIndicatorSpacing); |
| } |
| |
| IndicatorContainer(const IndicatorContainer&) = delete; |
| IndicatorContainer& operator=(const IndicatorContainer&) = delete; |
| ~IndicatorContainer() override = default; |
| |
| // Attaches an indicator to the end of container. |
| void PushIndicator(PaginationModel* model) { |
| const int page = buttons_.size(); |
| // Since the selector dot will also be added in the container, we should use |
| // `AddChildViewAt` to ensure the indicator is in the expected position in |
| // the child views. |
| auto* indicator_button = AddChildViewAt( |
| std::make_unique<IndicatorButton>( |
| base::BindRepeating( |
| [](PaginationModel* model, int page, const ui::Event& event) { |
| model->SelectPage(page, /*animate=*/true); |
| }, |
| model, page), |
| l10n_util::GetStringFUTF16( |
| IDS_APP_LIST_PAGE_SWITCHER, base::FormatNumber(page + 1), |
| base::FormatNumber(model->total_pages()))), |
| page); |
| buttons_.emplace_back(indicator_button); |
| } |
| |
| // Discards the indicator at the end of the container. |
| void PopIndicator() { |
| DCHECK(buttons_.size()); |
| auto indicator_button = buttons_.back(); |
| buttons_.pop_back(); |
| RemoveChildViewT(std::exchange(indicator_button, nullptr)); |
| } |
| |
| // Gets indicator corresponding to the given page. |
| IndicatorButton* GetIndicatorByPage(int page) { |
| DCHECK_GE(page, 0); |
| DCHECK_LT(page, static_cast<int>(buttons_.size())); |
| return buttons_[page].get(); |
| } |
| |
| int GetNumberOfIndicators() const { return buttons_.size(); } |
| |
| // Sets up scrolling if an invisible page is selected. |
| void StartScroll(int start_page, int target_page) { |
| // Scroll the indicator container by the distance of a indicator button size |
| // plus button spacing to reveal the next/previous indicator. |
| // TODO(zxdan): settings bounds at each step will cause repainting which is |
| // expensive. However, using transform sometimes makes the stroke of |
| // indicator circle become thicker. Will investigate the cause latter. |
| const bool forward = start_page < target_page; |
| const int start_page_offset = |
| forward ? kMaxNumVisibleIndicators - start_page - 1 : -start_page; |
| const int target_page_offset = |
| forward ? kMaxNumVisibleIndicators - target_page - 1 : -target_page; |
| const int scroll_unit = kIndicatorButtonSize + kIndicatorSpacing; |
| scroll_interval_ = {0.0, start_page_offset * scroll_unit, 1.0, |
| target_page_offset * scroll_unit}; |
| } |
| |
| // Scrolls the indicator container according to the given progress value. |
| void Scroll(double progress) { |
| if (!scroll_interval_) { |
| return; |
| } |
| |
| // Interpolate the scroll interval to get current container bounds. |
| ScrollWithOffset( |
| gfx::Tween::IntValueBetween(progress, scroll_interval_->start_value, |
| scroll_interval_->target_value)); |
| } |
| |
| void ResetScroll(bool canceled) { |
| if (scroll_interval_) { |
| ScrollWithOffset(canceled ? scroll_interval_->start_value |
| : scroll_interval_->target_value); |
| } |
| scroll_interval_ = std::nullopt; |
| } |
| |
| // Returns true if the scrolling is in progress. |
| bool ScrollingInProgress() { return !!scroll_interval_; } |
| |
| private: |
| // Scroll horizontally or vertically with given offset. |
| void ScrollWithOffset(int offset) { |
| if (GetOrientation() == views::BoxLayout::Orientation::kHorizontal) { |
| SetX(offset); |
| } else { |
| SetY(offset); |
| } |
| } |
| |
| std::vector<raw_ptr<IndicatorButton>> buttons_; |
| std::optional<InterpolationInterval<int>> scroll_interval_; |
| }; |
| |
| BEGIN_METADATA(PaginationView, IndicatorContainer) |
| END_METADATA |
| |
| //------------------------------------------------------------------------------ |
| // PaginationView: |
| PaginationView::PaginationView(PaginationModel* model, Orientation orientation) |
| : model_(model), |
| orientation_(orientation), |
| indicator_scroll_view_( |
| AddChildView(std::make_unique<views::ScrollView>())), |
| indicator_container_(indicator_scroll_view_->SetContents( |
| std::make_unique<IndicatorContainer>( |
| orientation == Orientation::kHorizontal |
| ? views::BoxLayout::Orientation::kHorizontal |
| : views::BoxLayout::Orientation::kVertical))) { |
| DCHECK(model_); |
| model_observation_.Observe(model_.get()); |
| |
| // Remove the default background color. |
| indicator_scroll_view_->SetBackgroundColor(std::nullopt); |
| |
| // The scroll view does not accept any scroll event. |
| indicator_scroll_view_->SetHorizontalScrollBarMode( |
| views::ScrollView::ScrollBarMode::kDisabled); |
| indicator_scroll_view_->SetVerticalScrollBarMode( |
| views::ScrollView::ScrollBarMode::kDisabled); |
| |
| if (model_->total_pages() >= kMinNumPages) { |
| TotalPagesChanged(0, model_->total_pages()); |
| } |
| |
| if (ShouldShowSelectorDot()) { |
| CreateSelectorDot(); |
| } |
| } |
| |
| PaginationView::~PaginationView() = default; |
| |
| gfx::Size PaginationView::CalculatePreferredSize( |
| const views::SizeBounds& available_size) const { |
| const int total_pages = model_->total_pages(); |
| if (total_pages < kMinNumPages) { |
| return gfx::Size(); |
| } |
| |
| // Initialize container size with indicator container size. |
| int container_size = GetIndicatorContainerWidth(total_pages); |
| if (total_pages > kMaxNumVisibleIndicators) { |
| // If the number of total pages exceeds visible maximum, add arrow buttons. |
| container_size += 2 * (kArrowButtonIconSize + kArrowIndicatorSpacing); |
| } |
| |
| return (orientation_ == Orientation::kHorizontal) |
| ? gfx::Size(container_size, kIndicatorButtonSize) |
| : gfx::Size(kIndicatorButtonSize, container_size); |
| } |
| |
| void PaginationView::Layout(PassKey) { |
| const bool horizontal = (orientation_ == Orientation::kHorizontal); |
| int offset = 0; |
| |
| // A callback to set the bounds of given arrow button if it exists. Return the |
| // button size if the button exists. Otherwise, return 0. |
| auto set_arrow_button = [&](views::ImageButton* arrow_button) -> int { |
| if (arrow_button) { |
| gfx::Point origin = |
| horizontal ? gfx::Point(offset, 0) : gfx::Point(0, offset); |
| arrow_button->SetBoundsRect(gfx::Rect( |
| origin, gfx::Size(kArrowButtonIconSize, kArrowButtonIconSize))); |
| return kArrowButtonIconSize; |
| } |
| return 0; |
| }; |
| |
| // Set the backward arrow button if exists. |
| offset += set_arrow_button(backward_arrow_button_); |
| |
| // Set the indicator container. |
| indicator_container_->SizeToPreferredSize(); |
| const int scroll_view_size = |
| GetIndicatorContainerWidth(model_->total_pages()); |
| if (horizontal) { |
| indicator_scroll_view_->SetBounds(offset, 0, scroll_view_size, |
| kIndicatorButtonSize); |
| } else { |
| indicator_scroll_view_->SetBounds(0, offset, kIndicatorButtonSize, |
| scroll_view_size); |
| } |
| offset += scroll_view_size + kArrowIndicatorSpacing; |
| |
| // Set the right arrow button if exists. |
| set_arrow_button(forward_arrow_button_); |
| |
| // Update arrow button visibility and selector dot position. |
| UpdateArrowButtonsVisiblity(); |
| UpdateSelectorDot(); |
| } |
| |
| void PaginationView::CreateArrowButtons() { |
| const bool horizontal = (orientation_ == Orientation::kHorizontal); |
| for (bool forward : {true, false}) { |
| auto arrow_button = std::make_unique<views::ImageButton>( |
| base::BindRepeating(&PaginationView::OnArrowButtonPressed, |
| base::Unretained(this), forward)); |
| |
| arrow_button->SetImageModel( |
| views::ImageButton::ButtonState::STATE_NORMAL, |
| ui::ImageModel::FromVectorIcon( |
| forward |
| ? (horizontal ? kOverflowShelfRightIcon : kChevronDownSmallIcon) |
| : (horizontal ? kOverflowShelfLeftIcon : kChevronUpSmallIcon), |
| kArrowButtonColorId, kArrowButtonIconSize)); |
| |
| if (forward) { |
| arrow_button->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_PAGINATION_FORWARD_ARROW_TOOLTIP)); |
| forward_arrow_button_ = AddChildView(std::move(arrow_button)); |
| } else { |
| arrow_button->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_PAGINATION_BACKWARD_ARROW_TOOLTIP)); |
| backward_arrow_button_ = AddChildView(std::move(arrow_button)); |
| } |
| } |
| } |
| |
| void PaginationView::RemoveArrowButtons() { |
| RemoveChildViewT(std::exchange(backward_arrow_button_, nullptr)); |
| RemoveChildViewT(std::exchange(forward_arrow_button_, nullptr)); |
| } |
| |
| void PaginationView::UpdateArrowButtonsVisiblity() { |
| // If the first page indicator is visible, hide the left arrow button. |
| if (backward_arrow_button_) { |
| backward_arrow_button_->SetVisible(!IsIndicatorVisible(0)); |
| } |
| |
| // If the last page indicator is visible, hide the right arrow button. |
| if (forward_arrow_button_) { |
| forward_arrow_button_->SetVisible( |
| !IsIndicatorVisible(model_->total_pages() - 1)); |
| } |
| } |
| |
| void PaginationView::OnArrowButtonPressed(bool forward, |
| const ui::Event& event) { |
| const int page_offset = forward ? 1 : -1; |
| model_->SelectPage(model_->selected_page() + page_offset, /*animate=*/true); |
| } |
| |
| void PaginationView::MaybeSetUpScroll() { |
| const int current_page = model_->selected_page(); |
| const int target_page = model_->transition().target_page; |
| if (!model_->is_valid_page(current_page) || |
| !model_->is_valid_page(target_page)) { |
| return; |
| } |
| |
| // If the target page indicator is not in visible area, scroll the container. |
| if (!IsIndicatorVisible(target_page)) { |
| indicator_container_->StartScroll(current_page, target_page); |
| } |
| } |
| |
| bool PaginationView::ShouldShowSelectorDot() const { |
| return model_->total_pages() >= kMinNumPages && |
| model_->is_valid_page(model_->selected_page()); |
| } |
| |
| void PaginationView::CreateSelectorDot() { |
| if (selector_dot_) { |
| return; |
| } |
| |
| selector_dot_ = |
| indicator_container_->AddChildView(std::make_unique<SelectorDotView>()); |
| UpdateSelectorDot(); |
| } |
| |
| void PaginationView::RemoveSelectorDot() { |
| if (!selector_dot_) { |
| return; |
| } |
| |
| indicator_container_->RemoveChildViewT(std::exchange(selector_dot_, nullptr)); |
| } |
| |
| void PaginationView::UpdateSelectorDot() { |
| if (!selector_dot_) { |
| return; |
| } |
| |
| // The selected page may become invalid when total pages is changing. |
| const int selected_page = model_->selected_page(); |
| if (!model_->is_valid_page(selected_page)) { |
| return; |
| } |
| |
| // Move the selector dot to the position of selected page indicator if the |
| // selector dot is not deforming. |
| if (!selector_dot_->DeformingInProgress()) { |
| selector_dot_->SetBoundsRect( |
| indicator_container_->GetIndicatorByPage(selected_page) |
| ->GetIndicatorBounds()); |
| } |
| } |
| |
| void PaginationView::SetUpSelectorDotDeformation() { |
| CHECK(selector_dot_); |
| CHECK(!selector_dot_->DeformingInProgress()); |
| |
| const int current_page = model_->selected_page(); |
| const int target_page = model_->transition().target_page; |
| |
| if (!model_->is_valid_page(current_page) || |
| !model_->is_valid_page(target_page)) { |
| return; |
| } |
| |
| const gfx::Rect current_bounds = |
| indicator_container_->GetIndicatorByPage(current_page) |
| ->GetIndicatorBounds(); |
| const gfx::Rect target_bounds = |
| indicator_container_->GetIndicatorByPage(target_page) |
| ->GetIndicatorBounds(); |
| // If moves to a neighbor page, the selector dot will first be stretched into |
| // a pill shape until it connects the current indicator to the target |
| // indicator, and then shrink back to a circle at the target indicator |
| // position. |
| if (std::abs(target_page - current_page) == 1) { |
| const gfx::Rect intermediate_bounds = |
| gfx::UnionRects(current_bounds, target_bounds); |
| selector_dot_->AddDeformInterval( |
| {0.0, current_bounds, 0.5, intermediate_bounds}); |
| selector_dot_->AddDeformInterval( |
| {0.5, intermediate_bounds, 1.0, target_bounds}); |
| return; |
| } |
| |
| // If jumps across multiple pages, the selector dot will first shrink at the |
| // current indicator position, and then expand at the target indicator |
| // position. |
| selector_dot_->AddDeformInterval( |
| {0.0, current_bounds, 0.5, |
| gfx::Rect(current_bounds.CenterPoint(), gfx::Size())}); |
| selector_dot_->AddDeformInterval( |
| {0.5, gfx::Rect(target_bounds.CenterPoint(), gfx::Size()), 1.0, |
| target_bounds}); |
| } |
| |
| bool PaginationView::IsIndicatorVisible(int page) const { |
| // Check if the indicator is in the visible rect of the scroll view. |
| return indicator_scroll_view_->GetVisibleRect().Contains( |
| indicator_container_->GetIndicatorByPage(page)->bounds()); |
| } |
| |
| void PaginationView::SelectedPageChanged(int old_selected, int new_selected) { |
| // Update selector dot position and arrow buttons visibility. |
| if (ShouldShowSelectorDot()) { |
| if (!selector_dot_) { |
| CreateSelectorDot(); |
| } else { |
| // Finish and reset ongoing deformation. |
| selector_dot_->ResetDeform(/*canceled=*/false); |
| } |
| } else { |
| RemoveSelectorDot(); |
| } |
| |
| // Finish and reset ongoing indicator container scrolling. |
| if (indicator_container_->ScrollingInProgress()) { |
| indicator_container_->ResetScroll(/*canceled=*/false); |
| UpdateArrowButtonsVisiblity(); |
| } |
| } |
| |
| void PaginationView::TotalPagesChanged(int previous_page_count, |
| int new_page_count) { |
| const int current_indicator_num = |
| indicator_container_->GetNumberOfIndicators(); |
| new_page_count = new_page_count < kMinNumPages ? 0 : new_page_count; |
| if (current_indicator_num == new_page_count) { |
| return; |
| } |
| |
| if (current_indicator_num < new_page_count) { |
| // Add more indicators at the end of container. |
| for (int i = current_indicator_num; i < new_page_count; i++) { |
| indicator_container_->PushIndicator(model_.get()); |
| } |
| |
| // Add arrow buttons if the number of total pages exceeds the visible |
| // maximum. |
| if (current_indicator_num <= kMaxNumVisibleIndicators && |
| new_page_count > kMaxNumVisibleIndicators) { |
| CreateArrowButtons(); |
| } |
| |
| // Create selector dot if the number of total pages exceeds the minimum |
| // number of pages. |
| if (new_page_count >= kMinNumPages && !selector_dot_) { |
| CreateSelectorDot(); |
| } |
| } else { |
| // Remove indicators from the end of the container. |
| for (int i = current_indicator_num; i > new_page_count; i--) { |
| indicator_container_->PopIndicator(); |
| } |
| |
| // Remove arrow buttons if the number of total pages does not exceed the |
| // visible maximum. |
| if (current_indicator_num > kMaxNumVisibleIndicators && |
| new_page_count <= kMaxNumVisibleIndicators) { |
| RemoveArrowButtons(); |
| } |
| |
| // Remove the selector dot if the number of pages is less than the minimum |
| // number of pages. |
| if (new_page_count < kMinNumPages && selector_dot_) { |
| RemoveSelectorDot(); |
| } |
| } |
| |
| DeprecatedLayoutImmediately(); |
| } |
| |
| void PaginationView::TransitionChanged() { |
| if (!selector_dot_) { |
| return; |
| } |
| |
| // If there is no transition, reset and cancel current selector dot |
| // deformation and indicator container scrolling. |
| if (!model_->has_transition()) { |
| selector_dot_->ResetDeform(/*canceled=*/true); |
| indicator_container_->ResetScroll(/*canceled=*/true); |
| return; |
| } |
| |
| const double progress = model_->transition().progress; |
| |
| // Scroll the indicator container if needed. |
| if (!indicator_container_->ScrollingInProgress()) { |
| MaybeSetUpScroll(); |
| } |
| indicator_container_->Scroll(progress); |
| |
| // Deform the selector dot. |
| if (!selector_dot_->DeformingInProgress()) { |
| SetUpSelectorDotDeformation(); |
| } |
| // Deform the selector dot. |
| selector_dot_->Deform(progress); |
| } |
| |
| BEGIN_METADATA(PaginationView) |
| END_METADATA |
| } // namespace ash |