feat: Improved folder icon animations and dragging sensitivity, b=no-bug, c=folders, tabs

This commit is contained in:
mr. m
2025-09-24 13:08:35 +02:00
parent f77db8cb20
commit 9046e083ee
5 changed files with 66 additions and 110 deletions

View File

@@ -19,9 +19,9 @@
"update-ff:raw": "surfer update",
"update-ff:rc": "python3 scripts/update_ff.py --rc",
"update-ff:l10n": "python3 scripts/update_ff.py --just-l10n",
"pretty": "prettier . --write && autopep8 -r --in-place scripts/ src/",
"pretty": "prettier . --write --cache && autopep8 -r --in-place scripts/ src/",
"lint": "npx eslint src/ && prettier . --check && autopep8 --diff scripts/ src/",
"lint:fix": "npx eslint src/ --fix && npm run pretty",
"lint:fix": "npm run pretty && npx eslint src/ --fix",
"prepare": "husky",
"reset-ff": "surfer reset",
"surfer": "surfer",

View File

@@ -33,41 +33,21 @@
</linearGradient>
</defs>
<!--Back Folder (path)-->
<path d="M8 5.625H11.9473C12.4866 5.625 13.0105 5.80861 13.4316 6.14551L14.2881 6.83105C14.9308 7.34508 15.7298 7.625 16.5527 7.625H20C21.3117 7.625 22.375 8.68832 22.375 10V20C22.375 21.3117 21.3117 22.375 20 22.375H8C6.68832 22.375 5.625 21.3117 5.625 20V8C5.625 6.68832 6.68832 5.625 8 5.625Z" style="fill: var(--zen-folder-behind-bgcolor);">
<animateTransform restart="whenNotActive" type="skewX" additive="sum" attributeName="transform" values="0;16" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="translate" additive="sum" attributeName="transform" values="0 0;-2 3.4" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="scale" additive="sum" attributeName="transform" values="1 1;0.85 0.85" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<path class="back" d="M8 5.625H11.9473C12.4866 5.625 13.0105 5.80861 13.4316 6.14551L14.2881 6.83105C14.9308 7.34508 15.7298 7.625 16.5527 7.625H20C21.3117 7.625 22.375 8.68832 22.375 10V20C22.375 21.3117 21.3117 22.375 20 22.375H8C6.68832 22.375 5.625 21.3117 5.625 20V8C5.625 6.68832 6.68832 5.625 8 5.625Z" style="fill: var(--zen-folder-behind-bgcolor);">
</path>
<path d="M8 5.625H11.9473C12.4866 5.625 13.0105 5.80861 13.4316 6.14551L14.2881 6.83105C14.9308 7.34508 15.7298 7.625 16.5527 7.625H20C21.3117 7.625 22.375 8.68832 22.375 10V20C22.375 21.3117 21.3117 22.375 20 22.375H8C6.68832 22.375 5.625 21.3117 5.625 20V8C5.625 6.68832 6.68832 5.625 8 5.625Z" style="stroke-width: 1.5px; stroke: var(--zen-folder-stroke); fill: url(#gradient-0); fill-opacity: 0.1;">
<animateTransform restart="whenNotActive" type="skewX" additive="sum" attributeName="transform" values="0;16" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="translate" additive="sum" attributeName="transform" values="0 0;-2 3.4" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="scale" additive="sum" attributeName="transform" values="1 1;0.85 0.85" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<path class="back" d="M8 5.625H11.9473C12.4866 5.625 13.0105 5.80861 13.4316 6.14551L14.2881 6.83105C14.9308 7.34508 15.7298 7.625 16.5527 7.625H20C21.3117 7.625 22.375 8.68832 22.375 10V20C22.375 21.3117 21.3117 22.375 20 22.375H8C6.68832 22.375 5.625 21.3117 5.625 20V8C5.625 6.68832 6.68832 5.625 8 5.625Z" style="stroke-width: 1.5px; stroke: var(--zen-folder-stroke); fill: url(#gradient-0); fill-opacity: 0.1;">
</path>
<!--Front Folder (rect)-->
<rect x="5.625" y="9.625" width="16.75" height="12.75" rx="2.375" style="fill: var(--zen-folder-front-bgcolor);">
<animateTransform restart="whenNotActive" type="skewX" additive="sum" attributeName="transform" values="0;-16" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="translate" additive="sum" attributeName="transform" values="0 0;11.1 3.4" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="scale" additive="sum" attributeName="transform" values="1 1;0.85 0.85" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<rect class="front" x="5.625" y="9.625" width="16.75" height="12.75" rx="2.375" style="fill: var(--zen-folder-front-bgcolor);">
</rect>
<rect x="5.625" y="9.625" width="16.75" height="12.75" rx="2.375" style="stroke-width: 1.5px; stroke: var(--zen-folder-stroke); fill: url(#gradient-1); fill-opacity: 0.1;">
<animateTransform restart="whenNotActive" type="skewX" additive="sum" attributeName="transform" values="0;-16" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="translate" additive="sum" attributeName="transform" values="0 0;11.1 3.4" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="scale" additive="sum" attributeName="transform" values="1 1;0.85 0.85" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<rect class="front" x="5.625" y="9.625" width="16.75" height="12.75" rx="2.375" style="stroke-width: 1.5px; stroke: var(--zen-folder-stroke); fill: url(#gradient-1); fill-opacity: 0.1;">
</rect>
<!--Icon (g)-->
<g id="folder-icon" style="fill: var(--zen-folder-stroke);">
<g class="icon" style="fill: var(--zen-folder-stroke);">
<image href="" height="19" width="20"/>
<animateTransform restart="whenNotActive" type="skewX" additive="sum" attributeName="transform" values="0;-16" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="translate" additive="sum" attributeName="transform" values="0 0;11.1 3.4" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="scale" additive="sum" attributeName="transform" values="1 1;0.85 0.85" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animate restart="whenNotActive" attributeName="opacity" values="0;0" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
</g>
<!--End Icon (g)-->
<g id="folder-dots" style="fill: var(--zen-folder-stroke);">
<animateTransform restart="whenNotActive" type="skewX" additive="sum" attributeName="transform" values="0;-16" begin="0s" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="translate" additive="sum" attributeName="transform" values="0 0;11.1 3.4" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animateTransform restart="whenNotActive" type="scale" additive="sum" attributeName="transform" values="1 1;0.85 0.85" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<animate restart="whenNotActive" attributeName="opacity" values="0;0" dur="0.3s" fill="freeze" keyTimes="0; 1" calcMode="spline" keySplines="0.42 0 0 1"/>
<g class="dots" style="fill: var(--zen-folder-stroke);">
<ellipse cx="10" cy="16" rx="1.25" ry="1.25"/>
<ellipse cx="14" cy="16" rx="1.25" ry="1.25"/>
<ellipse cx="18" cy="16" rx="1.25" ry="1.25"/>
@@ -89,13 +69,6 @@
this.#initialized = true;
this._activeTabs = [];
this.icon.appendChild(ZenFolder.rawIcon.cloneNode(true));
// Save original values for animations
this.icon.querySelectorAll('animate, animateTransform, animateMotion').forEach((anim) => {
const vals = anim.getAttribute('values');
if (vals) {
anim.dataset.origValues = vals;
}
});
this.labelElement.parentElement.setAttribute('context', 'zenFolderActions');

