chromium/ash/system/unified/quick_settings_view.cc

// Copyright 2022 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/system/unified/quick_settings_view.h"

#include "ash/ash_element_identifiers.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/style/pagination_view.h"
#include "ash/system/media/quick_settings_media_view_container.h"
#include "ash/system/tray/interacted_by_tap_recorder.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_detailed_view.h"
#include "ash/system/unified/detailed_view_controller.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/system/unified/feature_tiles_container_view.h"
#include "ash/system/unified/quick_settings_footer.h"
#include "ash/system/unified/quick_settings_header.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/view_class_properties.h"

namespace ash {

namespace {

constexpr auto kPaginationViewMargin = gfx::Insets::TLBR(0, 0, 8, 0);
constexpr auto kSlidersContainerMargin = gfx::Insets::TLBR(4, 0, 0, 0);

class AccessibilityFocusHelperView : public views::View {
  METADATA_HEADER(AccessibilityFocusHelperView, views::View)

 public:
  explicit AccessibilityFocusHelperView(UnifiedSystemTrayController* controller)
      : controller_(controller) {
    GetViewAccessibility().SetRole(ax::mojom::Role::kListItem);
  }

  bool HandleAccessibleAction(const ui::AXActionData& action_data) override {
    GetFocusManager()->ClearFocus();
    GetFocusManager()->SetStoredFocusView(nullptr);
    return true;
  }

 private:
  raw_ptr<UnifiedSystemTrayController> controller_;
};

BEGIN_METADATA(AccessibilityFocusHelperView)
END_METADATA

}  // namespace

// The container view for the system tray, i.e. the panel containing settings
// buttons and sliders (e.g. sign out, lock, volume slider, etc.).
class QuickSettingsView::SystemTrayContainer : public views::View {
  METADATA_HEADER(SystemTrayContainer, views::View)

 public:
  SystemTrayContainer()
      : layout_manager_(SetLayoutManager(std::make_unique<views::BoxLayout>(
            views::BoxLayout::Orientation::kVertical))) {}
  SystemTrayContainer(const SystemTrayContainer&) = delete;
  SystemTrayContainer& operator=(const SystemTrayContainer&) = delete;

  ~SystemTrayContainer() override = default;

  void SetFlexForView(views::View* view) {
    DCHECK_EQ(view->parent(), this);
    layout_manager_->SetFlexForView(view, 1);
  }

  // views::View:
  void ChildPreferredSizeChanged(views::View* child) override {
    PreferredSizeChanged();
  }

