Commit cd991305 authored by varkha's avatar varkha Committed by Commit bot

[ash-md] Stacks child layers properly for sticky header rows

The CL implements sticky section header rows in VPN page. Care is taken
to stack the rows with child controls that have layers below the sticky
section header rows.

BUG=664244
TEST=Run with --material-design-ink-drop-animation-speed=slow
     Touch a Wi-Fi connection row and while ripple is growing
     drag-scroll the list of connections up.
     Verify that the ripple is stacked below the Wi-Fi header row.

Review-Url: https://codereview.chromium.org/2557333003
Cr-Commit-Position: refs/heads/master@{#438689}
parent 9807edde
......@@ -30,7 +30,6 @@
#include "ui/gfx/font.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icons_public.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
......
......@@ -113,21 +113,11 @@ class VPNListProviderEntryMd : public views::ButtonListener,
const std::string& name,
int button_accessible_name_id)
: parent_(parent) {
// TODO(varkha): Make this a sticky section header.
TrayPopupUtils::ConfigureAsStickyHeader(this);
SetLayoutManager(new views::FillLayout);
TriView* tri_view = TrayPopupUtils::CreateSubHeaderRowView();
AddChildView(tri_view);
// Sets up the border. When the provider header is the first item in the
// list (i.e. when the list was empty before adding the provider row) there
// is already |kMenuSeparatorVerticalPadding| padding at the top of the
// scroll contents, so only add that padding when the list was not |empty|.
// TODO(varkha): Delete this special handling when we allow the header to be
// sticky and just use ConfigureAsStickyHeader() instead.
tri_view->SetBorder(
views::CreateEmptyBorder(top_item ? 0 : kMenuSeparatorVerticalPadding,
0, kMenuSeparatorVerticalPadding, 0));
views::Label* label = TrayPopupUtils::CreateDefaultLabel();
TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::SUB_HEADER);
style.SetupLabel(label);
......@@ -402,12 +392,6 @@ void VPNListView::Update() {
->GetVisibleNetworkListByType(chromeos::NetworkTypePattern::VPN(),
&networks);
if (!networks.empty() && IsConnectedOrConnecting(networks.front())) {
// If there is a connected or connecting network, show that network first.
AddNetwork(networks.front());
networks.erase(networks.begin());
}
// Show all VPN providers and all networks that are currently disconnected.
AddProvidersAndNetworks(networks);
......
......@@ -100,6 +100,26 @@ class ScrollContentsView : public views::View,
PositionHeaderRows();
}
void ReorderChildLayers(ui::Layer* parent_layer) override {
views::View::ReorderChildLayers(parent_layer);
ui::Layer* topmost_layer = TopmostLayer(this, parent_layer);
if (!topmost_layer)
return;
// Keep the sticky headers with layers above the rest of the children's
// layers. Make sure to avoid changing the stacking order of the layers that
// are children of |parent_layer| but do not belong to the same parent view.
// Note: this assumes that all views that have id=VIEW_ID_STICKY_HEADER have
// a layer.
for (int i = child_count() - 1; i >= 0; --i) {
View* child = child_at(i);
if (child->id() == VIEW_ID_STICKY_HEADER &&
child->layer() != topmost_layer) {
parent_layer->StackAbove(child->layer(), topmost_layer);
}
}
}
void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) override {
if (!details.is_add && details.parent == this) {
......@@ -169,24 +189,31 @@ class ScrollContentsView : public views::View,
Header* previous_header = nullptr;
for (auto& header : base::Reversed(headers_)) {
views::View* header_view = header.view;
header.draw_separator_below = false;
bool draw_separator_below = false;
if (header.natural_offset >= scroll_offset) {
previous_header = &header;
header_view->SetY(header.natural_offset);
continue;
}
if (previous_header &&
previous_header->view->y() <= scroll_offset + header_view->height()) {
// Lower header displacing the header above.
header.draw_separator_below = true;
header_view->SetY(previous_header->view->y() - header_view->height());
} else {
// A header becomes sticky.
header_view->SetY(scroll_offset);
header_view->Layout();
header_view->SchedulePaint();
if (previous_header &&
previous_header->view->y() <=
scroll_offset + header_view->height()) {
// Lower header displacing the header above.
draw_separator_below = true;
header_view->SetY(previous_header->view->y() - header_view->height());
} else {
// A header becomes sticky.
header_view->SetY(scroll_offset);
header_view->Layout();
header_view->SchedulePaint();
}
}
if (header.draw_separator_below != draw_separator_below) {
header.draw_separator_below = draw_separator_below;
TrayPopupUtils::ShowStickyHeaderSeparator(header_view,
draw_separator_below);
}
break;
if (header.natural_offset < scroll_offset)
break;
}
}
......@@ -197,22 +224,11 @@ class ScrollContentsView : public views::View,
bool PaintDelineation(const Header& header, const ui::PaintContext& context) {
const View* view = header.view;
// If the header is where it normally belongs, draw nothing.
if (view->y() == header.natural_offset)
// If the header is where it normally belongs or If the header is pushed by
// a header directly below it, draw nothing.
if (view->y() == header.natural_offset || header.draw_separator_below)
return false;
// If the header is pushed by a header directly below it, draw a separator.
if (header.draw_separator_below) {
// TODO(estade): look better at 1.5x scale.
ui::PaintRecorder recorder(context, size());
gfx::Canvas* canvas = recorder.canvas();
gfx::Rect separator = view->bounds();
separator.set_y(separator.bottom() - kSeparatorWidth);
separator.set_height(kSeparatorWidth);
canvas->FillRect(separator, kSeparatorColor);
return false;
}
// Otherwise, draw a shadow below.
DrawShadow(context,
gfx::Rect(0, 0, view->width(), view->bounds().bottom()));
......@@ -234,6 +250,25 @@ class ScrollContentsView : public views::View,
canvas->DrawRect(shadowed_area, paint);
}
// Recursively iterates through children to return the child layer that is
// stacked at the top. Only considers layers that are direct |parent_layer|'s
// children.
ui::Layer* TopmostLayer(views::View* view, ui::Layer* parent_layer) {
DCHECK(parent_layer);
// Iterate backwards through the children to find the topmost layer.
for (int i = view->child_count() - 1; i >= 0; --i) {
views::View* child = view->child_at(i);
ui::Layer* layer = TopmostLayer(child, parent_layer);
if (layer)
return layer;
}
if (view->layer() && view->layer() != parent_layer &&
view->layer()->parent() == parent_layer) {
return view->layer();
}
return nullptr;
}
views::BoxLayout* box_layout_;
// Header child views that stick to the top of visible viewport when scrolled.
......
......@@ -4,6 +4,7 @@
#include "ash/common/system/tray/tray_details_view.h"
#include "ash/common/ash_view_ids.h"
#include "ash/common/material_design/material_design_controller.h"
#include "ash/common/system/tray/hover_highlight_view.h"
#include "ash/common/system/tray/special_popup_row.h"
......@@ -60,6 +61,8 @@ class TestDetailsView : public TrayDetailsView {
title_row()->AddViewToRowNonMd(tray_popup_header_button_, true);
}
void CreateScrollerViews() { CreateScrollableList(); }
private:
TrayPopupHeaderButton* tray_popup_header_button_;
......@@ -312,5 +315,45 @@ TEST_F(TrayDetailsViewTest, TrayPopupHeaderButtonMouseHoverFeedback) {
EXPECT_TRUE(button->background());
}
TEST_F(TrayDetailsViewTest, ScrollContentsTest) {
SystemTray* tray = GetPrimarySystemTray();
TestItem* test_item = new TestItem;
tray->AddTrayItem(test_item);
tray->ShowDefaultView(BUBBLE_CREATE_NEW);
RunAllPendingInMessageLoop();
tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING);
RunAllPendingInMessageLoop();
test_item->detailed_view()->CreateScrollerViews();
test_item->detailed_view()->scroll_content()->SetPaintToLayer(true);
views::View* view1 = new views::View();
test_item->detailed_view()->scroll_content()->AddChildView(view1);
views::View* view2 = new views::View();
view2->SetPaintToLayer(true);
test_item->detailed_view()->scroll_content()->AddChildView(view2);
views::View* view3 = new views::View();
view3->SetPaintToLayer(true);
test_item->detailed_view()->scroll_content()->AddChildView(view3);
// Child layers should have same order as the child views.
const std::vector<ui::Layer*>& layers =
test_item->detailed_view()->scroll_content()->layer()->children();
EXPECT_EQ(2u, layers.size());
EXPECT_EQ(view2->layer(), layers[0]);
EXPECT_EQ(view3->layer(), layers[1]);
// Mark |view2| as sticky and add one more child (which will reorder layers).
view2->set_id(VIEW_ID_STICKY_HEADER);
views::View* view4 = new views::View();
view4->SetPaintToLayer(true);
test_item->detailed_view()->scroll_content()->AddChildView(view4);
// Sticky header layer should be above the last child's layer.
EXPECT_EQ(3u, layers.size());
EXPECT_EQ(view3->layer(), layers[0]);
EXPECT_EQ(view4->layer(), layers[1]);
EXPECT_EQ(view2->layer(), layers[2]);
}
} // namespace test
} // namespace ash
......@@ -287,6 +287,23 @@ void TrayPopupUtils::ConfigureAsStickyHeader(views::View* view) {
views::Background::CreateSolidBackground(kBackgroundColor));
view->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
view->SetPaintToLayer(true);
view->layer()->SetFillsBoundsOpaquely(false);
}
void TrayPopupUtils::ShowStickyHeaderSeparator(views::View* view,
bool show_separator) {
if (show_separator) {
view->SetBorder(views::CreatePaddedBorder(
views::CreateSolidSidedBorder(0, 0, kSeparatorWidth, 0,
kHorizontalSeparatorColor),
gfx::Insets(kMenuSeparatorVerticalPadding, 0,
kMenuSeparatorVerticalPadding - kSeparatorWidth, 0)));
} else {
view->SetBorder(views::CreateEmptyBorder(
gfx::Insets(kMenuSeparatorVerticalPadding, 0)));
}
view->SchedulePaint();
}
void TrayPopupUtils::ConfigureContainer(TriView::Container container,
......
......@@ -114,6 +114,9 @@ class TrayPopupUtils {
// Sets up |view| to be a sticky header in a tray detail scroll view.
static void ConfigureAsStickyHeader(views::View* view);
// Configures a |view| to have a visible separator below.
static void ShowStickyHeaderSeparator(views::View* view, bool show_separator);
// Configures |container_view| just like CreateDefaultRowView() would
// configure |container| on its returned TriView. To be used when mutliple
// targetable areas are required within a single row.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment