chromium/ash/wm/window_restore/informed_restore_items_overflow_view.cc

// Copyright 2024 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/wm/window_restore/informed_restore_items_overflow_view.h"

#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/typography.h"
#include "ash/wm/window_restore/informed_restore_app_image_view.h"
#include "ash/wm/window_restore/informed_restore_constants.h"
#include "base/i18n/number_formatting.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/views/background.h"
#include "ui/views/controls/label.h"

namespace ash {

namespace {

constexpr int kOverflowMaxElements = 7;
constexpr int kOverflowMaxThreshold = kOverflowMaxElements - 1;
constexpr int kOverflowTriangleElements = 6;
constexpr int kOverflowIconSpacing = 2;
constexpr int kOverflowBackgroundRounding = 20;
constexpr int kOverflowCountBackgroundRounding = 9;
constexpr gfx::Size kOverflowCountPreferredSize(18, 18);

}  // namespace

InformedRestoreItemsOverflowView::InformedRestoreItemsOverflowView(
    const InformedRestoreContentsData::AppsInfos& apps_infos) {
  const int num_elements = static_cast<int>(apps_infos.size());
  CHECK_GT(num_elements, informed_restore::kMaxItems);

  // TODO(hewer): Fix margins so the icons and text are aligned with
  // `InformedRestoreItemView` elements.
  SetBetweenChildSpacing(informed_restore::kItemChildSpacing);
  SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter);
  SetID(informed_restore::kOverflowViewID);
  SetOrientation(views::BoxLayout::Orientation::kHorizontal);

  auto add_inner_box_layout_view =
      [](views::BoxLayoutView* outer_box) -> views::BoxLayoutView* {
    views::BoxLayoutView* inner_box =
        outer_box->AddChildView(std::make_unique<views::BoxLayoutView>());
    inner_box->SetOrientation(views::BoxLayout::Orientation::kHorizontal);
    inner_box->SetBetweenChildSpacing(kOverflowIconSpacing);
    inner_box->SetMainAxisAlignment(
        views::BoxLayout::MainAxisAlignment::kCenter);
    inner_box->SetCrossAxisAlignment(
        views::BoxLayout::CrossAxisAlignment::kStretch);
    return inner_box;
  };

  // Create a series of `BoxLayoutView`s to represent a 1x2 row, a triangle
  // with one element on top and two on the bottom, or a 2x2 box. The triangle
  // is specific to the 3-window overflow case, and is why we prefer a
  // `BoxLayout` over a `TableLayout` to keep things uniform.
  views::BoxLayoutView* outer_box_view;
  views::BoxLayoutView* top_row_view;
  views::BoxLayoutView* bottom_row_view = nullptr;
  AddChildView(
      views::Builder<views::BoxLayoutView>()
          .CopyAddressTo(&outer_box_view)
          .SetOrientation(views::BoxLayout::Orientation::kVertical)
          .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter)
          .SetBackground(views::CreateThemedRoundedRectBackground(
              informed_restore::kIconBackgroundColorId,
              kOverflowBackgroundRounding))
          .Build());
  top_row_view = add_inner_box_layout_view(outer_box_view);
  top_row_view->SetID(informed_restore::kOverflowTopRowViewID);

  // Populate the `BoxLayoutView`s with window icons or a count of any excess
  // windows.
  for (int i = informed_restore::kOverflowMinThreshold; i < num_elements; ++i) {
    // If there are 5 or more overflow windows, save the last spot in the
    // bottom row to count the remaining windows.
    if (num_elements > kOverflowMaxElements && i >= kOverflowMaxThreshold) {
      views::Label* count_label;
      bottom_row_view->AddChildView(
          views::Builder<views::Label>()
              .CopyAddressTo(&count_label)
              // TODO(hewer): Cut off the maximum number of digits to
              // display.
              .SetText(base::FormatNumber(num_elements - kOverflowMaxThreshold))
              .SetPreferredSize(kOverflowCountPreferredSize)
              .SetEnabledColorId(cros_tokens::kCrosSysOnPrimaryContainer)
              .SetBackground(views::CreateThemedRoundedRectBackground(
                  cros_tokens::kCrosSysPrimaryContainer,
                  kOverflowCountBackgroundRounding))
              .Build());
      TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosLabel2,
                                            *count_label);
      break;
    }

    // The bottom row is needed once:
    //   - `num_elements` == `kOverflowTriangleElements` and `top_row_view` has
    //     1 element. This is to achieve the triangle layout.
    //   - `top_row_view` has 2 elements otherwise.
    if ((num_elements == kOverflowTriangleElements &&
         top_row_view->children().size() == 1u) ||
        top_row_view->children().size() == 2u) {
      if (!bottom_row_view) {
        bottom_row_view = add_inner_box_layout_view(outer_box_view);
        bottom_row_view->SetID(informed_restore::kOverflowBottomRowViewID);
      }
    }

    views::BoxLayoutView* row_view =
        bottom_row_view ? bottom_row_view : top_row_view;
    auto image_view = std::make_unique<InformedRestoreAppImageView>(
        apps_infos[i].app_id, InformedRestoreAppImageView::Type::kOverflow,
        base::DoNothing());
    image_view->SetID(informed_restore::kOverflowImageViewID);
    row_view->AddChildView(std::move(image_view));
  }

  // If there are no children in the bottom row inner box layout view, we have
  // two overflow icons. In this case, remove the between child spacing, and add
  // padding so that the size of the outer box layout will be the same as if
  // there were three or more overflow icons.
  if (bottom_row_view) {
    outer_box_view->SetBetweenChildSpacing(kOverflowIconSpacing);
  } else {
    const int padding_height =
        (informed_restore::kOverflowIconPreferredSize.height() +
         kOverflowIconSpacing) /
        2;
    outer_box_view->SetBetweenChildSpacing(0);
    outer_box_view->SetInsideBorderInsets(gfx::Insets::VH(padding_height, 0));
  }

  // Add a text label displaying the count of the remaining windows.
  views::Label* remaining_windows_label;
  AddChildView(views::Builder<views::Label>()
                   .CopyAddressTo(&remaining_windows_label)
                   .SetEnabledColorId(informed_restore::kItemTextColorId)
                   .SetHorizontalAlignment(gfx::ALIGN_LEFT)
                   .SetText(l10n_util::GetStringFUTF16Int(
                       IDS_ASH_INFORMED_RESTORE_WINDOW_OVERFLOW_COUNT,
                       num_elements - informed_restore::kOverflowMinThreshold))
                   .Build());
  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2,
                                        *remaining_windows_label);
  SetFlexForView(remaining_windows_label, 1);
}

InformedRestoreItemsOverflowView::~InformedRestoreItemsOverflowView() = default;

BEGIN_METADATA(InformedRestoreItemsOverflowView)
END_METADATA

}  // namespace ash