Chromium Code Reviews| Index: ash/common/system/tray/header_list_scroll_view.cc |
| diff --git a/ash/common/system/tray/header_list_scroll_view.cc b/ash/common/system/tray/header_list_scroll_view.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f3cb12905339031e5b40f1b2d6dbca1ea15a2c26 |
| --- /dev/null |
| +++ b/ash/common/system/tray/header_list_scroll_view.cc |
| @@ -0,0 +1,181 @@ |
| +// Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "ash/common/system/tray/header_list_scroll_view.h" |
| +#include "ui/views/border.h" |
| +#include "ui/views/layout/box_layout.h" |
| + |
| +namespace ash { |
| +namespace { |
| + |
| +const int kHeaderRowSeparatorThickness = 1; |
| +const SkColor kHeaderRowSeparatorColor = SkColorSetA(SK_ColorBLACK, 0x1F); |
| + |
| +} // namespace |
| + |
| +// Variation of a BoxLayout that keeps header rows at the end of children_ in |
| +// order to stack them above the other rows while maintaining the order (group |
| +// headers first, the rest of the group rows in their original order). |
| +// To use this layout tag header rows with set_id(kHeaderRowId) and tag groups |
| +// including the header row with SetGroup(group_id). |
| +class HeaderListScrollView::HeaderListLayout : public views::BoxLayout { |
| + public: |
| + class Header { |
| + public: |
| + explicit Header(views::View* header) : view(header) {} |
| + bool operator==(views::View* other) { return view == other; } |
| + bool operator==(int group_id) { return view->GetGroup() == group_id; } |
|
sadrul
2016/11/01 18:50:16
These operator overloads don't actually seem usefu
varkha
2016/11/02 02:03:21
Done. Used lambda instead as you have suggested.
|
| + |
| + views::View* view; |
| + }; |
| + |
| + HeaderListLayout() : views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1) {} |
| + ~HeaderListLayout() override {} |
| + const std::vector<Header>& headers() const { return headers_; } |
| + |
| + void SetHeaderView(views::View* child) { |
|
sadrul
2016/11/01 18:50:16
AddHeaderView
varkha
2016/11/02 02:03:21
Acknowledged.
|
| + auto header = std::find(headers_.begin(), headers_.end(), child); |
| + if (header == headers_.end()) |
| + headers_.push_back(Header(child)); |
| + } |
| + |
| + protected: |
| + // views::BoxLayout: |
| + void LayoutChildren(views::View* host, |
| + const gfx::Rect& child_area, |
| + int main_free_space, |
| + int flex_sum, |
| + int main_position, |
| + int current_flex, |
| + int* total_padding) override { |
| + // Ensure that the header rows are stacked at the end of |host|'s children. |
| + for (const auto& header : headers_) |
| + host->ReorderChildView(header.view, -1); |
|
sadrul
2016/11/01 18:50:16
Do you need to do this for all layout? Can this be
varkha
2016/11/02 02:03:21
Acknowledged.
|
| + |
| + auto headers(headers_); |
| + int group_id = -1; |
| + for (int i = 0, count = host->child_count(); i < count; ++i) { |
| + views::View* child = host->child_at(i); |
| + if (!child->visible() || |
| + headers_.end() != |
| + std::find(headers_.begin(), headers_.end(), child)) { |
| + continue; |
| + } |
| + if (child->GetGroup() != group_id) { |
| + // Find a header for the new group and place it above the group. |
| + group_id = child->GetGroup(); |
| + auto header = std::find(headers.begin(), headers.end(), group_id); |
| + if (header != headers.end()) { |
| + LayoutChild(header->view, child_area, main_free_space, flex_sum, |
| + &main_position, ¤t_flex, total_padding); |
| + headers.erase(header); |
| + } |
| + } |
| + LayoutChild(child, child_area, main_free_space, flex_sum, &main_position, |
| + ¤t_flex, total_padding); |
| + } |
| + // In the end layout unclaimed headers. |
| + for (const auto& header : headers) { |
| + LayoutChild(header.view, child_area, main_free_space, flex_sum, |
| + &main_position, ¤t_flex, total_padding); |
| + } |
| + } |
| + |
| + void ViewRemoved(views::View* host, views::View* view) override { |
| + views::BoxLayout::ViewRemoved(host, view); |
| + auto header = std::find(headers_.begin(), headers_.end(), view); |
| + if (header != headers_.end()) |
| + headers_.erase(header); |
| + } |
| + |
| + private: |
| + // Header child views that stick to the top of visible viewport when scrolled. |
| + std::vector<Header> headers_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(HeaderListLayout); |
| +}; |
| + |
| +HeaderListScrollView::HeaderListScrollView() { |
| + header_list_layout_ = new HeaderListLayout(); |
| + SetLayoutManager(header_list_layout_); |
| +} |
| +HeaderListScrollView::~HeaderListScrollView() {} |
| + |
| +// Sets |view| as a sticky header view for all items with the same group. |
| +void HeaderListScrollView::SetHeaderView(views::View* child) { |
| + header_list_layout_->SetHeaderView(child); |
| +} |
| + |
| +void HeaderListScrollView::Layout() { |
| + views::View::Layout(); |
| + headers_.clear(); |
| + for (auto& header : header_list_layout_->headers()) |
| + headers_.push_back(Header(header.view)); |
| + ScrollChildren(); |
| +} |
| + |
| +void HeaderListScrollView::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| + ScrollChildren(); |
| +} |
| + |
| +void HeaderListScrollView::ViewHierarchyChanged( |
| + const ViewHierarchyChangedDetails& details) { |
| + if (!details.is_add && details.parent == this) { |
| + auto header = std::find(headers_.begin(), headers_.end(), details.child); |
| + if (header != headers_.end()) |
| + headers_.erase(header); |
| + } |
| +} |
| + |
| +const char* HeaderListScrollView::GetClassName() const { |
| + return "HeaderListScrollView"; |
| +} |
| + |
| +// static |
| +void HeaderListScrollView::ShowHeaderSticky(views::View* header, |
| + bool show_sticky) { |
| + if (show_sticky) { |
| + header->SetBorder(views::Border::CreateSolidSidedBorder( |
| + 0, 0, kHeaderRowSeparatorThickness, 0, kHeaderRowSeparatorColor)); |
| + } else { |
| + header->SetBorder(views::Border::CreateSolidSidedBorder( |
| + kHeaderRowSeparatorThickness, 0, 0, 0, kHeaderRowSeparatorColor)); |
| + } |
| +} |
| + |
| +// Adjusts y-position of header rows allowing one or two rows to stick to the |
| +// top of the visible viewport. |
| +void HeaderListScrollView::ScrollChildren() { |
| + const int scroll_offset = -bounds().y(); |
| + Header* previous_header = nullptr; |
| + for (auto& header : headers_) { |
| + gfx::Rect header_bounds = header.view->bounds(); |
| + if (scroll_offset > header.offset) { |
| + header_bounds.set_y(scroll_offset); |
| + header.view->SetBoundsRect(header_bounds); |
| + ShowHeaderSticky(header.view, true); |
| + header.view->Layout(); |
| + header.view->SchedulePaint(); |
| + if (previous_header) { |
| + header_bounds = previous_header->view->bounds(); |
| + header_bounds.set_y(previous_header->offset); |
| + previous_header->view->SetBoundsRect(header_bounds); |
| + ShowHeaderSticky(previous_header->view, false); |
| + } |
| + previous_header = &header; |
| + } else if (previous_header && |
| + header_bounds.y() < previous_header->view->bounds().bottom()) { |
| + gfx::Rect previous_header_bounds = previous_header->view->bounds(); |
| + previous_header_bounds.set_y(header_bounds.y() - |
| + previous_header->view->bounds().height()); |
| + previous_header->view->SetBoundsRect(previous_header_bounds); |
| + ShowHeaderSticky(previous_header->view, false); |
| + ShowHeaderSticky(header.view, false); |
| + } else { |
| + ShowHeaderSticky(header.view, false); |
| + } |
| + } |
| +} |
| + |
| +} // namespace ash |