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 |