feat: Improved drag and drop simulation, b=no-bug, c=common, tabs

This commit is contained in:
mr. m
2025-12-22 17:32:55 +01:00
parent 04833ad090
commit 86102dee80
3 changed files with 42 additions and 18 deletions

View File

@@ -20,6 +20,9 @@
- name: zen.tabs.close-window-with-empty
value: true
- name: zen.tabs.folder-dragover-threshold-percent
value: 20 # Percentage of folder height to trigger dragover
- name: zen.ctrlTab.show-pending-tabs
value: false

View File

@@ -57,9 +57,10 @@
startTabDrag(event, tab, ...args) {
super.startTabDrag(event, tab, ...args);
let dt = event.dataTransfer;
const dt = event.dataTransfer;
const { offsetX, offsetY } = this.#getDragImageOffset(tab);
const { offsetX, offsetY } = this.#getDragImageOffset(event, tab);
dt.setDragImage(tab, offsetX, offsetY);
}
_animateTabMove(event) {
@@ -409,6 +410,11 @@
delete dragData.shouldDropIntoCollapsedTabGroup;
[dropBefore, dropElement] = this.#applyDragoverIndicator(event, tabs, movingTabs) ?? [
dropBefore,
dropElement,
];
// Default to dropping into `dropElement`'s tab group, if it exists.
let dropElementGroup = dropElement?.group;
let colorCode = dropElementGroup?.color;
@@ -433,8 +439,6 @@
this._tabbrowserTabs.toggleAttribute('movingtab-addToGroup', colorCode);
this._tabbrowserTabs.toggleAttribute('movingtab-ungroup', !colorCode);
this.#applyDragoverIndicator(event, tabs, movingTabs, overlapPercent);
if (
newDropElementIndex == oldDropElementIndex &&
dropBefore == dragData.dropBefore &&
@@ -448,6 +452,13 @@
dragData.animDropElementIndex = newDropElementIndex;
}
handle_dragover(event) {
super.handle_dragover(event);
if (event.target.closest('#tabbrowser-tabbox')) {
gZenViewSplitter.onBrowserDragOverToSplit(event);
}
}
handle_dragend(event) {
super.handle_dragend(event);
this.#removeDragOverBackground();
@@ -477,11 +488,12 @@
}
}
#applyDragoverIndicator(event, tabs, movingTabs, overlapPercent) {
#applyDragoverIndicator(event, tabs, movingTabs) {
const separation = 4;
const dropZoneSelector = ':is(.tabbrowser-tab, .zen-drop-target, .tab-group-label)';
let shouldPlayHapticFeedback = false;
let dropElement = event.target.closest(dropZoneSelector);
let dropBefore;
if (!dropElement) {
const numEssentials = gBrowser._numZenEssentials;
const numPinned = gBrowser.pinnedTabCount - numEssentials;
@@ -501,19 +513,27 @@
shouldPlayHapticFeedback = this.#lastDropTarget !== null;
this.#removeDragOverBackground();
}
let isZenFolder = dropElement.parentElement?.isZenFolder;
let canHightlightGroup =
gZenFolders.highlightGroupOnDragOver(dropElement.parentElement, movingTabs) ||
!dropElement.parentElement?.isZenFolder;
if (isTab(dropElement)) {
gZenFolders.highlightGroupOnDragOver(dropElement.parentElement, movingTabs) || !isZenFolder;
let rect = dropElement.getBoundingClientRect();
const overlapPercent = (event.clientY - rect.top) / rect.height;
// We wan't to leave a small threshold (20% for example) so we can drag tabs below and above
// a folder label without dragging into the folder.
let threshold = Services.prefs.getIntPref('zen.tabs.folder-dragover-threshold-percent') / 100;
let dropIntoFolder =
isZenFolder && !(overlapPercent < threshold || overlapPercent > 1 - threshold);
if (isTab(dropElement) || !dropIntoFolder) {
const indicator = gZenPinnedTabManager.dragIndicator;
let rect = dropElement.getBoundingClientRect();
let top = 0;
const threshold =
threshold =
Services.prefs.getIntPref('browser.tabs.dragDrop.moveOverThresholdPercent') / 100;
if (overlapPercent > threshold) {
top = Math.round(rect.top + rect.height) + 'px';
dropBefore = false;
} else {
top = Math.round(rect.top) + 'px';
dropBefore = true;
}
if (indicator.style.top !== top) {
shouldPlayHapticFeedback = true;
@@ -523,24 +543,28 @@
indicator.style.setProperty('--indicator-width', rect.width - separation + 'px');
indicator.style.top = top;
indicator.style.removeProperty('left');
this.#removeDragOverBackground();
if (!isTab(dropElement) && dropElement?.parentElement?.isZenFolder) {
dropElement = dropElement.parentElement;
}
} else if (dropElement.classList.contains('zen-drop-target') && canHightlightGroup) {
// removeTabContainersDragoverClass Already calls a new haptic feedback
shouldPlayHapticFeedback =
this.#applyDragOverBackground(dropElement) && !gZenPinnedTabManager._dragIndicator;
gZenPinnedTabManager.removeTabContainersDragoverClass();
dropElement = dropElement.parentElement?.labelElement || dropElement;
}
if (shouldPlayHapticFeedback) {
Services.zen.playHapticFeedback();
}
return [dropBefore, dropElement];
}
#getDragImageOffset(tab) {
const { offsetX, offsetY } = tab._dragData;
#getDragImageOffset(event, tab) {
const rect = tab.getBoundingClientRect();
return {
offsetX: offsetX - rect.left,
offsetY: offsetY - rect.top,
offsetX: event.clientX - rect.left,
offsetY: event.clientY - rect.top,
};
}
};

View File

@@ -655,9 +655,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
removeTabContainersDragoverClass(hideIndicator = true) {
if (this._dragIndicator) {
Services.zen.playHapticFeedback();
}
this.dragIndicator.remove();
this._dragIndicator = null;
if (hideIndicator) {