// 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/picker/views/picker_image_item_grid_view.h"
#include <iterator>
#include <memory>
#include <utility>
#include "ash/picker/views/picker_image_item_view.h"
#include "ash/picker/views/picker_item_view.h"
#include "ash/picker/views/picker_traversable_item_container.h"
#include "base/notimplemented.h"
#include "base/ranges/algorithm.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
namespace ash {
namespace {
// Padding between image grid items.
constexpr int kImageGridPadding = 8;
// Margin for the image grid.
constexpr auto kImageGridMargin = gfx::Insets::TLBR(16, 16, 0, 16);
// Number of columns in an image grid.
constexpr int kNumImageGridColumns = 2;
int GetImageGridColumnWidth(int grid_width) {
return (grid_width - (kNumImageGridColumns - 1) * kImageGridPadding -
kImageGridMargin.width()) /
kNumImageGridColumns;
}
std::unique_ptr<views::View> CreateImageGridColumn() {
auto column =
views::Builder<views::BoxLayoutView>()
.SetOrientation(views::BoxLayout::Orientation::kVertical)
.SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStart)
.Build();
column->SetBetweenChildSpacing(kImageGridPadding);
return column;
}
views::View* ItemInColumnWithIndexClosestTo(views::View* column,
const size_t index) {
if (column->children().empty()) {
return nullptr;
} else if (index < column->children().size()) {
return column->children()[index].get();
} else {
return column->children().back().get();
}
}
} // namespace
PickerImageItemGridView::PickerImageItemGridView(int grid_width)
: grid_width_(grid_width) {
SetLayoutManager(std::make_unique<views::TableLayout>())
->AddColumn(/*h_align=*/views::LayoutAlignment::kCenter,
/*v_align=*/views::LayoutAlignment::kStart,
/*horizontal_resize=*/1.0f,
/*size_type=*/views::TableLayout::ColumnSize::kFixed,
/*fixed_width=*/0, /*min_width=*/0)
.AddPaddingColumn(
/*horizontal_resize=*/views::TableLayout::kFixedSize,
/*width=*/kImageGridPadding)
.AddColumn(/*h_align=*/views::LayoutAlignment::kCenter,
/*v_align=*/views::LayoutAlignment::kStart,
/*horizontal_resize=*/1.0f,
/*size_type=*/views::TableLayout::ColumnSize::kFixed,
/*fixed_width=*/0, /*min_width=*/0)
.AddRows(1, /*vertical_resize=*/views::TableLayout::kFixedSize,
/*height=*/0);
SetProperty(views::kMarginsKey, kImageGridMargin);
AddChildView(CreateImageGridColumn());
AddChildView(CreateImageGridColumn());
}
PickerImageItemGridView::~PickerImageItemGridView() = default;
views::View* PickerImageItemGridView::GetTopItem() {
views::View* column = children().front();
return column->children().empty() ? nullptr
: column->children().front().get();
}
views::View* PickerImageItemGridView::GetBottomItem() {
views::View* tallest_column =
base::ranges::max(children(),
/*comp=*/base::ranges::less(),
/*proj=*/[](const views::View* v) {
return v->GetPreferredSize().height();
});
return tallest_column->children().empty()
? nullptr
: tallest_column->children().back().get();
}
views::View* PickerImageItemGridView::GetItemAbove(views::View* item) {
views::View* column = GetColumnContaining(item);
if (!column || item == column->children().front()) {
return nullptr;
}
return std::prev(base::ranges::find(column->children(), item))->get();
}
views::View* PickerImageItemGridView::GetItemBelow(views::View* item) {
views::View* column = GetColumnContaining(item);
if (!column || item == column->children().back()) {
return nullptr;
}
return std::next(base::ranges::find(column->children(), item))->get();
}
views::View* PickerImageItemGridView::GetItemLeftOf(views::View* item) {
views::View* column = GetColumnContaining(item);
if (!column || column == children().front()) {
return nullptr;
}
// Prefer to return the item with the same index in the column to the left,
// since this will probably be at a similar height to `item` (at least in
// usual scenarios where the grid items all have similar dimensions).
const size_t item_index = column->GetIndexOf(item).value();
views::View* left_column =
std::prev(base::ranges::find(children(), column))->get();
return ItemInColumnWithIndexClosestTo(left_column, item_index);
}
views::View* PickerImageItemGridView::GetItemRightOf(views::View* item) {
views::View* column = GetColumnContaining(item);
if (!column || column == children().back()) {
return nullptr;
}
// Prefer to return the item with the same index in the column to the right,
// since this will probably be at a similar height to `item` (at least in
// usual scenarios where the grid items all have similar dimensions).
const size_t item_index = column->GetIndexOf(item).value();
views::View* right_column =
std::next(base::ranges::find(children(), column))->get();
return ItemInColumnWithIndexClosestTo(right_column, item_index);
}
bool PickerImageItemGridView::ContainsItem(views::View* item) {
return Contains(item);
}
PickerImageItemView* PickerImageItemGridView::AddImageItem(
std::unique_ptr<PickerImageItemView> image_item) {
// TODO: b/338142316 - Wrap the image item in a View and give it a correct
// accessible role.
image_item->SetImageSizeFromWidth(GetImageGridColumnWidth(grid_width_));
views::View* shortest_column =
base::ranges::min(children(),
/*comp=*/base::ranges::less(),
/*proj=*/[](const views::View* v) {
return v->GetPreferredSize().height();
});
return shortest_column->AddChildView(std::move(image_item));
}
views::View* PickerImageItemGridView::GetColumnContaining(views::View* item) {
views::View* column = item->parent();
return column && column->parent() == this ? column : nullptr;
}
BEGIN_METADATA(PickerImageItemGridView)
END_METADATA
} // namespace ash