Commit 6f6d0b11 authored by John Lee's avatar John Lee Committed by Commit Bot

WebUI Tab Strip: Animate pinned tabs moving

Bug: 1082344
Change-Id: Ied871842af5ef7005ec252c4528dc54530af3dad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2261417Reviewed-by: default avatardpapad <dpapad@chromium.org>
Commit-Queue: John Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#782156}
parent 64198f20
...@@ -41,11 +41,12 @@ ...@@ -41,11 +41,12 @@
* tab. 20px is subtracted from the height of an unpinned tab as there * tab. 20px is subtracted from the height of an unpinned tab as there
* are two 10px gaps to separate each of the 3 pinned tabs. */ * are two 10px gaps to separate each of the 3 pinned tabs. */
--tabstrip-pinned-tab-size: calc((var(--tabstrip-tab-height) - 20px) / 3); --tabstrip-pinned-tab-size: calc((var(--tabstrip-tab-height) - 20px) / 3);
--tabstrip-tab-spacing: 10px;
display: grid; display: grid;
grid-auto-columns: var(--tabstrip-pinned-tab-size); grid-auto-columns: var(--tabstrip-pinned-tab-size);
grid-auto-flow: column; grid-auto-flow: column;
grid-gap: 10px; grid-gap: var(--tabstrip-tab-spacing);
grid-template-rows: repeat(3, var(--tabstrip-pinned-tab-size)); grid-template-rows: repeat(3, var(--tabstrip-pinned-tab-size));
padding-block-end: var(--tabstrip-tab-list-vertical-padding); padding-block-end: var(--tabstrip-tab-list-vertical-padding);
padding-block-start: var(--tabstrip-tab-list-vertical-padding); padding-block-start: var(--tabstrip-tab-list-vertical-padding);
......
...@@ -15,7 +15,7 @@ import {isRTL} from 'chrome://resources/js/util.m.js'; ...@@ -15,7 +15,7 @@ import {isRTL} from 'chrome://resources/js/util.m.js';
import {CustomElement} from './custom_element.js'; import {CustomElement} from './custom_element.js';
import {DragManager, DragManagerDelegate} from './drag_manager.js'; import {DragManager, DragManagerDelegate} from './drag_manager.js';
import {TabElement} from './tab.js'; import {isTabElement, TabElement} from './tab.js';
import {isTabGroupElement, TabGroupElement} from './tab_group.js'; import {isTabGroupElement, TabGroupElement} from './tab_group.js';
import {TabStripEmbedderProxy, TabStripEmbedderProxyImpl} from './tab_strip_embedder_proxy.js'; import {TabStripEmbedderProxy, TabStripEmbedderProxyImpl} from './tab_strip_embedder_proxy.js';
import {tabStripOptions} from './tab_strip_options.js'; import {tabStripOptions} from './tab_strip_options.js';
...@@ -54,55 +54,77 @@ const LayoutVariable = { ...@@ -54,55 +54,77 @@ const LayoutVariable = {
* @param {number} newIndex * @param {number} newIndex
*/ */
function animateElementMoved(movedElement, prevIndex, newIndex) { function animateElementMoved(movedElement, prevIndex, newIndex) {
// Direction is -1 for moving the movedElement towards the start of the tab // Direction is -1 for moving towards a lower index, +1 for moving
// strip, and +1 for moving towards the end of the tab strip. // towards a higher index. If moving towards a lower index, the TabList needs
const direction = Math.sign(prevIndex - newIndex); // to animate everything from the movedElement's current index to its prev
// index by traversing the nextElementSibling of each element because the
// The horizontal direction is flipped depending on RTL vs. LTR. // movedElement is now at a preceding position from all the elements it has
const horizontalDirection = (isRTL() ? -1 : 1) * direction; // slid across. If moving towards a higher index, the TabList needs to
// traverse the previousElementSiblings.
const direction = Math.sign(newIndex - prevIndex);
/** /**
* @param {!Element} element * @param {!Element} element
* @return {?Element} * @return {?Element}
*/ */
function getSiblingToAnimate(element) { function getSiblingToAnimate(element) {
return direction > -1 ? element.nextElementSibling : return direction === -1 ? element.nextElementSibling :
element.previousElementSibling; element.previousElementSibling;
} }
let elementToAnimate = getSiblingToAnimate(movedElement); let elementToAnimate = getSiblingToAnimate(movedElement);
for (let i = prevIndex; i !== newIndex && elementToAnimate; i -= direction) { for (let i = newIndex; i !== prevIndex && elementToAnimate; i -= direction) {
slideElement(elementToAnimate, -1 * horizontalDirection); const elementToAnimatePrevIndex = i;
const elementToAnimateNewIndex = i - direction;
slideElement(
elementToAnimate, elementToAnimatePrevIndex, elementToAnimateNewIndex);
elementToAnimate = getSiblingToAnimate(elementToAnimate); elementToAnimate = getSiblingToAnimate(elementToAnimate);
} }
// Animate the moved TabElement itself the total number of tabs that it slideElement(movedElement, prevIndex, newIndex);
// has been moved across.
slideElement(
movedElement, horizontalDirection * Math.abs(newIndex - prevIndex));
} }
/** /**
* Animates the slide of an element across the tab strip. * Animates the slide of an element across the tab strip (both vertically and
* horizontally for pinned tabs, and horizontally for other tabs and groups).
* @param {!Element} element * @param {!Element} element
* @param {number} horizontalScale * @param {number} prevIndex
* @param {number} newIndex
*/ */
function slideElement(element, horizontalScale) { function slideElement(element, prevIndex, newIndex) {
let horizontalMovement = newIndex - prevIndex;
let verticalMovement = 0;
if (isTabElement(element) && element.tab.pinned) {
const pinnedTabsPerColumn = 3;
const columnChange = Math.floor(newIndex / pinnedTabsPerColumn) -
Math.floor(prevIndex / pinnedTabsPerColumn);
horizontalMovement = columnChange;
verticalMovement =
(newIndex - prevIndex) - (columnChange * pinnedTabsPerColumn);
}
horizontalMovement *= isRTL() ? -1 : 1;
const translateX = `calc(${horizontalMovement * -1} * ` +
'(var(--tabstrip-tab-width) + var(--tabstrip-tab-spacing)))';
const translateY = `calc(${verticalMovement * -1} * ` +
'(var(--tabstrip-tab-height) + var(--tabstrip-tab-spacing)))';
element.isValidDragOverTarget = false; element.isValidDragOverTarget = false;
const animation = element.animate( const animation = element.animate(
[ [
{ {transform: `translate(${translateX}, ${translateY})`},
transform: 'translateX(calc(' + horizontalScale + ' ' + {transform: 'translate(0, 0)'},
'* (var(--tabstrip-tab-width) + var(--tabstrip-tab-spacing))))',
},
{transform: 'translateX(0)'},
], ],
{ {
duration: 120, duration: 120,
easing: 'ease-out', easing: 'ease-out',
}); });
animation.onfinish = () => { function onComplete() {
element.isValidDragOverTarget = true; element.isValidDragOverTarget = true;
}; }
animation.oncancel = onComplete;
animation.onfinish = onComplete;
} }
/** @implements {DragManagerDelegate} */ /** @implements {DragManagerDelegate} */
......
...@@ -269,9 +269,11 @@ suite('TabList', () => { ...@@ -269,9 +269,11 @@ suite('TabList', () => {
/** /**
* @param {!Element} element * @param {!Element} element
* @param {number} scale * @param {number} horizontalScale
* @param {number} verticalScale
*/ */
function testPlaceElementAnimationParams(element, scale) { function testPlaceElementAnimationParams(
element, horizontalScale, verticalScale) {
const animations = element.getAnimations(); const animations = element.getAnimations();
// TODO(crbug.com/1090645): Remove logging once the test no longer flakes. // TODO(crbug.com/1090645): Remove logging once the test no longer flakes.
...@@ -289,13 +291,17 @@ suite('TabList', () => { ...@@ -289,13 +291,17 @@ suite('TabList', () => {
assertEquals('ease-out', animations[0].effect.getTiming().easing); assertEquals('ease-out', animations[0].effect.getTiming().easing);
const keyframes = animations[0].effect.getKeyframes(); const keyframes = animations[0].effect.getKeyframes();
const tabSpacingVars = const horizontalTabSpacingVars =
'(var(--tabstrip-tab-width) + var(--tabstrip-tab-spacing))'; '(var(--tabstrip-tab-width) + var(--tabstrip-tab-spacing))';
const verticalTabSpacingVars =
'(var(--tabstrip-tab-height) + var(--tabstrip-tab-spacing))';
assertEquals(2, keyframes.length); assertEquals(2, keyframes.length);
assertEquals( assertEquals(
`translateX(calc(${scale} * ${tabSpacingVars}))`, `translate(calc(${horizontalScale} * ${
horizontalTabSpacingVars}), calc(${verticalScale} * ${
verticalTabSpacingVars}))`,
keyframes[0].transform); keyframes[0].transform);
assertEquals('translateX(0px)', keyframes[1].transform); assertEquals('translate(0px, 0px)', keyframes[1].transform);
} }
/** /**
...@@ -314,13 +320,13 @@ suite('TabList', () => { ...@@ -314,13 +320,13 @@ suite('TabList', () => {
const movedTab = unpinnedTabs[indexToMove]; const movedTab = unpinnedTabs[indexToMove];
tabList.placeTabElement(movedTab, newIndex, false, undefined); tabList.placeTabElement(movedTab, newIndex, false, undefined);
testPlaceElementAnimationParams( testPlaceElementAnimationParams(
movedTab, -1 * direction * Math.abs(newIndex - indexToMove)); movedTab, -1 * direction * Math.abs(newIndex - indexToMove), 0);
Array.from(unpinnedTabs) Array.from(unpinnedTabs)
.filter(tabElement => tabElement !== movedTab) .filter(tabElement => tabElement !== movedTab)
.forEach( .forEach(
tabElement => tabElement =>
testPlaceElementAnimationParams(tabElement, direction)); testPlaceElementAnimationParams(tabElement, direction, 0));
} }
test('PlaceTabElementAnimatesTabMovedTowardsStart', () => { test('PlaceTabElementAnimatesTabMovedTowardsStart', () => {
...@@ -341,6 +347,75 @@ suite('TabList', () => { ...@@ -341,6 +347,75 @@ suite('TabList', () => {
return testPlaceTabElementAnimation(0, tabs.length - 1, -1); return testPlaceTabElementAnimation(0, tabs.length - 1, -1);
}); });
test('PlacePinnedTabElementAnimatesTabsWithinSameColumn', async () => {
tabs.forEach(pinTabAt);
await tabList.animationPromises;
// Test moving a tab within the same column. If a tab is moved from index 0
// to index 2, it should move vertically down 2 places. Tabs at index 1 and
// index 2 should move up 1 space.
const pinnedTabs = getPinnedTabs();
tabList.placeTabElement(pinnedTabs[0], 2, /*pinned=*/ true);
await Promise.all([
testPlaceElementAnimationParams(pinnedTabs[0], 0, -2),
testPlaceElementAnimationParams(pinnedTabs[1], 0, 1),
testPlaceElementAnimationParams(pinnedTabs[2], 0, 1),
]);
});
test(
'PlacePinnedTabElementAnimatesTabsAcrossColumnsToHigherIndex',
async () => {
tabs.forEach(pinTabAt);
for (let i = 0; i < 4; i++) {
webUIListenerCallback('tab-created', {
active: false,
alertStates: [],
id: tabs.length + i,
index: tabs.length + i,
pinned: true,
title: 'Pinned tab',
});
}
await tabList.animationPromises;
const pinnedTabs = getPinnedTabs();
tabList.placeTabElement(pinnedTabs[2], 6, /*pinned=*/ true);
await Promise.all([
testPlaceElementAnimationParams(pinnedTabs[2], -2, 2),
testPlaceElementAnimationParams(pinnedTabs[3], 1, -2),
testPlaceElementAnimationParams(pinnedTabs[4], 0, 1),
testPlaceElementAnimationParams(pinnedTabs[5], 0, 1),
testPlaceElementAnimationParams(pinnedTabs[6], 1, -2),
]);
});
test(
'PlacePinnedTabElementAnimatesTabsAcrossColumnsToLowerIndex',
async () => {
tabs.forEach(pinTabAt);
for (let i = 0; i < 4; i++) {
webUIListenerCallback('tab-created', {
active: false,
alertStates: [],
id: tabs.length + i,
index: tabs.length + i,
pinned: true,
title: 'Pinned tab',
});
}
await tabList.animationPromises;
const pinnedTabs = getPinnedTabs();
tabList.placeTabElement(pinnedTabs[3], 0, /*pinned=*/ true);
await Promise.all([
testPlaceElementAnimationParams(pinnedTabs[3], 1, 0),
testPlaceElementAnimationParams(pinnedTabs[2], -1, 2),
testPlaceElementAnimationParams(pinnedTabs[1], 0, -1),
testPlaceElementAnimationParams(pinnedTabs[0], 0, -1),
]);
});
test('PlacesTabGroupElement', () => { test('PlacesTabGroupElement', () => {
const tabGroupElement = /** @type {!TabGroupElement} */ ( const tabGroupElement = /** @type {!TabGroupElement} */ (
document.createElement('tabstrip-tab-group')); document.createElement('tabstrip-tab-group'));
...@@ -373,14 +448,14 @@ suite('TabList', () => { ...@@ -373,14 +448,14 @@ suite('TabList', () => {
/** @type {!TabGroupElement} */ (tabToGroup.parentElement); /** @type {!TabGroupElement} */ (tabToGroup.parentElement);
tabList.placeTabGroupElement(groupElement, newIndex); tabList.placeTabGroupElement(groupElement, newIndex);
testPlaceElementAnimationParams( testPlaceElementAnimationParams(
groupElement, -1 * direction * Math.abs(newIndex - indexToGroup)); groupElement, -1 * direction * Math.abs(newIndex - indexToGroup), 0);
// Test animations on all the other tabs. // Test animations on all the other tabs.
Array.from(getUnpinnedTabs()) Array.from(getUnpinnedTabs())
.filter(tabElement => tabElement.parentElement !== groupElement) .filter(tabElement => tabElement.parentElement !== groupElement)
.forEach( .forEach(
tabElement => tabElement =>
testPlaceElementAnimationParams(tabElement, direction)); testPlaceElementAnimationParams(tabElement, direction, 0));
} }
test('PlaceTabGroupElementAnimatesTabGroupMovedTowardsStart', () => { test('PlaceTabGroupElementAnimatesTabGroupMovedTowardsStart', () => {
...@@ -416,8 +491,8 @@ suite('TabList', () => { ...@@ -416,8 +491,8 @@ suite('TabList', () => {
tabList.placeTabGroupElement(tabGroup, 0); tabList.placeTabGroupElement(tabGroup, 0);
// Both the TabElement and TabGroupElement should move by a scale of 1. // Both the TabElement and TabGroupElement should move by a scale of 1.
testPlaceElementAnimationParams(tabGroup, 1); testPlaceElementAnimationParams(tabGroup, 1, 0);
testPlaceElementAnimationParams(ungroupedTab, -1); testPlaceElementAnimationParams(ungroupedTab, -1, 0);
}); });
test('AddNewTabGroup', () => { test('AddNewTabGroup', () => {
......
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