Chromium Code Reviews| Index: ash/common/system/tray/tray_details_view.cc |
| diff --git a/ash/common/system/tray/tray_details_view.cc b/ash/common/system/tray/tray_details_view.cc |
| index 70ff14fd6b4feb2838251819af30b34515ef2cf2..08e686ba57abe5762741928bbcdca2b1806e22b6 100644 |
| --- a/ash/common/system/tray/tray_details_view.cc |
| +++ b/ash/common/system/tray/tray_details_view.cc |
| @@ -16,10 +16,160 @@ |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| +#include "ui/views/view_targeter.h" |
| +#include "ui/views/view_targeter_delegate.h" |
| namespace ash { |
| namespace { |
| +const int kHeaderRowSeparatorThickness = 1; |
| +const SkColor kHeaderRowSeparatorColor = SkColorSetA(SK_ColorBLACK, 0x1F); |
| + |
| +// A view that is used as ScrollView contents. It supports designating some of |
| +// the children as sticky header rows. The sticky header rows are not scrolled |
| +// above the top of the visible viewport until the next one "pushes" it up and |
| +// are painted above other children. To indicate that a child is a sticky header |
| +// row use set_id(kHeaderRowId). |
| +class ScrollContentsView : public views::View, |
| + public views::ViewTargeterDelegate { |
| + public: |
| + ScrollContentsView() { |
| + SetEventTargeter(base::MakeUnique<views::ViewTargeter>(this)); |
| + SetLayoutManager( |
| + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); |
| + } |
| + ~ScrollContentsView() override {} |
| + |
| + protected: |
| + // views::View: |
| + void OnBoundsChanged(const gfx::Rect& previous_bounds) override { |
| + PositionHeaderRows(); |
| + } |
| + |
| + void PaintChildren(const ui::PaintContext& context) override { |
| + for (int i = 0; i < child_count(); ++i) { |
| + if (child_at(i)->id() != kHeaderRowId && !child_at(i)->layer()) |
| + child_at(i)->Paint(context); |
| + } |
| + // Paint header rows above other children in Z-order. |
| + for (auto& header : headers_) { |
| + if (!header.view()->layer()) |
| + header.view()->Paint(context); |
| + } |
| + } |
| + |
| + void Layout() override { |
| + views::View::Layout(); |
| + headers_.clear(); |
| + for (int i = 0; i < child_count(); ++i) { |
| + views::View* view = child_at(i); |
| + if (view->id() == kHeaderRowId) |
| + headers_.push_back(Header(view)); |
| + } |
| + PositionHeaderRows(); |
| + } |
| + |
| + void ViewHierarchyChanged( |
| + const ViewHierarchyChangedDetails& details) override { |
| + if (!details.is_add && details.parent == this) { |
| + headers_.erase(std::remove_if(headers_.begin(), headers_.end(), |
| + [details](const Header& header) { |
| + return header.view() == details.child; |
| + }), |
| + headers_.end()); |
|
sadrul
2016/11/04 15:03:03
There should only be one header that matches the c
varkha
2016/11/04 18:18:49
Done.
varkha
2016/11/07 03:27:45
Looked into this some more. While you are right th
|
| + } |
| + } |
| + |
| + View* TargetForRect(View* root, const gfx::Rect& rect) override { |
|
sadrul
2016/11/04 15:03:03
// views::ViewTargeterDelegate:
varkha
2016/11/04 18:18:49
Done.
|
| + // Give header rows first dibs on events. |
| + for (auto& header : headers_) { |
| + views::View* view = header.view(); |
| + gfx::Rect local_to_header = rect; |
| + local_to_header.Offset(-view->x(), -view->y()); |
| + if (ViewTargeterDelegate::DoesIntersectRect(view, local_to_header)) |
| + return ViewTargeterDelegate::TargetForRect(view, local_to_header); |
| + } |
| + return ViewTargeterDelegate::TargetForRect(root, rect); |
| + } |
| + |
| + private: |
| + // A structure that keeps the original offset of each header between the |
| + // calls to Layout() to allow keeping track of which view should be sticky. |
| + class Header { |
| + public: |
| + Header(views::View* header) |
|
sadrul
2016/11/04 15:03:03
explicit
varkha
2016/11/04 18:18:49
Done.
|
| + : view_(header), offset_(header->y()), sticky_(true) { |
| + DecorateAsSticky(false); |
| + } |
| + |
| + // Sets decorations on a header row to indicate whether it is |sticky|. |
| + void DecorateAsSticky(bool sticky) { |
| + if (sticky_ == sticky) |
| + return; |
| + sticky_ = sticky; |
| + if (sticky) { |
| + view_->SetBorder(views::Border::CreateSolidSidedBorder( |
| + 0, 0, kHeaderRowSeparatorThickness, 0, kHeaderRowSeparatorColor)); |
| + } else { |
| + view_->SetBorder(views::Border::CreateSolidSidedBorder( |
| + kHeaderRowSeparatorThickness, 0, 0, 0, kHeaderRowSeparatorColor)); |
| + } |
| + } |
| + |
| + const views::View* view() const { return view_; } |
| + views::View* view() { |
| + return const_cast<views::View*>(const_cast<const Header*>(this)->view()); |
|
Evan Stade
2016/11/04 15:53:46
p.s. this seems crazily verbose instead of just 'r
varkha
2016/11/04 18:18:49
Done.
|
| + } |
| + int offset() const { return offset_; } |
| + |
| + private: |
| + // A header View that can be decorated as sticky. |
| + views::View* view_; |
| + |
| + // Offset from the top of ScrollContentsView to |view|'s original vertical |
| + // position. |
| + int offset_; |
| + |
| + // True if the header is decorated as sticky with a shadow below. |
| + bool sticky_; |
|
sadrul
2016/11/04 15:03:03
DISALLOW_COPY_AND_ASSIGN
varkha
2016/11/04 18:18:49
This structure is used in a value array so I think
Evan Stade
2016/11/04 19:18:45
So use emplace_back instead of push_back?
varkha
2016/11/07 03:27:45
I will use emplace_back() but that still relies on
|
| + }; |
| + |
| + // Adjusts y-position of header rows allowing one or two rows to stick to the |
| + // top of the visible viewport. |
| + void PositionHeaderRows() { |
| + const int scroll_offset = -y(); |
| + auto previous_header = headers_.rend(); |
| + for (auto header = headers_.rbegin(); header != headers_.rend(); ++header) { |
|
sadrul
2016/11/04 15:03:03
for (auto& header : base::Reversed(headers_)) {
varkha
2016/11/04 18:18:49
Done.
|
| + if (header->offset() >= scroll_offset) { |
| + header->DecorateAsSticky(false); |
| + previous_header = header; |
| + continue; |
| + } |
| + views::View* header_view = header->view(); |
| + if (previous_header != headers_.rend() && |
| + previous_header->view()->y() < |
| + scroll_offset + header_view->height()) { |
| + // Lower header displacing the header above. |
| + header_view->SetY(previous_header->view()->y() - header_view->height()); |
| + header->DecorateAsSticky(false); |
| + previous_header->DecorateAsSticky(false); |
| + } else { |
| + // A header becomes sticky. |
| + header_view->SetY(scroll_offset); |
| + header->DecorateAsSticky(true); |
| + header_view->Layout(); |
| + header_view->SchedulePaint(); |
| + } |
| + break; |
| + } |
| + } |
| + |
| + // Header child views that stick to the top of visible viewport when scrolled. |
| + std::vector<Header> headers_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ScrollContentsView); |
| +}; |
| + |
| // Constants for the title row in material design. |
| const int kTitleRowVerticalPadding = 4; |
| const int kTitleRowSeparatorBorderHeight = 1; |
| @@ -65,8 +215,6 @@ class TitleRowSeparatorLayout : public views::LayoutManager { |
| } |
| }; |
| -} // namespace |
| - |
| class ScrollSeparator : public views::View { |
| public: |
| ScrollSeparator() {} |
| @@ -74,7 +222,7 @@ class ScrollSeparator : public views::View { |
| ~ScrollSeparator() override {} |
| private: |
| - // Overriden from views::View. |
| + // views::View: |
| void OnPaint(gfx::Canvas* canvas) override { |
| canvas->FillRect(gfx::Rect(0, height() / 2, width(), 1), kBorderLightColor); |
| } |
| @@ -85,6 +233,8 @@ class ScrollSeparator : public views::View { |
| DISALLOW_COPY_AND_ASSIGN(ScrollSeparator); |
| }; |
| +} // namespace |
| + |
| class ScrollBorder : public views::Border { |
| public: |
| ScrollBorder() {} |
| @@ -93,7 +243,7 @@ class ScrollBorder : public views::Border { |
| void set_visible(bool visible) { visible_ = visible; } |
| private: |
| - // Overridden from views::Border. |
| + // views::Border: |
| void Paint(const views::View& view, gfx::Canvas* canvas) override { |
| if (!visible_) |
| return; |
| @@ -105,7 +255,7 @@ class ScrollBorder : public views::Border { |
| gfx::Size GetMinimumSize() const override { return gfx::Size(0, 1); } |
| - bool visible_; |
| + bool visible_ = false; |
| DISALLOW_COPY_AND_ASSIGN(ScrollBorder); |
| }; |
| @@ -161,7 +311,7 @@ void TrayDetailsView::CreateTitleRow(int string_id) { |
| title_row_separator_->SetLayoutManager(new TitleRowSeparatorLayout); |
| views::Separator* separator = |
| new views::Separator(views::Separator::HORIZONTAL); |
| - separator->SetColor(ash::kTitleRowSeparatorBorderColor); |
| + separator->SetColor(kTitleRowSeparatorBorderColor); |
| separator->SetPreferredSize(kTitleRowSeparatorBorderHeight); |
| separator->SetBorder(views::Border::CreateEmptyBorder( |
| kTitleRowSeparatorHeight - kTitleRowSeparatorBorderHeight, 0, 0, 0)); |
| @@ -181,9 +331,7 @@ void TrayDetailsView::CreateTitleRow(int string_id) { |
| void TrayDetailsView::CreateScrollableList() { |
| DCHECK(!scroller_); |
| - scroll_content_ = new views::View; |
| - scroll_content_->SetLayoutManager( |
| - new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1)); |
| + scroll_content_ = new ScrollContentsView(); |
| scroller_ = new FixedSizedScrollView; |
| scroller_->SetContentsView(scroll_content_); |