Commit 6c4ce51e authored by michaelpg's avatar michaelpg Committed by Commit bot

MD Settings: fix collapse animation once and for all

Makes the collapsing card position: absolute (instead of fixed).  Anchoring
it to the section makes it actually follow the section, so it always moves
toward the right position even if the containter's scroll/height/size changes.
Should reduce jarring jumps at the end of transitions.

Also moves the style changes into Web Animations instead of inline. This makes
the animations fire-and-forget. Transition clean up is largely automatic, there
are no styles to (forget to) remove (at the wrong time). If a transition is
buggy, we're still likely to end up in the right place.

This fixes several animation bugs (but likely introduces new ones).

BUG=589681, 621245, 622172
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2230123002
Cr-Commit-Position: refs/heads/master@{#414289}
parent 4656783b
......@@ -59,13 +59,14 @@
</div>
<template is="dom-if" if="[[showPages_.basic]]">
<settings-basic-page prefs="{{prefs}}"
page-visibility="[[pageVisibility]]">
page-visibility="[[pageVisibility]]"
on-subpage-expand="onSubpageExpand_">
</settings-basic-page>
</template>
<template is="dom-if"
if="[[showAdvancedSettings_(pageVisibility.advancedSettings)]]">
<template is="dom-if" if="[[showAdvancedToggle_(
showPages_.basic, inSubpage_, previousShowPages_)]]">
showPages_.basic, hasExpandedSection_, previousShowPages_)]]">
<div id="toggleSpacer"></div>
<div id="toggleContainer">
<div id="advancedToggle" on-tap="toggleAdvancedPage_">
......@@ -76,7 +77,8 @@
</template>
<template is="dom-if" if="[[showPages_.advanced]]">
<settings-advanced-page prefs="{{prefs}}"
page-visibility="[[pageVisibility]]">
page-visibility="[[pageVisibility]]"
on-subpage-expand="onSubpageExpand_">
</settings-advanced-page>
</template>
</template>
......
......@@ -31,8 +31,12 @@ Polymer({
value: false,
},
/** @private */
inSubpage_: Boolean,
/**
* True if a section is fully expanded to hide other sections beneath it.
* Not true otherwise (even while animating a section open/closed).
* @private
*/
hasExpandedSection_: Boolean,
/** @private */
overscroll_: {
......@@ -90,6 +94,9 @@ Polymer({
this.advancedToggleExpanded_ = e.detail;
this.currentRouteChanged(settings.getCurrentRoute());
}.bind(this));
var currentRoute = settings.getCurrentRoute();
this.hasExpandedSection_ = currentRoute && currentRoute.isSubpage();
},
/** @private */
......@@ -136,22 +143,42 @@ Polymer({
*/
showAdvancedToggle_: function() {
var inSearchMode = !!this.previousShowPages_;
return this.showPages_.basic && !this.inSubpage_ && !inSearchMode;
return !inSearchMode && this.showPages_.basic && !this.hasExpandedSection_;
},
/** @protected */
currentRouteChanged: function(newRoute) {
this.inSubpage_ = newRoute.isSubpage();
this.style.height = this.inSubpage_ ? '100%' : '';
// When the route changes from a sub-page to the main page, immediately
// update hasExpandedSection_ to unhide the other sections.
if (!newRoute.isSubpage())
this.hasExpandedSection_ = false;
this.updatePagesShown_();
},
if (settings.Route.ABOUT.contains(newRoute)) {
/** @private */
onSubpageExpand_: function() {
// The subpage finished expanding fully. Hide pages other than the current
// section's parent page.
this.hasExpandedSection_ = true;
this.updatePagesShown_();
},
/**
* Updates the hidden state of the about, basic and advanced pages, based on
* the current route and the Advanced toggle state.
* @private
*/
updatePagesShown_: function() {
var currentRoute = settings.getCurrentRoute();
if (settings.Route.ABOUT.contains(currentRoute)) {
this.showPages_ = {about: true, basic: false, advanced: false};
} else {
this.showPages_ = {
about: false,
basic: settings.Route.BASIC.contains(newRoute) || !this.inSubpage_,
advanced: settings.Route.ADVANCED.contains(newRoute) ||
(!this.inSubpage_ && this.advancedToggleExpanded_),
basic: settings.Route.BASIC.contains(currentRoute) ||
!this.hasExpandedSection_,
advanced: settings.Route.ADVANCED.contains(currentRoute) ||
(!this.hasExpandedSection_ && this.advancedToggleExpanded_),
};
if (this.showPages_.advanced) {
......
......@@ -10,6 +10,7 @@
'../compiled_resources2.gyp:route',
'settings_section',
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:assert',
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:util',
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......
<link rel="import" href="chrome://resources/html/assert.html">
<link rel="import" href="/animation/animation.html">
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="/route.html">
<script src="/settings_page/main_page_behavior.js"></script>
......@@ -9,7 +9,12 @@
* @param {!Function} readyCallback
*/
function doWhenReady(readyTest, readyCallback) {
// TODO(dschuyler): Determine whether this hack can be removed.
if (readyTest()) {
readyCallback();
return;
}
// TODO(michaelpg): Remove this hack.
// See also: https://github.com/Polymer/polymer/issues/3629
var intervalId = setInterval(function() {
if (readyTest()) {
......@@ -82,10 +87,7 @@ var MainPageBehaviorImpl = {
// If the section shouldn't be expanded, collapse it.
if (!currentRoute.isSubpage() || expandedSection != currentSection) {
promise = this.collapseSection_(expandedSection);
// Scroll to the collapsed section. TODO(michaelpg): This can look weird
// because the collapse we just scheduled calculated its end target
// based on the current scroll position. This bug existed before, and is
// fixed in the next patch by making the card position: absolute.
// Scroll to the collapsed section.
if (currentSection)
currentSection.scrollIntoView();
}
......@@ -149,7 +151,13 @@ var MainPageBehaviorImpl = {
*/
expandSection_: function(section) {
assert(this.scroller);
assert(section.canAnimateExpand());
if (!section.canAnimateExpand()) {
// Try to wait for the section to be created.
return new Promise(function(resolve, reject) {
setTimeout(resolve);
});
}
// Save the scroller position before freezing it.
this.origScrollTop_ = this.scroller.scrollTop;
......@@ -159,20 +167,26 @@ var MainPageBehaviorImpl = {
section.setFrozen(true);
this.currentAnimation_ = section.animateExpand(this.scroller);
var promise = this.currentAnimation_ ?
this.currentAnimation_.finished : Promise.resolve();
var finished;
return promise.then(function() {
this.scroller.scrollTop = 0;
return this.currentAnimation_.finished.then(function() {
// Hide other sections and scroll to the top of the subpage.
this.classList.add('showing-subpage');
this.toggleOtherSectionsHidden_(section.section, true);
this.scroller.scrollTop = 0;
section.setFrozen(false);
// Notify that the page is fully expanded.
this.fire('subpage-expand');
finished = true;
}.bind(this), function() {
// The animation was canceled; restore the section.
// The animation was canceled; restore the section and scroll position.
section.setFrozen(false);
this.scroller.scrollTop = this.origScrollTop_;
finished = false;
}).then(function() {
section.cleanUpAnimateExpand(finished);
}.bind(this)).then(function() {
this.toggleScrolling_(true);
this.currentAnimation_ = null;
}.bind(this));
......@@ -182,32 +196,65 @@ var MainPageBehaviorImpl = {
* Animates the card in |section|, collapsing it back into its section.
* @param {!SettingsSectionElement} section
* @return {!Promise} Resolved when the transition is finished or canceled.
* @private
*/
collapseSection_: function(section) {
assert(this.scroller);
assert(section.canAnimateCollapse());
assert(section.classList.contains('expanded'));
this.toggleOtherSectionsHidden_(section.section, false);
this.toggleScrolling_(false);
var canAnimateCollapse = section.canAnimateCollapse();
if (canAnimateCollapse) {
this.toggleScrolling_(false);
// Do the initial collapse setup, which takes the section out of the flow,
// before showing everything.
section.setUpAnimateCollapse(this.scroller);
} else {
section.classList.remove('expanded');
}
this.currentAnimation_ =
section.animateCollapse(this.scroller, this.origScrollTop_);
var promise = this.currentAnimation_ ?
this.currentAnimation_.finished : Promise.resolve();
// Show everything.
this.toggleOtherSectionsHidden_(section.section, false);
this.classList.remove('showing-subpage');
return promise.then(function() {
section.cleanUpAnimateCollapse(true);
}, function() {
section.cleanUpAnimateCollapse(false);
}).then(function() {
if (!canAnimateCollapse) {
// Finish by restoring the section into the page.
section.setFrozen(false);
section.classList.remove('collapsing');
this.toggleScrolling_(true);
this.currentAnimation_ = null;
return Promise.resolve();
}
// Play the actual collapse animation.
return new Promise(function(resolve, reject) {
// Wait for the other sections to show up so we can scroll properly.
setTimeout(function() {
var newSection = settings.getCurrentRoute().section &&
this.getSection(settings.getCurrentRoute().section);
// Scroll to the section if indicated by the route. TODO(michaelpg): Is
// this the right behavior, or should we return to the previous scroll
// position?
if (newSection)
newSection.scrollIntoView();
else
this.scroller.scrollTop = this.origScrollTop_;
this.currentAnimation_ = section.animateCollapse(
/** @type {!HTMLElement} */(this.scroller));
this.currentAnimation_.finished.catch(function() {
// The collapse was canceled, so the page is showing a subpage still.
this.fire('subpage-expand');
}.bind(this)).then(function() {
// Clean up after the animation succeeds or cancels.
section.setFrozen(false);
section.classList.remove('collapsing');
this.toggleScrolling_(true);
this.currentAnimation_ = null;
resolve();
}.bind(this));
}.bind(this));
}.bind(this));
},
/**
/**
* Hides or unhides the sections not being expanded.
* @param {string} sectionName The section to keep visible.
......
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animation-runner-behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/shadow.html">
<link rel="import" href="/animation/animation.html">
......@@ -9,6 +8,9 @@
:host {
display: flex;
flex-direction: column;
/* Margin so the box-shadow isn't clipped during animations. */
margin-left: 3px;
margin-right: 3px;
position: relative;
}
......@@ -30,17 +32,19 @@
overflow: hidden;
}
:host(.expanded) #header {
display: none;
:host(.expanding) #card,
:host(.collapsing) #card,
:host(.expanded) #card {
@apply(--shadow-elevation-4dp);
}
:host(.frozen) #card {
position: fixed;
width: 100%;
:host(:not(.expanding):not(.expanded)) #card {
/* Keep the fading-out neon-animatable inside the card. */
position: relative;
}
:host(.expanded.frozen) #card {
position: relative;
:host(.expanded) #header {
display: none;
}
:host([hidden-by-search]) {
......
......@@ -15,6 +15,11 @@
width: 96%;
}
:host(.showing-subpage) {
/* Set the height to the container height so the subpage scrolls. */
height: 100%;
}
:host > div {
height: inherit;
}
......
......@@ -685,6 +685,13 @@ TEST_F('CrSettingsNonExistentRouteTest', 'All', function() {
mocha.run();
});
// Hangs on ASAN builder for unknown reasons. TODO(michaelpg): Find reason.
GEN('#if defined(ADDRESS_SANITIZER)');
GEN('#define MAYBE_All DISABLED_All');
GEN('#else');
GEN('#define MAYBE_All All');
GEN('#endif');
/**
* @constructor
* @extends {SettingsPageBrowserTest}
......@@ -698,7 +705,7 @@ CrSettingsRouteDynamicParametersTest.prototype = {
browsePreload: 'chrome://md-settings/people?guid=a%2Fb&foo=42',
};
TEST_F('CrSettingsRouteDynamicParametersTest', 'All', function() {
TEST_F('CrSettingsRouteDynamicParametersTest', 'MAYBE_All', function() {
suite('DynamicParameters', function() {
test('get parameters from URL and navigation', function(done) {
assertEquals(settings.Route.PEOPLE, settings.getCurrentRoute());
......
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