View File

@@ -40,7 +40,6 @@
#lastFolderContextMenu = null;
#foldersEnabled = false;
#folderAnimCache = new Map();
#animationCount = 0;
@@ -304,7 +303,7 @@
folder.removeAttribute('has-active');
}
this.collapseVisibleTab(folder, true);
this.updateFolderIcon(folder, 'close', false);
this.updateFolderIcon(folder, 'close');
}
}
}
@@ -721,7 +720,7 @@
async #convertFolderToSpace(folder) {
const currentWorkspace = gZenWorkspaces.getActiveWorkspaceFromCache();
let selectedTab = folder.tabs.find((tab) => tab.selected);
const icon = folder.icon?.querySelector('svg #folder-icon image');
const icon = folder.icon?.querySelector('svg .icon image');
const newSpace = await gZenWorkspaces.createAndSaveWorkspace(
folder.label,
@@ -855,7 +854,7 @@
gBrowser.TabStateFlusher.flush(tab.linkedBrowser);
});
this.updateFolderIcon(folder, 'auto', false);
this.updateFolderIcon(folder, 'auto');
if (options.renameFolder) {
folder.rename();
@@ -1116,71 +1115,14 @@
}
}
updateFolderIcon(group, state = 'auto', play = true) {
updateFolderIcon(group, state = 'auto') {
const svg = group.querySelector('svg');
if (!svg) return [];
let animations = this.#folderAnimCache.get(group);
if (!animations) {
animations = svg.querySelectorAll('animate, animateTransform, animateMotion');
this.#folderAnimCache.set(group, animations);
}
const isCollapsed = group.collapsed;
svg.setAttribute('state', state === 'auto' ? (isCollapsed ? 'close' : 'open') : state);
const hasActive = group.hasAttribute('has-active');
const OPACITY = {
'folder-dots': { active: '0;1', baseOrig: '0;0' },
'folder-icon': { active: '1;0', baseOrig: '1;1' },
};
animations.forEach((animation) => {
const parentId = animation.parentElement.id;
const isOpacity = animation.getAttribute('attributeName') === 'opacity';
if (!animation.dataset.origValues) {
animation.dataset.origValues = animation.getAttribute('values');
}
const origValues = animation.dataset.origValues;
const [fromValue, toValue] = origValues.split(';');
const isActiveState = isCollapsed && hasActive && isOpacity;
if (!play && !isActiveState) {
if (isOpacity && OPACITY[parentId]) {
const staticValue = OPACITY[parentId].baseOrig;
animation.dataset.origValues = staticValue;
animation.setAttribute('values', staticValue);
animation.beginElement();
}
return;
}
if (isOpacity && OPACITY[parentId]) {
animation.dataset.origValues = OPACITY[parentId].baseOrig;
}
let newValues;
if (isActiveState && OPACITY[parentId]) {
newValues = OPACITY[parentId].active;
const [activeFrom, activeTo] = newValues.split(';');
animation.dataset.origValues = `${activeTo};${activeFrom}`;
} else {
const stateValues = {
open: `${fromValue};${toValue}`,
close: `${toValue};${fromValue}`,
auto: isCollapsed ? `${toValue};${fromValue}` : `${fromValue};${toValue}`,
};
newValues = stateValues[state] || stateValues.auto;
}
if (animation.getAttribute('values') !== newValues) {
animation.setAttribute('values', newValues);
animation.beginElement();
}
});
svg.setAttribute('active', hasActive && isCollapsed ? 'true' : 'false');
return [];
}
@@ -1256,7 +1198,7 @@
}
setFolderUserIcon(group, icon) {
const svgIcon = group.icon.querySelector('svg #folder-icon image');
const svgIcon = group.icon.querySelector('svg .icon image');
if (!svgIcon) return;
svgIcon.setAttribute('href', icon ?? '');
if (svgIcon.getAttribute('href') !== icon) {
@@ -1305,7 +1247,7 @@
if (group.activeTabs.length === 0) {
group.removeAttribute('has-active');
this.updateFolderIcon(group, 'close', false);
this.updateFolderIcon(group, 'close');
}
return this.on_TabGroupCollapse({
@@ -1332,7 +1274,7 @@
if (group.activeTabs.length === 0) {
group.removeAttribute('has-active');
this.updateFolderIcon(group, 'close', false);
this.updateFolderIcon(group, 'close');
}
this.on_TabGroupExpand({ target: group, forExpandVisible: true });
@@ -1408,7 +1350,7 @@
if (tabsContainer.hasAttribute('hidden')) tabsContainer.removeAttribute('hidden');
animations.push(...this.updateFolderIcon(current, 'close', false));
animations.push(...this.updateFolderIcon(current, 'close'));
animations.push(
gZenUIManager.motion.animate(
groupStart,
@@ -1518,7 +1460,7 @@
#groupInit(group, stateData) {
// Setup zen-folder icon to the correct position
this.updateFolderIcon(group, 'auto', false);
this.updateFolderIcon(group, 'auto');
if (stateData?.userIcon) {
this.setFolderUserIcon(group, stateData.userIcon);
}
@@ -1575,7 +1517,7 @@
let prevSiblingInfo = null;
const prevSibling = folder.previousElementSibling;
const userIcon = folder?.icon?.querySelector('svg #folder-icon image');
const userIcon = folder?.icon?.querySelector('svg .icon image');
if (prevSibling) {
if (gBrowser.isTabGroup(prevSibling)) {

View File

@@ -6,7 +6,6 @@
tab-group[split-view-group] {
display: block;
min-width: 100%;
@media (prefers-reduced-motion: no-preference) {
transition: var(--zen-tabbox-element-indent-transition);
@@ -151,6 +150,10 @@ tab-group[split-view-group] {
tab-group .tab-group-container {
flex-direction: column;
}
tab-group[split-view-group] {
min-width: 100%;
}
}
tab-group[split-view-group] .tabbrowser-tab {
@@ -295,6 +298,38 @@ zen-folder {
fill: var(--zen-folder-stroke);
transform: translate(4px, 6.5px);
}
& g,
& rect,
& path {
transition:
transform 0.3s cubic-bezier(0.42, 0, 0, 1),
opacity 0.3s cubic-bezier(0.42, 0, 0, 1);
}
&[state='open'] .back {
transform: skewX(16deg) translate(-2px, 3.4px) scale(0.85);
}
&[state='open'] :is(.front, .dots, .icon) {
transform: skewX(-16deg) translate(11.1px, 3.4px) scale(0.85);
}
& .icon {
opacity: 1;
}
& .dots {
opacity: 0;
}
&[active='true'] .icon {
opacity: 0;
}
&[active='true'] .dots {
opacity: 1;
}
}
}

View File

@@ -1246,6 +1246,7 @@
for (const item of this.dragShiftableItems) {
item.style.transform = '';
}
delete this._topToNormalTabs;
for (const item of gBrowser.tabContainer.ariaFocusableItems) {
if (gBrowser.isTab(item)) {
let isVisible = true;
@@ -1280,7 +1281,7 @@
: [separator];
}
animateSeparatorMove(movingTabs, dropElement, isPinned, event) {
animateSeparatorMove(movingTabs, dropElement, isPinned) {
let draggedTab = movingTabs[0];
if (gBrowser.isTabGroupLabel(draggedTab) && draggedTab.group.isZenFolder) {
this._isGoingToPinnedTabs = true;
@@ -1290,14 +1291,19 @@
draggedTab = draggedTab.group;
}
const itemsToCheck = this.dragShiftableItems;
const translate = event.screenY;
let translate = movingTabs[isPinned ? movingTabs.length - 1 : 0].getBoundingClientRect().top;
if (isPinned) {
const rect = draggedTab.getBoundingClientRect();
translate += rect.height;
}
const draggingTabHeight = movingTabs.reduce((acc, item) => {
return acc + window.windowUtils.getBoundsWithoutFlushing(item).height;
}, 0);
let topToNormalTabs = itemsToCheck[0].screenY;
if (!isPinned) {
topToNormalTabs += draggedTab.getBoundingClientRect().height;
if (typeof this._topToNormalTabs === 'undefined') {
const rects = itemsToCheck.map((item) => window.windowUtils.getBoundsWithoutFlushing(item));
this._topToNormalTabs = rects[0].top + rects.at(-1).height / (isPinned ? 2 : 4);
}
let topToNormalTabs = this._topToNormalTabs;
const isGoingToPinnedTabs =
translate < topToNormalTabs && gBrowser.pinnedTabCount - gBrowser._numZenEssentials > 0;
const multiplier = isGoingToPinnedTabs !== isPinned ? (isGoingToPinnedTabs ? 1 : -1) : 0;