 private:
  const raw_ptr<views::BoxLayout> layout_manager_;
};

BEGIN_METADATA(QuickSettingsView, SystemTrayContainer)
END_METADATA

QuickSettingsView::QuickSettingsView(UnifiedSystemTrayController* controller)
    : controller_(controller),
      interacted_by_tap_recorder_(
          std::make_unique<InteractedByTapRecorder>(this)) {
  DCHECK(controller_);
  controller_->model()->pagination_model()->AddObserver(this);

  SetProperty(views::kElementIdentifierKey, kQuickSettingsViewElementId);

  SetLayoutManager(std::make_unique<views::FillLayout>());

  auto* scroll_view = AddChildView(std::make_unique<views::ScrollView>());
  scroll_view->SetAllowKeyboardScrolling(false);
  scroll_view->SetBackgroundColor(std::nullopt);
  scroll_view->ClipHeightTo(0, INT_MAX);
  scroll_view->SetDrawOverflowIndicator(false);
  scroll_view->SetVerticalScrollBarMode(
      views::ScrollView::ScrollBarMode::kHiddenButEnabled);
  system_tray_container_ =
      scroll_view->SetContents(std::make_unique<views::FlexLayoutView>());
  system_tray_container_->SetOrientation(views::LayoutOrientation::kVertical);

  header_ = system_tray_container_->AddChildView(
      std::make_unique<QuickSettingsHeader>(controller_));
  feature_tiles_container_ = system_tray_container_->AddChildView(
      std::make_unique<FeatureTilesContainerView>(controller_));

  // Creates a container for `PaginationView`. This is needed to align the view
  // in the center.
  auto* pagination_view_container = system_tray_container_->AddChildView(
      std::make_unique<views::BoxLayoutView>());
  pagination_view_container->SetOrientation(
      views::BoxLayout::Orientation::kHorizontal);
  pagination_view_container->SetMainAxisAlignment(
      views::BoxLayout::MainAxisAlignment::kCenter);
  pagination_view_container->SetProperty(views::kMarginsKey,
                                         kPaginationViewMargin);
  pagination_view_ =
      pagination_view_container->AddChildView(std::make_unique<PaginationView>(
          controller_->model()->pagination_model()));

  media_view_container_ = system_tray_container_->AddChildView(
      std::make_unique<QuickSettingsMediaViewContainer>(controller_));

  sliders_container_ = system_tray_container_->AddChildView(
      std::make_unique<views::FlexLayoutView>());
  sliders_container_->SetOrientation(views::LayoutOrientation::kVertical);
  sliders_container_->SetProperty(views::kMarginsKey, kSlidersContainerMargin);

  footer_ = system_tray_container_->AddChildView(
      std::make_unique<QuickSettingsFooter>(controller_));

  detailed_view_container_ = AddChildView(std::make_unique<views::View>());
  detailed_view_container_->SetUseDefaultFillLayout(true);
  detailed_view_container_->SetVisible(false);

  auto* focus_helper_view_ = system_tray_container_->AddChildView(
      std::make_unique<AccessibilityFocusHelperView>(controller_));
  focus_helper_view_->SetID(VIEW_ID_QS_ACCESSIBILITY_FOCUS_HELPER_VIEW);
}

QuickSettingsView::~QuickSettingsView() {
  controller_->model()->pagination_model()->RemoveObserver(this);
}

void QuickSettingsView::SetMaxHeight(int max_height) {
  max_height_ = max_height;
  feature_tiles_container_->SetRowsFromHeight(
      CalculateHeightForFeatureTilesContainer());
}

void QuickSettingsView::AddTiles(
    std::vector<std::unique_ptr<FeatureTile>> tiles) {
  feature_tiles_container_->AddTiles(std::move(tiles));
}

views::View* QuickSettingsView::AddSliderView(
    std::unique_ptr<views::View> slider_view) {
  return sliders_container_->AddChildView(std::move(slider_view));
}

void QuickSettingsView::AddMediaView(std::unique_ptr<views::View> media_view) {
  DCHECK(media_view);
  DCHECK(media_view_container_);
  media_view_container_->AddChildView(std::move(media_view));
}

void QuickSettingsView::SetShowMediaView(bool show_media_view) {
  DCHECK(media_view_container_);
  media_view_container_->SetShowMediaView(show_media_view);
  feature_tiles_container_->AdjustRowsForMediaViewVisibility(
      show_media_view, CalculateHeightForFeatureTilesContainer());
  PreferredSizeChanged();
}

void QuickSettingsView::SetDetailedView(
    std::unique_ptr<views::View> detailed_view) {
  detailed_view_container_->RemoveAllChildViews();
  detailed_view_container_->AddChildView(std::move(detailed_view));
  system_tray_container_->SetVisible(false);
  detailed_view_container_->SetVisible(true);

  // We need to trigger layout here to make sure that `CalendarView` is notified
  // that it can be initialized through `OnViewBoundsChanged`. The
  // `CalendarView` depends on `OnViewsBoundsChanged` to check if it can
  // `ScrollToToday`.
  DeprecatedLayoutImmediately();
}

void QuickSettingsView::ResetDetailedView() {
  detailed_view_container_->RemoveAllChildViews();
  detailed_view_container_->SetVisible(false);
  if (media_view_container_) {
    media_view_container_->MaybeShowMediaView();
  }
  system_tray_container_->SetVisible(true);
}

void QuickSettingsView::SaveFocus() {
  auto* focus_manager = GetFocusManager();
  if (!focus_manager) {
    return;
  }

  saved_focused_view_ = focus_manager->GetFocusedView();
}

void QuickSettingsView::RestoreFocus() {
  if (saved_focused_view_) {
    saved_focused_view_->RequestFocus();
  }
}

int QuickSettingsView::GetCurrentHeight() const {
  return GetPreferredSize().height();
}

// TODO(b/253303697): The `FeatureTilesContainer` does not currently respect
// size constraints when vertical space is limited. This leads to the
// `QuickSettingsView` being clipped from the bottom.
int QuickSettingsView::CalculateHeightForFeatureTilesContainer() {
  int media_view_container_height =
      media_view_container_ ? media_view_container_->GetExpandedHeight() : 0;

  return max_height_ - header_->GetPreferredSize().height() -
         pagination_view_->GetPreferredSize().height() -
         sliders_container_->GetPreferredSize().height() -
         media_view_container_height - footer_->GetPreferredSize().height();
}

std::u16string QuickSettingsView::GetDetailedViewAccessibleName() const {
  return controller_->detailed_view_controller()->GetAccessibleName();
}

bool QuickSettingsView::IsDetailedViewShown() const {
  return detailed_view_container_->GetVisible();
}

void QuickSettingsView::TotalPagesChanged(int previous_page_count,
                                          int new_page_count) {
  pagination_view_->SetVisible(new_page_count > 1);
}

views::View* QuickSettingsView::GetAccessibilityFocusHelperViewForTesting() {
  return system_tray_container_->GetViewByID(
      VIEW_ID_QS_ACCESSIBILITY_FOCUS_HELPER_VIEW);
}

BEGIN_METADATA(QuickSettingsView)
END_METADATA

}  // namespace ash