From 04a46912ea4062103d914872cb2f4700514e5eaf Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:38:25 +0200 Subject: [PATCH 01/27] Refactor: calculate splitter template using templateAreas without splitters --- src/ZenViewSplitter.mjs | 177 +++++++++++++++++++++++++--------------- 1 file changed, 110 insertions(+), 67 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 5bd034a..cafe3ae 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -321,13 +321,13 @@ var gZenViewSplitter = new class { this.currentView = this._data.indexOf(splitData); const gridType = splitData.gridType || 'grid'; - this.applyGridLayout(splitData.tabs, gridType, activeTab); this.setTabsDocShellState(splitData.tabs, true); this.updateSplitViewButton(false); - this.updateGridSizes(splitData); - this.applySplitters(splitData.widths.length , splitData.heights.length); - this.applyGridSizes(); + + this.applyGridToTabs(splitData.tabs, gridType, activeTab); + const gridAreas = this.calculateGridAreas(splitData.tabs, gridType); + this.applyGridLayout(gridAreas); } /** @@ -337,10 +337,7 @@ var gZenViewSplitter = new class { * @param {string} gridType - The type of grid layout. * @param {Tab} activeTab - The active tab. */ - applyGridLayout(tabs, gridType, activeTab) { - const gridAreas = this.calculateGridAreas(tabs, gridType); - this.tabBrowserPanel.style.gridTemplateAreas = gridAreas; - + applyGridToTabs(tabs, gridType, activeTab) { tabs.forEach((tab, index) => { tab.splitView = true; const container = tab.linkedBrowser.closest('.browserSidebarContainer'); @@ -349,55 +346,109 @@ var gZenViewSplitter = new class { } /** - * Adds splitters to tabBrowserPanel + * Apply grid layout to tabBrowserPanel * - * @param nrOfColumns number of columns in the grid - * @param nrOfRows number of rows in the grid + * @param gridTemplateAreas */ - applySplitters(nrOfColumns, nrOfRows) { - this.removeSplitters(); - const vSplittersNeeded = (nrOfColumns - 1) * nrOfRows; - const hSplittersNeeded = nrOfRows - 1; + applyGridLayout(gridTemplateAreas) { + const finalLayout = this.calculateLayoutWithSplitters(gridTemplateAreas); - const insertSplitter = (i, orient, gridIdx) => { - const splitter = document.createElement('div'); - splitter.className = 'zen-split-view-splitter'; - splitter.setAttribute('orient', orient); - splitter.setAttribute('gridIdx', gridIdx); - splitter.style.gridArea = `${orient === 'vertical' ? 'v' : 'h'}Splitter${i}`; - splitter.addEventListener('mousedown', this.handleSplitterMouseDown); - this.tabBrowserPanel.insertAdjacentElement("afterbegin", splitter); - } - for (let i = 1; i <= vSplittersNeeded; i++) { - insertSplitter(i, 'vertical', Math.floor((i - 1) /nrOfRows) + 1); - } - for (let i = 1; i <= hSplittersNeeded; i++) { - insertSplitter(i, 'horizontal', i); + this.tabBrowserPanel.style.gridTemplateAreas = finalLayout.templateAreas; + this.removeSplitters(); + finalLayout.splitters.forEach(s => this.insertSplitter(s.nr, s.orient, s.gridIdx)); + + this.updateGridDimensions(this._data[this.currentView], finalLayout.nrOfRows, finalLayout.nrOfColumns); + this.applyGridSizes(); + } + + + /** + * Takes a gridLayout and returns a new one with splitters. + * + * @param girdTemplateAreas + * @returns {{splitters: *[], templateAreas: string, nrOfColumns: number, nrOfRows: number}} + */ + calculateLayoutWithSplitters(girdTemplateAreas) { + const rows = girdTemplateAreas.split(/['"]\s+['"]/).map(r => r.replaceAll(/['"]/g, '').split(/\s+/)); + + let finalTemplateAreas = ''; + const splitters = []; + + let vSplitterCount = 0; + let hSplitterCount = 0; + for (let i = 0; i < rows.length; i++) { + let nextRow = ''; + finalTemplateAreas += `'`; + let buildingHSplitter = false; + for (let j = 0; j < rows[i].length; j++) { + const current = rows[i][j]; + const rightNeighbor = rows[i][j + 1]; + finalTemplateAreas += " " + current; + if (rightNeighbor && rightNeighbor !== current) { + finalTemplateAreas += ` vSplitter${vSplitterCount + 1}`; + vSplitterCount++; + splitters.push({nr: vSplitterCount, orient: 'vertical', gridIdx: j + 1}); + } + + if (!rows[i + 1]) { + continue; + } + const underNeighbor = rows[i + 1][j]; + if (underNeighbor !== current) { + nextRow += ` hSplitter${hSplitterCount + 1}`; + buildingHSplitter = true; + } else { + nextRow += ' ' + current; + hSplitterCount++; + splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1}); + buildingHSplitter = false; + } + if (j === rows[i].length - 1) { + if (buildingHSplitter) { + hSplitterCount++; + splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1}); + buildingHSplitter = false; + } + continue; + } + const rightNeighborJoinedWithUnderNeighbor = rightNeighbor === rows[i + 1][j + 1]; + if (rightNeighborJoinedWithUnderNeighbor && (underNeighbor === current)) { + if (current === rightNeighbor) nextRow += ' ' + current; // square + else nextRow += ` vSplitter${vSplitterCount}`; + } else { + nextRow += ` hSplitter${hSplitterCount + 1}`; + } + } + finalTemplateAreas += `'`; + if (nextRow) { + finalTemplateAreas += `'${nextRow}'`; + } } + + return {templateAreas: finalTemplateAreas, splitters: splitters, nrOfRows: rows.length, nrOfColumns: rows[0]?.length || 0}; + } + + insertSplitter(nr, orient, gridIdx) { + const splitter = document.createElement('div'); + splitter.className = 'zen-split-view-splitter'; + splitter.setAttribute('orient', orient); + splitter.setAttribute('gridIdx', gridIdx); + splitter.style.gridArea = `${orient === 'vertical' ? 'v' : 'h'}Splitter${nr}`; + splitter.addEventListener('mousedown', this.handleSplitterMouseDown); + this.tabBrowserPanel.insertAdjacentElement("afterbegin", splitter); } /** * Initialize splitData with default widths and heights if dimensions of grid don't match * - * @param {object} splitData - The split data. + * @param splitData the splits data + * @param nrOfRows number of rows in the grid (excluding splitters) + * @param nrOfColumns number of columns in the grid (excluding splitters) */ - updateGridSizes(splitData) { - const tabs = splitData.tabs; - const gridType = splitData.gridType; - - let nrOfWidths = 1; - let nrOfHeights = 1; - if (gridType === 'vsep') { - nrOfWidths = tabs.length; - } else if (gridType === 'hsep') { - nrOfHeights = tabs.length; - } else if (gridType === 'grid') { - nrOfWidths = tabs.length > 2 ? Math.ceil(tabs.length / 2) : 2; - nrOfHeights = tabs.length > 2 ? 2 : 1; - } - if (splitData.widths?.length !== nrOfWidths || splitData.heights?.length !== nrOfHeights) { - splitData.widths = Array(nrOfWidths).fill(100 / nrOfWidths); - splitData.heights = Array(nrOfHeights).fill(100 / nrOfHeights); + updateGridDimensions(splitData, nrOfRows, nrOfColumns) { + if (splitData.widths?.length !== nrOfColumns || splitData.heights?.length !== nrOfRows) { + splitData.widths = Array( nrOfColumns).fill(100 / nrOfColumns); + splitData.heights = Array(nrOfRows).fill(100 / nrOfRows); } } @@ -419,10 +470,10 @@ var gZenViewSplitter = new class { return this.calculateGridAreasForGrid(tabs); } if (gridType === 'vsep') { - return `'${tabs.slice(0, -1).map((_, j) => `tab${j + 1} vSplitter${j + 1}`).join(' ')} tab${tabs.length}'`; + return `'${tabs.map((_, j) => `tab${j + 1}`).join(' ')}'`; } if (gridType === 'hsep') { - return tabs.slice(0, -1).map((_, j) => `'tab${j + 1}' 'hSplitter${j + 1}'`).join(' ') + `'tab${tabs.length}`; + return tabs.map((_, j) => `'tab${j + 1}'`).join(' '); } return ''; } @@ -434,32 +485,24 @@ var gZenViewSplitter = new class { * @returns {string} The calculated grid areas. */ calculateGridAreasForGrid(tabs) { - if (tabs.length === 2) { - return "'tab1 vSplitter1 tab2'"; - } - const rows = ['', '']; - for (let i = 0; i < tabs.length - 2; i++) { + tabs.forEach((_, i) => { if (i % 2 === 0) { - rows[0] += ` tab${i + 1} vSplitter${i + 1}`; + rows[0] += ` tab${i + 1}`; } else { - rows[1] += ` tab${i + 1} vSplitter${i + 1}`; + rows[1] += ` tab${i + 1}`; } - } - for (let i = tabs.length - 2; i < tabs.length; i++) { - if (i % 2 === 0) { - rows[0] += ` tab${i + 1}`; - } else { - rows[1] += ` tab${i + 1}`; - } + }); + + if (tabs.length === 2) { + return "'tab1 tab2'"; } - let middleColumn = 'hSplitter1 '.repeat(tabs.length - 1); if (tabs.length % 2 !== 0) { - rows[1] += ` vSplitter${tabs.length - 1} tab${tabs.length}`; - middleColumn += ` tab${tabs.length}`; + rows[1] += ` tab${tabs.length}`; } - return `'${rows[0].trim()}' '${middleColumn}' '${rows[1].trim()}'`; + + return `'${rows[0].trim()}' '${rows[1].trim()}'`; } /** From fd61d7d1f18c31237b3146cfc02e0641fd8a03e8 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Tue, 17 Sep 2024 18:01:18 +0200 Subject: [PATCH 02/27] Refactor: don't combine horizontal splitters --- src/ZenViewSplitter.mjs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index cafe3ae..3cdea7d 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -379,15 +379,18 @@ var gZenViewSplitter = new class { for (let i = 0; i < rows.length; i++) { let nextRow = ''; finalTemplateAreas += `'`; - let buildingHSplitter = false; for (let j = 0; j < rows[i].length; j++) { const current = rows[i][j]; const rightNeighbor = rows[i][j + 1]; finalTemplateAreas += " " + current; - if (rightNeighbor && rightNeighbor !== current) { - finalTemplateAreas += ` vSplitter${vSplitterCount + 1}`; - vSplitterCount++; - splitters.push({nr: vSplitterCount, orient: 'vertical', gridIdx: j + 1}); + if (rightNeighbor) { + if (rightNeighbor !== current) { + finalTemplateAreas += ` vSplitter${vSplitterCount + 1}`; + vSplitterCount++; + splitters.push({nr: vSplitterCount, orient: 'vertical', gridIdx: j + 1, panels: current + 1}); + } else { + finalTemplateAreas += " " + current; + } } if (!rows[i + 1]) { @@ -396,19 +399,16 @@ var gZenViewSplitter = new class { const underNeighbor = rows[i + 1][j]; if (underNeighbor !== current) { nextRow += ` hSplitter${hSplitterCount + 1}`; - buildingHSplitter = true; + hSplitterCount++; + let panels = 1; + while (rows[i][j - panels] === current) { + panels++; + } + splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1, panels: panels}); } else { nextRow += ' ' + current; - hSplitterCount++; - splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1}); - buildingHSplitter = false; } if (j === rows[i].length - 1) { - if (buildingHSplitter) { - hSplitterCount++; - splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1}); - buildingHSplitter = false; - } continue; } const rightNeighborJoinedWithUnderNeighbor = rightNeighbor === rows[i + 1][j + 1]; From 9eb42269de2847661590c7415e4c0dea56990e82 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:11:33 +0200 Subject: [PATCH 03/27] Add split view rearrangement mode --- src/ZenViewSplitter.mjs | 151 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 3cdea7d..1e43033 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -53,6 +53,8 @@ var gZenViewSplitter = new class { * @param {boolean} forUnsplit - Indicates if the tab is being removed for unsplitting. */ removeTabFromGroup(tab, groupIndex, forUnsplit) { + this.disableTabSwitchView(); + const group = this._data[groupIndex]; const tabIndex = group.tabs.indexOf(tab); group.tabs.splice(tabIndex, 1); @@ -66,6 +68,149 @@ var gZenViewSplitter = new class { } } + enableTabSwitchView() { + if (!this.splitViewActive) return; + this.switchViewEnabled = true; + + const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser); + browsers.forEach(b => { + b.style.pointerEvents = 'none'; + b.style.opacity = '.7'; + }); + + if (!this._thumnailCanvas) { + this._thumnailCanvas = document.createElement("canvas"); + this._thumnailCanvas.width = 280 * devicePixelRatio; + this._thumnailCanvas.height = 140 * devicePixelRatio; + } + + browsers.forEach(b => { + const container = b.closest('.browserContainer'); + + }); + this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart); + this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); + this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); + this.tabBrowserPanel.addEventListener('click', this.disableTabSwitchView, {once: true}); + } + + disableTabSwitchView = () => { + if (!this.switchViewEnabled) return; + this.switchViewEnabled = false; + + this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart); + this.tabBrowserPanel.removeEventListener('dragover', this.onBrowserDragOver); + this.tabBrowserPanel.removeEventListener('drop', this.onBrowserDrop); + const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser); + browsers.forEach(b => { + b.style.pointerEvents = ''; + b.style.opacity = ''; + }); + } + + onBrowserDragStart = (event) => { + let browser = event.target.querySelector('browser'); + if (!browser) { + return; + } + const browserContainer = browser.closest('.browserSidebarContainer'); + event.dataTransfer.setData('text/plain', browserContainer.id); + + let dt = event.dataTransfer; + let scale = window.devicePixelRatio; + let canvas = this._dndCanvas; + if (!canvas) { + this._dndCanvas = canvas = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "canvas" + ); + canvas.style.width = "100%"; + canvas.style.height = "100%"; + } + + canvas.width = 160 * scale; + canvas.height = 90 * scale; + let toDrag = canvas; + let dragImageOffset = -16; + if (gMultiProcessBrowser) { + var context = canvas.getContext("2d"); + context.fillStyle = "white"; + context.fillRect(0, 0, canvas.width, canvas.height); + + let captureListener; + let platform = AppConstants.platform; + // On Windows and Mac we can update the drag image during a drag + // using updateDragImage. On Linux, we can use a panel. + if (platform === "win" || platform === "macosx") { + captureListener = function () { + dt.updateDragImage(canvas, dragImageOffset, dragImageOffset); + }; + } else { + // Create a panel to use it in setDragImage + // which will tell xul to render a panel that follows + // the pointer while a dnd session is on. + if (!this._dndPanel) { + this._dndCanvas = canvas; + this._dndPanel = document.createXULElement("panel"); + this._dndPanel.className = "dragfeedback-tab"; + this._dndPanel.setAttribute("type", "drag"); + let wrapper = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "div" + ); + wrapper.style.width = "160px"; + wrapper.style.height = "90px"; + wrapper.appendChild(canvas); + this._dndPanel.appendChild(wrapper); + document.documentElement.appendChild(this._dndPanel); + } + toDrag = this._dndPanel; + } + // PageThumb is async with e10s but that's fine + // since we can update the image during the dnd. + PageThumbs.captureToCanvas(browser, canvas) + .then(captureListener) + .catch(e => console.error(e)); + } else { + // For the non e10s case we can just use PageThumbs + // sync, so let's use the canvas for setDragImage. + PageThumbs.captureToCanvas(browser, canvas).catch(e => + console.error(e) + ); + dragImageOffset = dragImageOffset * scale; + } + dt.setDragImage(toDrag, dragImageOffset, dragImageOffset); + return true; + } + + onBrowserDragOver = (event) => { + event.preventDefault(); + } + + onBrowserDrop = (event) => { + console.log(event); + const containerId = event.dataTransfer.getData('text/plain'); + + const startTab = gBrowser.getTabForBrowser( + document.getElementById(containerId).querySelector('browser') + ); + const endTab = gBrowser.getTabForBrowser( + event.target.querySelector('browser') + ); + if (!startTab || !endTab) { + return; + } + + const currentData = this._data[this.currentView]; + + const startIdx = currentData.tabs.indexOf(startTab); + const endIdx = currentData.tabs.indexOf(endTab); + + currentData.tabs[startIdx] = endTab; + currentData.tabs[endIdx] = startTab; + this.applyGridToTabs(currentData.tabs, currentData.gridType, gBrowser.selectedTab); + } + /** * Resets the state of a tab. * @@ -188,6 +333,10 @@ var gZenViewSplitter = new class { return Services.prefs.getIntPref('zen.splitView.min-resize-width'); } + get splitViewActive() { + return this.currentView >= 0; + } + /** * Splits a link in a new tab. */ @@ -531,7 +680,7 @@ var gZenViewSplitter = new class { * @param {Event} event - The event. */ handleTabEvent = (event) => { - if (event.type === 'mouseover' && !this.canChangeTabOnHover) { + if (this.switchViewEnabled || (event.type === 'mouseover' && !this.canChangeTabOnHover)) { return; } const container = event.currentTarget; From 966ada7f0d5208403a68f591c634262a63292fdf Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:45:21 +0200 Subject: [PATCH 04/27] Use layoutTree to construct splitview. No longer make use of a grid. --- src/ZenViewSplitter.mjs | 224 +++++++++++----------------------------- 1 file changed, 63 insertions(+), 161 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 1e43033..b23e93b 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -1,3 +1,19 @@ + +class SplitNode { + constructor(direction, widthInParent = 100, heightInParent = 100) { + this.direction = direction; // row or column + this.children = []; + this.widthInParent = widthInParent; + this.heightInParent = heightInParent; + } +} +class SplitLeafNode { + constructor(tabContainerId,widthInParent = 100, heightInParent = 100) { + this.id = tabContainerId; + this.widthInParent = widthInParent; + this.heightInParent = heightInParent; + } +} var gZenViewSplitter = new class { constructor() { this._data = []; @@ -53,8 +69,6 @@ var gZenViewSplitter = new class { * @param {boolean} forUnsplit - Indicates if the tab is being removed for unsplitting. */ removeTabFromGroup(tab, groupIndex, forUnsplit) { - this.disableTabSwitchView(); - const group = this._data[groupIndex]; const tabIndex = group.tabs.indexOf(tab); group.tabs.splice(tabIndex, 1); @@ -69,33 +83,18 @@ var gZenViewSplitter = new class { } enableTabSwitchView() { - if (!this.splitViewActive) return; - this.switchViewEnabled = true; - - const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser); - browsers.forEach(b => { - b.style.pointerEvents = 'none'; - b.style.opacity = '.7'; - }); - if (!this._thumnailCanvas) { this._thumnailCanvas = document.createElement("canvas"); this._thumnailCanvas.width = 280 * devicePixelRatio; this._thumnailCanvas.height = 140 * devicePixelRatio; } - browsers.forEach(b => { - const container = b.closest('.browserContainer'); - - }); this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); - this.tabBrowserPanel.addEventListener('click', this.disableTabSwitchView, {once: true}); } disableTabSwitchView = () => { - if (!this.switchViewEnabled) return; this.switchViewEnabled = false; this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart); @@ -109,6 +108,7 @@ var gZenViewSplitter = new class { } onBrowserDragStart = (event) => { + if (!this.splitViewActive) return; let browser = event.target.querySelector('browser'); if (!browser) { return; @@ -184,10 +184,12 @@ var gZenViewSplitter = new class { } onBrowserDragOver = (event) => { + if (!this.splitViewActive) return; event.preventDefault(); } onBrowserDrop = (event) => { + if (!this.splitViewActive) return; console.log(event); const containerId = event.dataTransfer.getData('text/plain'); @@ -474,107 +476,68 @@ var gZenViewSplitter = new class { this.setTabsDocShellState(splitData.tabs, true); this.updateSplitViewButton(false); - this.applyGridToTabs(splitData.tabs, gridType, activeTab); - const gridAreas = this.calculateGridAreas(splitData.tabs, gridType); - this.applyGridLayout(gridAreas); + this.applyGridToTabs(splitData.tabs, activeTab); + + const layout = this.calculateLayoutTree(splitData.tabs, gridType); + splitData.layoutTree = layout; + this.applyGridLayout(layout); + } + + calculateLayoutTree(tabs, gridType) { + const containerIds = tabs.map(t => t.linkedBrowser.closest('.browserSidebarContainer').id); + let rootNode; + if (gridType === 'vsep') { + rootNode = new SplitNode('row'); + rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100 / tabs.length, 100)); + } else if (gridType === 'hsep') { + rootNode = new SplitNode('column'); + rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100, 100 / tabs.length)); + } + + return rootNode; } /** * Applies the grid layout to the tabs. * * @param {Tab[]} tabs - The tabs to apply the grid layout to. - * @param {string} gridType - The type of grid layout. * @param {Tab} activeTab - The active tab. */ - applyGridToTabs(tabs, gridType, activeTab) { + applyGridToTabs(tabs,activeTab) { tabs.forEach((tab, index) => { tab.splitView = true; const container = tab.linkedBrowser.closest('.browserSidebarContainer'); - this.styleContainer(container, tab === activeTab, index, gridType); + this.styleContainer(container, tab === activeTab, index); }); } /** * Apply grid layout to tabBrowserPanel * - * @param gridTemplateAreas + * @param {SplitNode} splitNode SplitNode + * @param {{top, bottom, left, right}} nodeRootPosition position of node relative to root of split */ - applyGridLayout(gridTemplateAreas) { - const finalLayout = this.calculateLayoutWithSplitters(gridTemplateAreas); + applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 100, left: 0, right: 100}) { + const rootToNodeWidthRatio = (nodeRootPosition.right - nodeRootPosition.left) / 100; + const rootToNodeHeightRatio = (nodeRootPosition.bottom - nodeRootPosition.top) / 100; - this.tabBrowserPanel.style.gridTemplateAreas = finalLayout.templateAreas; - this.removeSplitters(); - finalLayout.splitters.forEach(s => this.insertSplitter(s.nr, s.orient, s.gridIdx)); - - this.updateGridDimensions(this._data[this.currentView], finalLayout.nrOfRows, finalLayout.nrOfColumns); - this.applyGridSizes(); - } - - - /** - * Takes a gridLayout and returns a new one with splitters. - * - * @param girdTemplateAreas - * @returns {{splitters: *[], templateAreas: string, nrOfColumns: number, nrOfRows: number}} - */ - calculateLayoutWithSplitters(girdTemplateAreas) { - const rows = girdTemplateAreas.split(/['"]\s+['"]/).map(r => r.replaceAll(/['"]/g, '').split(/\s+/)); - - let finalTemplateAreas = ''; - const splitters = []; - - let vSplitterCount = 0; - let hSplitterCount = 0; - for (let i = 0; i < rows.length; i++) { - let nextRow = ''; - finalTemplateAreas += `'`; - for (let j = 0; j < rows[i].length; j++) { - const current = rows[i][j]; - const rightNeighbor = rows[i][j + 1]; - finalTemplateAreas += " " + current; - if (rightNeighbor) { - if (rightNeighbor !== current) { - finalTemplateAreas += ` vSplitter${vSplitterCount + 1}`; - vSplitterCount++; - splitters.push({nr: vSplitterCount, orient: 'vertical', gridIdx: j + 1, panels: current + 1}); - } else { - finalTemplateAreas += " " + current; - } - } - - if (!rows[i + 1]) { - continue; - } - const underNeighbor = rows[i + 1][j]; - if (underNeighbor !== current) { - nextRow += ` hSplitter${hSplitterCount + 1}`; - hSplitterCount++; - let panels = 1; - while (rows[i][j - panels] === current) { - panels++; - } - splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1, panels: panels}); - } else { - nextRow += ' ' + current; - } - if (j === rows[i].length - 1) { - continue; - } - const rightNeighborJoinedWithUnderNeighbor = rightNeighbor === rows[i + 1][j + 1]; - if (rightNeighborJoinedWithUnderNeighbor && (underNeighbor === current)) { - if (current === rightNeighbor) nextRow += ' ' + current; // square - else nextRow += ` vSplitter${vSplitterCount}`; - } else { - nextRow += ` hSplitter${hSplitterCount + 1}`; - } + let leftOffset = nodeRootPosition.left; + let topOffset = nodeRootPosition.top; + splitNode.children.forEach((childNode) => { + const childRootPosition = {top: topOffset, right: 100 - (leftOffset + childNode.widthInParent * rootToNodeWidthRatio), bottom: 100 - (topOffset + childNode.heightInParent * rootToNodeHeightRatio), left: leftOffset}; + if (!childNode.children) { + const browserContainer = document.getElementById(childNode.id); + browserContainer.style.inset = `${childRootPosition.top}% ${childRootPosition.right}% ${childRootPosition.bottom}% ${childRootPosition.left}%`; + } else { + this.applyGridLayout(childNode, childRootPosition); } - finalTemplateAreas += `'`; - if (nextRow) { - finalTemplateAreas += `'${nextRow}'`; - } - } - return {templateAreas: finalTemplateAreas, splitters: splitters, nrOfRows: rows.length, nrOfColumns: rows[0]?.length || 0}; + if (splitNode.direction === 'column') { + topOffset += childNode.heightInParent * rootToNodeWidthRatio; + } else { + leftOffset += childNode.widthInParent * rootToNodeHeightRatio; + } + }); } insertSplitter(nr, orient, gridIdx) { @@ -587,82 +550,20 @@ var gZenViewSplitter = new class { this.tabBrowserPanel.insertAdjacentElement("afterbegin", splitter); } - /** - * Initialize splitData with default widths and heights if dimensions of grid don't match - * - * @param splitData the splits data - * @param nrOfRows number of rows in the grid (excluding splitters) - * @param nrOfColumns number of columns in the grid (excluding splitters) - */ - updateGridDimensions(splitData, nrOfRows, nrOfColumns) { - if (splitData.widths?.length !== nrOfColumns || splitData.heights?.length !== nrOfRows) { - splitData.widths = Array( nrOfColumns).fill(100 / nrOfColumns); - splitData.heights = Array(nrOfRows).fill(100 / nrOfRows); - } - } - removeSplitters() { [...gZenViewSplitter.tabBrowserPanel.children] .filter(e => e.classList.contains('zen-split-view-splitter')) .forEach(s => s.remove()); } - /** - * Calculates the grid areas for the tabs. - * - * @param {Tab[]} tabs - The tabs. - * @param {string} gridType - The type of grid layout. - * @returns {string} The calculated grid areas. - */ - calculateGridAreas(tabs, gridType) { - if (gridType === 'grid') { - return this.calculateGridAreasForGrid(tabs); - } - if (gridType === 'vsep') { - return `'${tabs.map((_, j) => `tab${j + 1}`).join(' ')}'`; - } - if (gridType === 'hsep') { - return tabs.map((_, j) => `'tab${j + 1}'`).join(' '); - } - return ''; - } - - /** - * Calculates the grid areas for the tabs in a grid layout. - * - * @param {Tab[]} tabs - The tabs. - * @returns {string} The calculated grid areas. - */ - calculateGridAreasForGrid(tabs) { - const rows = ['', '']; - tabs.forEach((_, i) => { - if (i % 2 === 0) { - rows[0] += ` tab${i + 1}`; - } else { - rows[1] += ` tab${i + 1}`; - } - }); - - if (tabs.length === 2) { - return "'tab1 tab2'"; - } - - if (tabs.length % 2 !== 0) { - rows[1] += ` tab${tabs.length}`; - } - - return `'${rows[0].trim()}' '${rows[1].trim()}'`; - } - /** * Styles the container for a tab. * * @param {Element} container - The container element. * @param {boolean} isActive - Indicates if the tab is active. * @param {number} index - The index of the tab. - * @param {string} gridType - The type of grid layout. */ - styleContainer(container, isActive, index, gridType) { + styleContainer(container, isActive, index) { container.removeAttribute('zen-split-active'); if (isActive) { container.setAttribute('zen-split-active', 'true'); @@ -671,7 +572,8 @@ var gZenViewSplitter = new class { container.addEventListener('click', this.handleTabEvent); container.addEventListener('mouseover', this.handleTabEvent); - container.style.gridArea = `tab${index + 1}`; + container.style.position = 'absolute'; + container.setAttribute('zen-split-id', index); } /** From d853a8d08d01aa25dc659f06515930a9d7e52925 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:12:43 +0200 Subject: [PATCH 05/27] Fixes for splitview with layoutTree, working gridmode --- src/ZenViewSplitter.mjs | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index b23e93b..96434c9 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -228,8 +228,6 @@ var gZenViewSplitter = new class { if (!forUnsplit) { tab.linkedBrowser.docShellIsActive = false; - } else { - container.style.gridArea = '1 / 1'; } } @@ -489,9 +487,20 @@ var gZenViewSplitter = new class { if (gridType === 'vsep') { rootNode = new SplitNode('row'); rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100 / tabs.length, 100)); - } else if (gridType === 'hsep') { + } else if (gridType === 'hsep' || (tabs.length === 2 && gridType === 'grid')) { rootNode = new SplitNode('column'); rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100, 100 / tabs.length)); + } else if (gridType === 'grid') { + rootNode = new SplitNode('row'); + const rowWidth = 100 / Math.ceil(tabs.length / 2); + for (let i = 0; i < tabs.length - 1; i += 2) { + const columnNode = new SplitNode('column', rowWidth, 100); + columnNode.children = [new SplitLeafNode(containerIds[i], 100, 50), new SplitLeafNode(containerIds[i + 1], 100, 50)]; + rootNode.children.push(columnNode); + } + if (tabs.length % 2 !== 0) { + rootNode.children.push(new SplitLeafNode(containerIds[tabs.length - 1], rowWidth, 100)); + } } return rootNode; @@ -517,25 +526,26 @@ var gZenViewSplitter = new class { * @param {SplitNode} splitNode SplitNode * @param {{top, bottom, left, right}} nodeRootPosition position of node relative to root of split */ - applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 100, left: 0, right: 100}) { - const rootToNodeWidthRatio = (nodeRootPosition.right - nodeRootPosition.left) / 100; - const rootToNodeHeightRatio = (nodeRootPosition.bottom - nodeRootPosition.top) / 100; + applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 0, left: 0, right: 0}) { + if (!splitNode.children) { + const browserContainer = document.getElementById(splitNode.id); + browserContainer.style.inset = `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; + return; + } + + const rootToNodeWidthRatio = ((100 - nodeRootPosition.right) - nodeRootPosition.left) / 100; + const rootToNodeHeightRatio = ((100 - nodeRootPosition.bottom) - nodeRootPosition.top) / 100; let leftOffset = nodeRootPosition.left; let topOffset = nodeRootPosition.top; splitNode.children.forEach((childNode) => { const childRootPosition = {top: topOffset, right: 100 - (leftOffset + childNode.widthInParent * rootToNodeWidthRatio), bottom: 100 - (topOffset + childNode.heightInParent * rootToNodeHeightRatio), left: leftOffset}; - if (!childNode.children) { - const browserContainer = document.getElementById(childNode.id); - browserContainer.style.inset = `${childRootPosition.top}% ${childRootPosition.right}% ${childRootPosition.bottom}% ${childRootPosition.left}%`; - } else { - this.applyGridLayout(childNode, childRootPosition); - } + this.applyGridLayout(childNode, childRootPosition); if (splitNode.direction === 'column') { - topOffset += childNode.heightInParent * rootToNodeWidthRatio; + topOffset += childNode.heightInParent * rootToNodeHeightRatio; } else { - leftOffset += childNode.widthInParent * rootToNodeHeightRatio; + leftOffset += childNode.widthInParent * rootToNodeWidthRatio; } }); } @@ -571,9 +581,6 @@ var gZenViewSplitter = new class { container.setAttribute('zen-split-anim', 'true'); container.addEventListener('click', this.handleTabEvent); container.addEventListener('mouseover', this.handleTabEvent); - - container.style.position = 'absolute'; - container.setAttribute('zen-split-id', index); } /** From a102dd4c3796aa294b1e817e4f183b4ce3919d8b Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:15:35 +0200 Subject: [PATCH 06/27] Insert splitters in right location for splitview. --- src/ZenViewSplitter.mjs | 73 ++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 96434c9..cf53f09 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -3,6 +3,7 @@ class SplitNode { constructor(direction, widthInParent = 100, heightInParent = 100) { this.direction = direction; // row or column this.children = []; + this.splitters = []; this.widthInParent = widthInParent; this.heightInParent = heightInParent; } @@ -22,6 +23,8 @@ var gZenViewSplitter = new class { this.__modifierElement = null; this.__hasSetMenuListener = false; this.canChangeTabOnHover = null; + this.splitterBox = null; + this._splitNodeToSplitters = new Map(); XPCOMUtils.defineLazyPreferenceGetter( this, @@ -30,6 +33,12 @@ var gZenViewSplitter = new class { false ); + ChromeUtils.defineLazyGetter( + this, + 'splitterBox', + () => document.getElementById('zen-splitview-splitterbox') + ); + window.addEventListener('TabClose', this.handleTabClose.bind(this)); this.initializeContextMenu(); this.insertPageActionButton(); @@ -380,8 +389,15 @@ var gZenViewSplitter = new class { async onLocationChange(browser) { const tab = window.gBrowser.getTabForBrowser(browser); this.updateSplitViewButton(!tab?.splitView); - if (tab) { - this.updateSplitView(tab); + if (tab && this.splitViewActive) { + this._data[this.currentView].tabs.forEach(t => { + const container = t.linkedBrowser.closest('.browserSidebarContainer'); + if (t === tab) { + container.setAttribute('zen-split-active', true); + } else if (container.hasAttribute('zen-split-active')) { + container.removeAttribute('zen-split-active'); + } + }); tab.linkedBrowser.docShellIsActive = true; } } @@ -413,12 +429,13 @@ var gZenViewSplitter = new class { } } - this._data.push({ + const splitData = { tabs, - gridType: gridType, - }); + gridType + } + this._data.push(splitData); window.gBrowser.selectedTab = tabs[0]; - this.updateSplitView(tabs[0]); + this.activateSplitView(splitData, tabs[0]); } /** @@ -526,7 +543,7 @@ var gZenViewSplitter = new class { * @param {SplitNode} splitNode SplitNode * @param {{top, bottom, left, right}} nodeRootPosition position of node relative to root of split */ - applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 0, left: 0, right: 0}) { + applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 0, left: 0, right: 0}) { if (!splitNode.children) { const browserContainer = document.getElementById(splitNode.id); browserContainer.style.inset = `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; @@ -536,9 +553,11 @@ var gZenViewSplitter = new class { const rootToNodeWidthRatio = ((100 - nodeRootPosition.right) - nodeRootPosition.left) / 100; const rootToNodeHeightRatio = ((100 - nodeRootPosition.bottom) - nodeRootPosition.top) / 100; + const currentSplitters = this.getSplitters(splitNode); + let leftOffset = nodeRootPosition.left; let topOffset = nodeRootPosition.top; - splitNode.children.forEach((childNode) => { + splitNode.children.forEach((childNode, i) => { const childRootPosition = {top: topOffset, right: 100 - (leftOffset + childNode.widthInParent * rootToNodeWidthRatio), bottom: 100 - (topOffset + childNode.heightInParent * rootToNodeHeightRatio), left: leftOffset}; this.applyGridLayout(childNode, childRootPosition); @@ -547,17 +566,47 @@ var gZenViewSplitter = new class { } else { leftOffset += childNode.widthInParent * rootToNodeWidthRatio; } + + // splitters get inserted by parent + const isLastNode = i === (splitNode.children.length - 1); + if (!isLastNode) { + let splitter = currentSplitters?.[i]; + if (!splitter) { + splitter = this.createSplitter(splitNode.direction === 'column' ? 'horizontal' : 'vertical', childNode, i); + } + if (splitNode.direction === 'column') { + splitter.style.inset = `${100 - childRootPosition.bottom}% ${childRootPosition.right}% 0% ${childRootPosition.left}%`; + } else { + splitter.style.inset = `${childRootPosition.top}% 0% ${childRootPosition.bottom}% ${100 - childRootPosition.right}%`; + } + } }); } - insertSplitter(nr, orient, gridIdx) { + /** + * + * @param {String} orient + * @param {SplitNode} parentNode + * @param {Number} idx + */ + createSplitter(orient, parentNode, idx) { const splitter = document.createElement('div'); splitter.className = 'zen-split-view-splitter'; splitter.setAttribute('orient', orient); - splitter.setAttribute('gridIdx', gridIdx); - splitter.style.gridArea = `${orient === 'vertical' ? 'v' : 'h'}Splitter${nr}`; + splitter.setAttribute('gridIdx', idx); + this.splitterBox.insertAdjacentElement("afterbegin", splitter); + + splitter.parentSplitNode = parentNode; + if (!this._splitNodeToSplitters.has(parentNode)) { + this._splitNodeToSplitters.set(parentNode, []); + } + this._splitNodeToSplitters.get(parentNode).push(splitter); + splitter.addEventListener('mousedown', this.handleSplitterMouseDown); - this.tabBrowserPanel.insertAdjacentElement("afterbegin", splitter); + return splitter; + } + getSplitters(parentNode) { + return this._splitNodeToSplitters.get(parentNode); } removeSplitters() { From 28baa2f9d480afb69a0e228f0d44b2524125c8ac Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:00:37 +0200 Subject: [PATCH 07/27] Reimplement splitview resize for treelayout splitview. --- src/ZenViewSplitter.mjs | 96 +++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index cf53f09..512c3a8 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -6,6 +6,7 @@ class SplitNode { this.splitters = []; this.widthInParent = widthInParent; this.heightInParent = heightInParent; + this.positionToRoot = null; // position relative to root node } } class SplitLeafNode { @@ -33,6 +34,13 @@ var gZenViewSplitter = new class { false ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + 'minResizeWidth', + 'zen.splitView.min-resize-width', + 7 + ); + ChromeUtils.defineLazyGetter( this, 'splitterBox', @@ -338,10 +346,6 @@ var gZenViewSplitter = new class { return this._tabBrowserPanel; } - get minResizeWidth() { - return Services.prefs.getIntPref('zen.splitView.min-resize-width'); - } - get splitViewActive() { return this.currentView >= 0; } @@ -541,9 +545,12 @@ var gZenViewSplitter = new class { * Apply grid layout to tabBrowserPanel * * @param {SplitNode} splitNode SplitNode - * @param {{top, bottom, left, right}} nodeRootPosition position of node relative to root of split */ - applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 0, left: 0, right: 0}) { + applyGridLayout(splitNode) { + if (!splitNode.positionToRoot) { + splitNode.positionToRoot = {top: 0, bottom: 0, left: 0, right: 0}; + } + const nodeRootPosition = splitNode.positionToRoot; if (!splitNode.children) { const browserContainer = document.getElementById(splitNode.id); browserContainer.style.inset = `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; @@ -559,7 +566,8 @@ var gZenViewSplitter = new class { let topOffset = nodeRootPosition.top; splitNode.children.forEach((childNode, i) => { const childRootPosition = {top: topOffset, right: 100 - (leftOffset + childNode.widthInParent * rootToNodeWidthRatio), bottom: 100 - (topOffset + childNode.heightInParent * rootToNodeHeightRatio), left: leftOffset}; - this.applyGridLayout(childNode, childRootPosition); + childNode.positionToRoot = childRootPosition; + this.applyGridLayout(childNode); if (splitNode.direction === 'column') { topOffset += childNode.heightInParent * rootToNodeHeightRatio; @@ -572,7 +580,7 @@ var gZenViewSplitter = new class { if (!isLastNode) { let splitter = currentSplitters?.[i]; if (!splitter) { - splitter = this.createSplitter(splitNode.direction === 'column' ? 'horizontal' : 'vertical', childNode, i); + splitter = this.createSplitter(splitNode.direction === 'column' ? 'horizontal' : 'vertical', splitNode, i); } if (splitNode.direction === 'column') { splitter.style.inset = `${100 - childRootPosition.bottom}% ${childRootPosition.right}% 0% ${childRootPosition.left}%`; @@ -610,9 +618,10 @@ var gZenViewSplitter = new class { } removeSplitters() { - [...gZenViewSplitter.tabBrowserPanel.children] - .filter(e => e.classList.contains('zen-split-view-splitter')) - .forEach(s => s.remove()); + Array.from(this._splitNodeToSplitters.values()).forEach(s => { + s.remove; + }); + this._splitNodeToSplitters.clear(); } /** @@ -649,36 +658,38 @@ var gZenViewSplitter = new class { }; handleSplitterMouseDown = (event) => { - const splitData = this._data[this.currentView]; - const isVertical = event.target.getAttribute('orient') === 'vertical'; - const dimension = isVertical ? 'widths' : 'heights'; + const dimension = isVertical ? 'width' : 'height'; + const dimensionInParent = dimension + 'InParent'; const clientAxis = isVertical ? 'screenX' : 'screenY'; - const gridIdx = event.target.getAttribute('gridIdx'); - let prevPosition = event[clientAxis]; + const gridIdx = parseInt(event.target.getAttribute('gridIdx')); + const startPosition = event[clientAxis]; + const splitNode = event.target.parentSplitNode; + let rootToNodeSize; + if (isVertical) rootToNodeSize = 100 / (100 - splitNode.positionToRoot.right - splitNode.positionToRoot.left); + else rootToNodeSize = 100 / (100 - splitNode.positionToRoot.bottom - splitNode.positionToRoot.top); + const originalSizes = splitNode.children.map(c => c[dimensionInParent]); + const dragFunc = (dEvent) => { requestAnimationFrame(() => { - const movementX = dEvent[clientAxis] - prevPosition; - let percentageChange = (movementX / this.tabBrowserPanel.getBoundingClientRect()[isVertical ? 'width' : 'height']) * 100; - const currentSize = splitData[dimension][gridIdx - 1]; - const neighborSize = splitData[dimension][gridIdx]; - if (currentSize < this.minResizeWidth && neighborSize < this.minResizeWidth) { - return; + const movement = dEvent[clientAxis] - startPosition; + let movementPercent = (movement / this.tabBrowserPanel.getBoundingClientRect()[dimension] * rootToNodeSize) * 100; + + let reducingMovement = Math.max(movementPercent, -movementPercent); + for (let i = gridIdx + (movementPercent < 0 ? 0 : 1); 0 <= i && i < originalSizes.length; i += movementPercent < 0 ? -1 : 1) { + const current = originalSizes[i]; + const newSize = Math.max(this.minResizeWidth, current - reducingMovement); + splitNode.children[i][dimensionInParent] = newSize; + const amountReduced = current - newSize; + reducingMovement -= amountReduced; + if (reducingMovement <= 0) break; } - let max = false; - if (currentSize + percentageChange < this.minResizeWidth) { - percentageChange = this.minResizeWidth - currentSize; - max = true; - } else if (neighborSize - percentageChange < this.minResizeWidth) { - percentageChange = neighborSize - this.minResizeWidth; - max = true; - } - splitData[dimension][gridIdx - 1] += percentageChange; - splitData[dimension][gridIdx] -= percentageChange; - this.applyGridSizes(); - if (!max) prevPosition = dEvent[clientAxis]; + const increasingMovement = Math.max(movementPercent, - movementPercent) - reducingMovement; + const increaseIndex = gridIdx + (movementPercent < 0 ? 1 : 0); + splitNode.children[increaseIndex][dimensionInParent] = originalSizes[increaseIndex] + increasingMovement; + this.applyGridLayout(splitNode); }); } const stopListeners = () => { @@ -691,23 +702,6 @@ var gZenViewSplitter = new class { setCursor(isVertical ? 'ew-resize' : 'n-resize'); } - /** - * Applies the grid column and row sizes - */ - applyGridSizes() { - const splitData = this._data[this.currentView]; - const columnGap = 'var(--zen-split-column-gap)'; - const rowGap = 'var(--zen-split-row-gap)'; - - this.tabBrowserPanel.style.gridTemplateColumns = splitData.widths.slice(0, -1).map( - (w) => `calc(${w}% - ${columnGap} * ${splitData.widths.length - 1}/${splitData.widths.length}) ${columnGap}` - ).join(' '); - - this.tabBrowserPanel.style.gridTemplateRows = splitData.heights.slice(0, -1).map( - (h) => `calc(${h}% - ${rowGap} * ${splitData.heights.length - 1}/${splitData.heights.length}) ${rowGap}` - ).join(' '); - } - /** * Sets the docshell state for the tabs. * From 5020e59f8e90e7652f40a82caec8f1b3acb01b33 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:28:09 +0200 Subject: [PATCH 08/27] Fix splitview wrong size bug --- src/ZenViewSplitter.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 512c3a8..08de068 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -673,6 +673,7 @@ var gZenViewSplitter = new class { const dragFunc = (dEvent) => { requestAnimationFrame(() => { + originalSizes.forEach((s, i) => splitNode.children[i][dimensionInParent] = s); // reset changes const movement = dEvent[clientAxis] - startPosition; let movementPercent = (movement / this.tabBrowserPanel.getBoundingClientRect()[dimension] * rootToNodeSize) * 100; @@ -692,14 +693,13 @@ var gZenViewSplitter = new class { this.applyGridLayout(splitNode); }); } - const stopListeners = () => { - removeEventListener('mousemove', dragFunc); - removeEventListener('mouseup', stopListeners); - setCursor('auto'); - } - addEventListener('mousemove', dragFunc); - addEventListener('mouseup', stopListeners); + setCursor(isVertical ? 'ew-resize' : 'n-resize'); + document.addEventListener('mousemove', dragFunc); + document.addEventListener('mouseup', () => { + removeEventListener('mousemove', dragFunc); + setCursor('auto'); + }, {once: true}); } /** From ae18457baa7ce69616a5aaed3f3012a7f4caebd2 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:53:39 +0200 Subject: [PATCH 09/27] Add tabRemove functionality for reworked splitview. --- src/ZenViewSplitter.mjs | 150 +++++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 32 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 08de068..60cd12e 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -1,12 +1,46 @@ - class SplitNode { + /** + * @type {number} + * @type + */ + splitters = []; + widthInParent ; + /** + * @type {number} + */ + heightInParent ; + /** + * @type {Object} + */ + positionToRoot; // position relative to root node + /** + * @type {SplitNode} + */ + parent; + /** + * @type {string} + */ + direction; + constructor(direction, widthInParent = 100, heightInParent = 100) { - this.direction = direction; // row or column - this.children = []; - this.splitters = []; this.widthInParent = widthInParent; this.heightInParent = heightInParent; - this.positionToRoot = null; // position relative to root node + this.direction = direction; // row or column + this._children = []; + } + + set children(children) { + if (children) children.forEach(c => c.parent = this); + this._children = children; + } + + get children() { + return this._children; + } + + addChild(child) { + child.parent = this; + this._children.push(child); } } class SplitLeafNode { @@ -26,6 +60,7 @@ var gZenViewSplitter = new class { this.canChangeTabOnHover = null; this.splitterBox = null; this._splitNodeToSplitters = new Map(); + this._containerToSplitNode = new Map(); XPCOMUtils.defineLazyPreferenceGetter( this, @@ -95,10 +130,56 @@ var gZenViewSplitter = new class { if (group.tabs.length < 2) { this.removeGroup(groupIndex); } else { - this.updateSplitView(group.tabs[group.tabs.length - 1]); + const node = this._containerToSplitNode.get(tab.linkedBrowser.closest('.browserSidebarContainer')) + this.applyRemoveNode(node); } } + /** + * Remove a SplitNode from its tree and the view + * @param {SplitNode} toRemove + */ + applyRemoveNode(toRemove) { + this._removeNodeSplitters(toRemove, true); + const parent = toRemove.parent; + const childIndex = parent.children.indexOf(toRemove); + parent.children.splice(childIndex, 1); + if (parent.children.length !== 1) { + const nodeToResize = parent.children[Math.max(0, childIndex - 1)]; + if (parent.direction === 'column') nodeToResize.heightInParent += toRemove.heightInParent; + else nodeToResize.widthInParent += toRemove.widthInParent; + this.applyGridLayout(parent); + return; + } + // node that is not a leaf cannot have less than 2 children, this makes for better resizing + // node takes place of parent + const leftOverChild = parent.children[0]; + leftOverChild.widthInParent = parent.widthInParent; + leftOverChild.heightInParent = parent.heightInParent; + if (parent.parent) { + leftOverChild.parent = parent.parent; + parent.parent.children[parent.parent.children.indexOf(parent)] = leftOverChild; + this._removeNodeSplitters(parent, false); + } else { + const viewData = Object.entries(this._data).find(s => s.layoutTree === parent); + viewData.layoutTree = parent; + parent.positionToRoot = null; + } + this.applyGridLayout(parent.parent, true); + } + + /** + * @param node + * @param {boolean} recursive + * @private + */ + _removeNodeSplitters(node, recursive ) { + this.getSplitters(node)?.forEach(s => s.remove()); + if (!recursive) return; + this._splitNodeToSplitters.delete(node); + if (node.children) node.children.forEach(c => this._removeNodeSplitters(c)); + } + enableTabSwitchView() { if (!this._thumnailCanvas) { this._thumnailCanvas = document.createElement("canvas"); @@ -240,8 +321,7 @@ var gZenViewSplitter = new class { tab.splitView = false; tab.linkedBrowser.zenModeActive = false; const container = tab.linkedBrowser.closest('.browserSidebarContainer'); - container.removeAttribute('zen-split'); - container.removeAttribute('style'); + this.resetContainerStyle(container); if (!forUnsplit) { tab.linkedBrowser.docShellIsActive = false; @@ -255,7 +335,7 @@ var gZenViewSplitter = new class { */ removeGroup(groupIndex) { if (this.currentView === groupIndex) { - this.resetSplitView(false); + this.resetSplitView(); } for (const tab of this._data[groupIndex].tabs) { this.resetTabState(tab, true); @@ -273,12 +353,9 @@ var gZenViewSplitter = new class { } } this.removeSplitters(); + this.tabBrowserPanel.removeAttribute('zen-split-view'); this.currentView = -1; - this.tabBrowserPanel.removeAttribute('zen-split-view'); - this.tabBrowserPanel.style.gridTemplateAreas = ''; - this.tabBrowserPanel.style.gridTemplateColumns = ''; - this.tabBrowserPanel.style.gridTemplateRows = ''; } /** @@ -453,6 +530,7 @@ var gZenViewSplitter = new class { this.updateSplitViewButton(true); if (this.currentView >= 0) { this.deactivateSplitView(); + return; } if (!splitData) { return; @@ -473,9 +551,6 @@ var gZenViewSplitter = new class { container.removeEventListener('mouseover', this.handleTabEvent); } this.tabBrowserPanel.removeAttribute('zen-split-view'); - this.tabBrowserPanel.style.gridTemplateAreas = ''; - this.tabBrowserPanel.style.gridTemplateColumns = ''; - this.tabBrowserPanel.style.gridTemplateRows = ''; this.setTabsDocShellState(this._data[this.currentView].tabs, false); this.currentView = -1; } @@ -517,10 +592,10 @@ var gZenViewSplitter = new class { for (let i = 0; i < tabs.length - 1; i += 2) { const columnNode = new SplitNode('column', rowWidth, 100); columnNode.children = [new SplitLeafNode(containerIds[i], 100, 50), new SplitLeafNode(containerIds[i + 1], 100, 50)]; - rootNode.children.push(columnNode); + rootNode.addChild(columnNode); } if (tabs.length % 2 !== 0) { - rootNode.children.push(new SplitLeafNode(containerIds[tabs.length - 1], rowWidth, 100)); + rootNode.addChild(new SplitLeafNode(containerIds[tabs.length - 1], rowWidth, 100)); } } @@ -554,13 +629,15 @@ var gZenViewSplitter = new class { if (!splitNode.children) { const browserContainer = document.getElementById(splitNode.id); browserContainer.style.inset = `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; + this._containerToSplitNode.set(browserContainer, splitNode); return; } const rootToNodeWidthRatio = ((100 - nodeRootPosition.right) - nodeRootPosition.left) / 100; const rootToNodeHeightRatio = ((100 - nodeRootPosition.bottom) - nodeRootPosition.top) / 100; - const currentSplitters = this.getSplitters(splitNode); + const splittersNeeded = splitNode.children.length - 1; + const currentSplitters = this.getSplitters(splitNode, splittersNeeded); let leftOffset = nodeRootPosition.left; let topOffset = nodeRootPosition.top; @@ -575,13 +652,8 @@ var gZenViewSplitter = new class { leftOffset += childNode.widthInParent * rootToNodeWidthRatio; } - // splitters get inserted by parent - const isLastNode = i === (splitNode.children.length - 1); - if (!isLastNode) { - let splitter = currentSplitters?.[i]; - if (!splitter) { - splitter = this.createSplitter(splitNode.direction === 'column' ? 'horizontal' : 'vertical', splitNode, i); - } + if (i < splittersNeeded) { + const splitter = currentSplitters[i]; if (splitNode.direction === 'column') { splitter.style.inset = `${100 - childRootPosition.bottom}% ${childRootPosition.right}% 0% ${childRootPosition.left}%`; } else { @@ -613,14 +685,28 @@ var gZenViewSplitter = new class { splitter.addEventListener('mousedown', this.handleSplitterMouseDown); return splitter; } - getSplitters(parentNode) { - return this._splitNodeToSplitters.get(parentNode); + + /** + * @param {SplitNode} parentNode + * @param {number|undefined} splittersNeeded if provided the amount of splitters for node will be adjusted to match + */ + getSplitters(parentNode, splittersNeeded) { + let currentSplitters = this._splitNodeToSplitters.get(parentNode) || []; + if (!splittersNeeded || currentSplitters.length === splittersNeeded) return currentSplitters; + for (let i = currentSplitters?.length || 0; i < splittersNeeded; i++) { + currentSplitters.push( + this.createSplitter(parentNode.direction === 'column' ? 'horizontal' : 'vertical', parentNode, i) + ); + } + if (currentSplitters.length > splittersNeeded) { + currentSplitters.slice(currentSplitters - splittersNeeded).forEach(s => s.remove()); + currentSplitters = currentSplitters.slice(0, splittersNeeded); + } + return currentSplitters; } removeSplitters() { - Array.from(this._splitNodeToSplitters.values()).forEach(s => { - s.remove; - }); + Array.from(this._splitNodeToSplitters.values()).forEach(s => s.remove()); this._splitNodeToSplitters.clear(); } @@ -736,7 +822,7 @@ var gZenViewSplitter = new class { resetContainerStyle(container) { container.removeAttribute('zen-split-active'); container.classList.remove('deck-selected'); - container.style.gridArea = ''; + container.style.inset = ''; } /** From 5fb767ef7200e2f3264998959b2a9254d210d258 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:31:45 +0200 Subject: [PATCH 10/27] splitView Tabremove fixes. --- src/ZenViewSplitter.mjs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 60cd12e..3ccf98b 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -160,12 +160,13 @@ var gZenViewSplitter = new class { leftOverChild.parent = parent.parent; parent.parent.children[parent.parent.children.indexOf(parent)] = leftOverChild; this._removeNodeSplitters(parent, false); + this.applyGridLayout(parent.parent); } else { - const viewData = Object.entries(this._data).find(s => s.layoutTree === parent); + const viewData = Object.values(this._data).find(s => s.layoutTree === parent); viewData.layoutTree = parent; parent.positionToRoot = null; + this.applyGridLayout(parent); } - this.applyGridLayout(parent.parent, true); } /** @@ -676,12 +677,6 @@ var gZenViewSplitter = new class { splitter.setAttribute('gridIdx', idx); this.splitterBox.insertAdjacentElement("afterbegin", splitter); - splitter.parentSplitNode = parentNode; - if (!this._splitNodeToSplitters.has(parentNode)) { - this._splitNodeToSplitters.set(parentNode, []); - } - this._splitNodeToSplitters.get(parentNode).push(splitter); - splitter.addEventListener('mousedown', this.handleSplitterMouseDown); return splitter; } @@ -699,14 +694,15 @@ var gZenViewSplitter = new class { ); } if (currentSplitters.length > splittersNeeded) { - currentSplitters.slice(currentSplitters - splittersNeeded).forEach(s => s.remove()); + currentSplitters.slice(splittersNeeded - currentSplitters.length).forEach(s => s.remove()); currentSplitters = currentSplitters.slice(0, splittersNeeded); } + this._splitNodeToSplitters.set(parentNode, currentSplitters); return currentSplitters; } removeSplitters() { - Array.from(this._splitNodeToSplitters.values()).forEach(s => s.remove()); + Array.from(this._splitNodeToSplitters.values())[0].forEach(e => e.remove()); this._splitNodeToSplitters.clear(); } From e349aee50cfca03a1e7d4110cd3188864228f723 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:41:54 +0200 Subject: [PATCH 11/27] Fix splitview resizing. --- src/ZenViewSplitter.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 3ccf98b..0c20433 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -692,6 +692,7 @@ var gZenViewSplitter = new class { currentSplitters.push( this.createSplitter(parentNode.direction === 'column' ? 'horizontal' : 'vertical', parentNode, i) ); + currentSplitters[i].parentSplitNode = parentNode; } if (currentSplitters.length > splittersNeeded) { currentSplitters.slice(splittersNeeded - currentSplitters.length).forEach(s => s.remove()); @@ -779,7 +780,7 @@ var gZenViewSplitter = new class { setCursor(isVertical ? 'ew-resize' : 'n-resize'); document.addEventListener('mousemove', dragFunc); document.addEventListener('mouseup', () => { - removeEventListener('mousemove', dragFunc); + document.removeEventListener('mousemove', dragFunc); setCursor('auto'); }, {once: true}); } From 422adbb8963dc3b478b07fe178e2be5e92f64aa2 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:29:42 +0200 Subject: [PATCH 12/27] Fix split update logic, fix add tab to split --- src/ZenViewSplitter.mjs | 116 +++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 67 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 0c20433..293f7a6 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -309,7 +309,7 @@ var gZenViewSplitter = new class { currentData.tabs[startIdx] = endTab; currentData.tabs[endIdx] = startTab; - this.applyGridToTabs(currentData.tabs, currentData.gridType, gBrowser.selectedTab); + this.applyGridToTabs(currentData.tabs, currentData.gridType); } /** @@ -323,7 +323,8 @@ var gZenViewSplitter = new class { tab.linkedBrowser.zenModeActive = false; const container = tab.linkedBrowser.closest('.browserSidebarContainer'); this.resetContainerStyle(container); - + container.removeEventListener('click', this.handleTabEvent); + container.removeEventListener('mouseover', this.handleTabEvent); if (!forUnsplit) { tab.linkedBrowser.docShellIsActive = false; } @@ -336,7 +337,8 @@ var gZenViewSplitter = new class { */ removeGroup(groupIndex) { if (this.currentView === groupIndex) { - this.resetSplitView(); + this.deactivateCurrentSplitView(); + gBrowser.selectedBrowser.closest('.browserSidebarContainer').classList.add('deck-selected'); } for (const tab of this._data[groupIndex].tabs) { this.resetTabState(tab, true); @@ -344,21 +346,6 @@ var gZenViewSplitter = new class { this._data.splice(groupIndex, 1); } - /** - * Resets the split view. - */ - resetSplitView(resetTabState = true) { - if (resetTabState) { - for (const tab of this._data[this.currentView].tabs) { - this.resetTabState(tab, true); - } - } - this.removeSplitters(); - this.tabBrowserPanel.removeAttribute('zen-split-view'); - - this.currentView = -1; - } - /** * context menu item display update */ @@ -471,15 +458,8 @@ var gZenViewSplitter = new class { async onLocationChange(browser) { const tab = window.gBrowser.getTabForBrowser(browser); this.updateSplitViewButton(!tab?.splitView); - if (tab && this.splitViewActive) { - this._data[this.currentView].tabs.forEach(t => { - const container = t.linkedBrowser.closest('.browserSidebarContainer'); - if (t === tab) { - container.setAttribute('zen-split-active', true); - } else if (container.hasAttribute('zen-split-active')) { - container.removeAttribute('zen-split-active'); - } - }); + if (tab) { + this.updateSplitView(tab); tab.linkedBrowser.docShellIsActive = true; } } @@ -503,21 +483,36 @@ var gZenViewSplitter = new class { for (const tab of tabs) { if (!this._data[groupIndex].tabs.includes(tab)) { this._data[groupIndex].tabs.push(tab); + this.addTabToSplit(tab, this._data[groupIndex].layoutTree); } } this._data[groupIndex].gridType = gridType; - this.updateSplitView(existingSplitTab); + this.applyGridLayout(this._data[groupIndex].layoutTree); return; } } const splitData = { tabs, - gridType + gridType, + layoutTree: this.calculateLayoutTree(tabs, gridType), } this._data.push(splitData); window.gBrowser.selectedTab = tabs[0]; - this.activateSplitView(splitData, tabs[0]); + this.activateSplitView(splitData); + } + + addTabToSplit(tab, splitNode) { + const tabContainer = tab.linkedBrowser.closest('.browserSidebarContainer'); + if (splitNode.direction === 'row') { + const reduce = splitNode.children.length / (splitNode.children.length + 1); + splitNode.children.forEach(c => c.widthInParent *= reduce); + splitNode.addChild(new SplitLeafNode(tabContainer.id, (1 - reduce) * 100, 100)); + } else if (splitNode.direction === 'column') { + const reduce = splitNode.children.length / (splitNode.children.length + 1); + splitNode.children.forEach(c => c.heightInParent *= reduce); + splitNode.addChild(new SplitLeafNode(tabContainer.id, (1 - reduce) * 100, 100)); + } } /** @@ -526,31 +521,27 @@ var gZenViewSplitter = new class { * @param {Tab} tab - The tab to update the split view for. */ updateSplitView(tab) { - const splitData = this._data.find((group) => group.tabs.includes(tab)); - if (!splitData || (this.currentView >= 0 && !this._data[this.currentView].tabs.includes(tab))) { - this.updateSplitViewButton(true); - if (this.currentView >= 0) { - this.deactivateSplitView(); - return; - } - if (!splitData) { - return; - } - } + const oldView = this.currentView; + const newView = this._data.findIndex((group) => group.tabs.includes(tab)); - this.activateSplitView(splitData, tab); + if (oldView === newView) return; + if (newView < 0 && oldView >= 0) { + this.updateSplitViewButton(true); + this.deactivateCurrentSplitView(); + return; + } + this.activateSplitView(this._data[newView]); } /** * Deactivates the split view. */ - deactivateSplitView() { + deactivateCurrentSplitView() { for (const tab of this._data[this.currentView].tabs) { const container = tab.linkedBrowser.closest('.browserSidebarContainer'); this.resetContainerStyle(container); - container.removeEventListener('click', this.handleTabEvent); - container.removeEventListener('mouseover', this.handleTabEvent); } + this.removeSplitters(); this.tabBrowserPanel.removeAttribute('zen-split-view'); this.setTabsDocShellState(this._data[this.currentView].tabs, false); this.currentView = -1; @@ -560,22 +551,19 @@ var gZenViewSplitter = new class { * Activates the split view. * * @param {object} splitData - The split data. - * @param {Tab} activeTab - The active tab. */ - activateSplitView(splitData, activeTab) { - this.tabBrowserPanel.setAttribute('zen-split-view', 'true'); - this.currentView = this._data.indexOf(splitData); + activateSplitView(splitData) { + const oldView = this.currentView; + const newView = this._data.indexOf(splitData); + if (oldView >= 0 && oldView !== newView) this.deactivateCurrentSplitView(); + this.currentView = newView; - const gridType = splitData.gridType || 'grid'; + this.tabBrowserPanel.setAttribute('zen-split-view', 'true'); this.setTabsDocShellState(splitData.tabs, true); this.updateSplitViewButton(false); - - this.applyGridToTabs(splitData.tabs, activeTab); - - const layout = this.calculateLayoutTree(splitData.tabs, gridType); - splitData.layoutTree = layout; - this.applyGridLayout(layout); + this.applyGridToTabs(splitData.tabs); + this.applyGridLayout(splitData.layoutTree); } calculateLayoutTree(tabs, gridType) { @@ -609,11 +597,11 @@ var gZenViewSplitter = new class { * @param {Tab[]} tabs - The tabs to apply the grid layout to. * @param {Tab} activeTab - The active tab. */ - applyGridToTabs(tabs,activeTab) { + applyGridToTabs(tabs) { tabs.forEach((tab, index) => { tab.splitView = true; const container = tab.linkedBrowser.closest('.browserSidebarContainer'); - this.styleContainer(container, tab === activeTab, index); + this.styleContainer(container); }); } @@ -703,7 +691,7 @@ var gZenViewSplitter = new class { } removeSplitters() { - Array.from(this._splitNodeToSplitters.values())[0].forEach(e => e.remove()); + Array.from(this._splitNodeToSplitters.values()).forEach(e => e[0].remove()) this._splitNodeToSplitters.clear(); } @@ -711,14 +699,8 @@ var gZenViewSplitter = new class { * Styles the container for a tab. * * @param {Element} container - The container element. - * @param {boolean} isActive - Indicates if the tab is active. - * @param {number} index - The index of the tab. */ - styleContainer(container, isActive, index) { - container.removeAttribute('zen-split-active'); - if (isActive) { - container.setAttribute('zen-split-active', 'true'); - } + styleContainer(container) { container.setAttribute('zen-split-anim', 'true'); container.addEventListener('click', this.handleTabEvent); container.addEventListener('mouseover', this.handleTabEvent); @@ -967,4 +949,4 @@ var gZenViewSplitter = new class { : [gBrowser.selectedTab, tabs[nextTabIndex]]; this.splitTabs(selected_tabs, gridType); } -}; +}; \ No newline at end of file From 894d8562821ddfbde21f863cfc2953b0970c3f76 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:39:23 +0200 Subject: [PATCH 13/27] Fix split/unsplit logic --- src/ZenViewSplitter.mjs | 42 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 293f7a6..5bd3cfc 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -3,7 +3,6 @@ class SplitNode { * @type {number} * @type */ - splitters = []; widthInParent ; /** * @type {number} @@ -176,8 +175,8 @@ var gZenViewSplitter = new class { */ _removeNodeSplitters(node, recursive ) { this.getSplitters(node)?.forEach(s => s.remove()); - if (!recursive) return; this._splitNodeToSplitters.delete(node); + if (!recursive) return; if (node.children) node.children.forEach(c => this._removeNodeSplitters(c)); } @@ -338,7 +337,6 @@ var gZenViewSplitter = new class { removeGroup(groupIndex) { if (this.currentView === groupIndex) { this.deactivateCurrentSplitView(); - gBrowser.selectedBrowser.closest('.browserSidebarContainer').classList.add('deck-selected'); } for (const tab of this._data[groupIndex].tabs) { this.resetTabState(tab, true); @@ -478,18 +476,21 @@ var gZenViewSplitter = new class { const existingSplitTab = tabs.find((tab) => tab.splitView); if (existingSplitTab) { const groupIndex = this._data.findIndex((group) => group.tabs.includes(existingSplitTab)); - if (groupIndex >= 0) { + const group = this._data[groupIndex]; + if (group.gridType === gridType) { // Add any tabs that are not already in the group for (const tab of tabs) { - if (!this._data[groupIndex].tabs.includes(tab)) { - this._data[groupIndex].tabs.push(tab); - this.addTabToSplit(tab, this._data[groupIndex].layoutTree); + if (!group.tabs.includes(tab)) { + group.tabs.push(tab); + this.addTabToSplit(tab, group.layoutTree); } } - this._data[groupIndex].gridType = gridType; - this.applyGridLayout(this._data[groupIndex].layoutTree); - return; + } else { + group.gridType = gridType; + group.layoutTree = this.calculateLayoutTree(tabs, gridType); } + this.activateSplitView(group); + return; } const splitData = { @@ -552,11 +553,12 @@ var gZenViewSplitter = new class { * * @param {object} splitData - The split data. */ - activateSplitView(splitData) { + activateSplitView(splitData, reset = false) { const oldView = this.currentView; const newView = this._data.indexOf(splitData); if (oldView >= 0 && oldView !== newView) this.deactivateCurrentSplitView(); this.currentView = newView; + if (reset) this.removeSplitters(); this.tabBrowserPanel.setAttribute('zen-split-view', 'true'); @@ -691,7 +693,7 @@ var gZenViewSplitter = new class { } removeSplitters() { - Array.from(this._splitNodeToSplitters.values()).forEach(e => e[0].remove()) + Array.from(this._splitNodeToSplitters.values()).flatMap(v => v).forEach(e => e.remove()); this._splitNodeToSplitters.clear(); } @@ -799,8 +801,7 @@ var gZenViewSplitter = new class { * @param {Element} container - The container element. */ resetContainerStyle(container) { - container.removeAttribute('zen-split-active'); - container.classList.remove('deck-selected'); + container.removeAttribute('zen-split'); container.style.inset = ''; } @@ -889,8 +890,10 @@ var gZenViewSplitter = new class { if (gridType === 'unsplit') { this.unsplitCurrentView(); } else { - this._data[this.currentView].gridType = gridType; - this.updateSplitView(window.gBrowser.selectedTab); + const group = this._data[this.currentView]; + group.gridType = gridType; + group.layoutTree = this.calculateLayoutTree(group.tabs, gridType); + this.activateSplitView(group, true); } panel.hidePopup(); } @@ -900,14 +903,9 @@ var gZenViewSplitter = new class { */ unsplitCurrentView() { if (this.currentView < 0) return; + this.removeGroup(this.currentView); const currentTab = window.gBrowser.selectedTab; - const tabs = this._data[this.currentView].tabs; - // note: This MUST be an index loop, as we are removing tabs from the array - for (let i = tabs.length - 1; i >= 0; i--) { - this.handleTabClose({ target: tabs[i], forUnsplit: true }); - } window.gBrowser.selectedTab = currentTab; - this.updateSplitViewButton(true); } /** From 39b876f7015c72e1a3aee20a7dd0b5b1ca636f8e Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:07:29 +0200 Subject: [PATCH 14/27] Fix splitview can't activate when tab unloaded, autoload tab when activating split. --- src/ZenViewSplitter.mjs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 5bd3cfc..be26737 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -43,8 +43,8 @@ class SplitNode { } } class SplitLeafNode { - constructor(tabContainerId,widthInParent = 100, heightInParent = 100) { - this.id = tabContainerId; + constructor(tab, widthInParent = 100, heightInParent = 100) { + this.tab = tab; this.widthInParent = widthInParent; this.heightInParent = heightInParent; } @@ -59,7 +59,7 @@ var gZenViewSplitter = new class { this.canChangeTabOnHover = null; this.splitterBox = null; this._splitNodeToSplitters = new Map(); - this._containerToSplitNode = new Map(); + this._tabToSplitNode = new Map(); XPCOMUtils.defineLazyPreferenceGetter( this, @@ -129,7 +129,7 @@ var gZenViewSplitter = new class { if (group.tabs.length < 2) { this.removeGroup(groupIndex); } else { - const node = this._containerToSplitNode.get(tab.linkedBrowser.closest('.browserSidebarContainer')) + const node = this._tabToSplitNode.get(tab); this.applyRemoveNode(node); } } @@ -504,15 +504,14 @@ var gZenViewSplitter = new class { } addTabToSplit(tab, splitNode) { - const tabContainer = tab.linkedBrowser.closest('.browserSidebarContainer'); if (splitNode.direction === 'row') { const reduce = splitNode.children.length / (splitNode.children.length + 1); splitNode.children.forEach(c => c.widthInParent *= reduce); - splitNode.addChild(new SplitLeafNode(tabContainer.id, (1 - reduce) * 100, 100)); + splitNode.addChild(new SplitLeafNode(tab, (1 - reduce) * 100, 100)); } else if (splitNode.direction === 'column') { const reduce = splitNode.children.length / (splitNode.children.length + 1); splitNode.children.forEach(c => c.heightInParent *= reduce); - splitNode.addChild(new SplitLeafNode(tabContainer.id, (1 - reduce) * 100, 100)); + splitNode.addChild(new SplitLeafNode(tab, (1 - reduce) * 100, 100)); } } @@ -559,6 +558,11 @@ var gZenViewSplitter = new class { if (oldView >= 0 && oldView !== newView) this.deactivateCurrentSplitView(); this.currentView = newView; if (reset) this.removeSplitters(); + splitData.tabs.forEach((tab) => { + if (tab.hasAttribute('pending')) { + gBrowser.getBrowserForTab(tab).reload(); + } + }); this.tabBrowserPanel.setAttribute('zen-split-view', 'true'); @@ -569,24 +573,23 @@ var gZenViewSplitter = new class { } calculateLayoutTree(tabs, gridType) { - const containerIds = tabs.map(t => t.linkedBrowser.closest('.browserSidebarContainer').id); let rootNode; if (gridType === 'vsep') { rootNode = new SplitNode('row'); - rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100 / tabs.length, 100)); + rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100 / tabs.length, 100)); } else if (gridType === 'hsep' || (tabs.length === 2 && gridType === 'grid')) { rootNode = new SplitNode('column'); - rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100, 100 / tabs.length)); + rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100, 100 / tabs.length)); } else if (gridType === 'grid') { rootNode = new SplitNode('row'); const rowWidth = 100 / Math.ceil(tabs.length / 2); for (let i = 0; i < tabs.length - 1; i += 2) { const columnNode = new SplitNode('column', rowWidth, 100); - columnNode.children = [new SplitLeafNode(containerIds[i], 100, 50), new SplitLeafNode(containerIds[i + 1], 100, 50)]; + columnNode.children = [new SplitLeafNode(tabs[i], 100, 50), new SplitLeafNode(tabs[i + 1], 100, 50)]; rootNode.addChild(columnNode); } if (tabs.length % 2 !== 0) { - rootNode.addChild(new SplitLeafNode(containerIds[tabs.length - 1], rowWidth, 100)); + rootNode.addChild(new SplitLeafNode(tabs[tabs.length - 1], rowWidth, 100)); } } @@ -618,9 +621,9 @@ var gZenViewSplitter = new class { } const nodeRootPosition = splitNode.positionToRoot; if (!splitNode.children) { - const browserContainer = document.getElementById(splitNode.id); + const browserContainer = splitNode.tab.linkedBrowser.closest('.browserSidebarContainer'); browserContainer.style.inset = `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; - this._containerToSplitNode.set(browserContainer, splitNode); + this._tabToSplitNode.set(splitNode.tab, splitNode); return; } From d046938e412e7262706d4d46ff4a2399ade068e8 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:20:49 +0200 Subject: [PATCH 15/27] Fix split/unsplit logic --- src/ZenViewSplitter.mjs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index be26737..f796166 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -477,7 +477,11 @@ var gZenViewSplitter = new class { if (existingSplitTab) { const groupIndex = this._data.findIndex((group) => group.tabs.includes(existingSplitTab)); const group = this._data[groupIndex]; - if (group.gridType === gridType) { + if (group.gridType !== gridType || tabs.length !== tabs.length) { + // reset layout + group.gridType = gridType; + group.layoutTree = this.calculateLayoutTree([...new Set(group.tabs.concat(tabs))], gridType); + } else { // Add any tabs that are not already in the group for (const tab of tabs) { if (!group.tabs.includes(tab)) { @@ -485,11 +489,8 @@ var gZenViewSplitter = new class { this.addTabToSplit(tab, group.layoutTree); } } - } else { - group.gridType = gridType; - group.layoutTree = this.calculateLayoutTree(tabs, gridType); } - this.activateSplitView(group); + this.activateSplitView(group, true); return; } @@ -544,6 +545,7 @@ var gZenViewSplitter = new class { this.removeSplitters(); this.tabBrowserPanel.removeAttribute('zen-split-view'); this.setTabsDocShellState(this._data[this.currentView].tabs, false); + this.updateSplitViewButton(true); this.currentView = -1; } @@ -574,10 +576,10 @@ var gZenViewSplitter = new class { calculateLayoutTree(tabs, gridType) { let rootNode; - if (gridType === 'vsep') { + if (gridType === 'vsep' || (tabs.length === 2 && gridType === 'grid')) { rootNode = new SplitNode('row'); rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100 / tabs.length, 100)); - } else if (gridType === 'hsep' || (tabs.length === 2 && gridType === 'grid')) { + } else if (gridType === 'hsep') { rootNode = new SplitNode('column'); rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100, 100 / tabs.length)); } else if (gridType === 'grid') { @@ -696,7 +698,7 @@ var gZenViewSplitter = new class { } removeSplitters() { - Array.from(this._splitNodeToSplitters.values()).flatMap(v => v).forEach(e => e.remove()); + [...this.splitterBox.children].forEach(s => s.remove()); this._splitNodeToSplitters.clear(); } From 54ce8ff5a74a7eeac891924ff61b4a83773e39a3 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sat, 5 Oct 2024 00:34:47 +0200 Subject: [PATCH 16/27] Fix duplicate field declaration after merge --- src/ZenViewSplitter.mjs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index cdc9c3e..6c9c815 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -57,25 +57,13 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { _tabBrowserPanel = null; __modifierElement = null; __hasSetMenuListener = false; - _data = []; - currentView = -1; - _tabBrowserPanel = null; - __modifierElement = null; - __hasSetMenuListener = false; - canChangeTabOnHover = null; splitterBox = null; _splitNodeToSplitters = new Map(); _tabToSplitNode = new Map(); init() { XPCOMUtils.defineLazyPreferenceGetter(this, 'canChangeTabOnHover', 'zen.splitView.change-on-hover', false); - - XPCOMUtils.defineLazyPreferenceGetter( - this, - 'minResizeWidth', - 'zen.splitView.min-resize-width', - 7 - ); + XPCOMUtils.defineLazyPreferenceGetter(this, 'minResizeWidth', 'zen.splitView.min-resize-width', 7); ChromeUtils.defineLazyGetter( this, From 6578992a115ad47556cf6b2abf6e836857f09933 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sat, 5 Oct 2024 03:30:23 +0200 Subject: [PATCH 17/27] Render dropZone when dragging. --- src/ZenViewSplitter.mjs | 74 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 6c9c815..dbcb126 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -57,9 +57,11 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { _tabBrowserPanel = null; __modifierElement = null; __hasSetMenuListener = false; - splitterBox = null; + overlay = null; _splitNodeToSplitters = new Map(); _tabToSplitNode = new Map(); + dropZone; + _edgeHoverSize = 20; init() { XPCOMUtils.defineLazyPreferenceGetter(this, 'canChangeTabOnHover', 'zen.splitView.change-on-hover', false); @@ -67,8 +69,14 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { ChromeUtils.defineLazyGetter( this, - 'splitterBox', - () => document.getElementById('zen-splitview-splitterbox') + 'overlay', + () => document.getElementById('zen-splitview-overlay') + ); + + ChromeUtils.defineLazyGetter( + this, + 'dropZone', + () => document.getElementById('zen-splitview-dropzone') ); window.addEventListener('TabClose', this.handleTabClose.bind(this)); @@ -119,7 +127,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (group.tabs.length < 2) { this.removeGroup(groupIndex); } else { - const node = this._tabToSplitNode.get(tab); + const node = this.getSplitNodeFromTab(tab); this.applyRemoveNode(node); } } @@ -177,6 +185,11 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this._thumnailCanvas.height = 140 * devicePixelRatio; } + const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser); + browsers.forEach(b => { + b.style.pointerEvents = 'none'; + b.style.opacity = '.7'; + }); this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); @@ -274,6 +287,44 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { onBrowserDragOver = (event) => { if (!this.splitViewActive) return; event.preventDefault(); + const browser = event.target.querySelector('browser'); + if (!browser) return; + const tab = gBrowser.getTabForBrowser(browser); + const splitNode = this.getSplitNodeFromTab(tab); + + const posToRoot = {...splitNode.positionToRoot}; + const browserRect = browser.getBoundingClientRect(); + const hoverSide = this.calculateHoverSide(event.clientX, event.clientY, browserRect); + + if (hoverSide !== 'center') { + const isVertical = hoverSide === 'top' || hoverSide === 'bottom'; + const browserSize = 100 - (isVertical ? posToRoot.top + posToRoot.bottom : posToRoot.right + posToRoot.left); + const reduce= browserSize * .5; + + posToRoot[this._oppositeSide(hoverSide)] += reduce; + } + const newInset = + `${posToRoot.top}% ${posToRoot.right}% ${posToRoot.bottom}% ${posToRoot.left}%`; + if (this.dropZone.style.inset !== newInset) { + window.requestAnimationFrame(() => this.dropZone.style.inset = newInset); + } + } + + _oppositeSide(side) { + if (side === 'top') return 'bottom'; + if (side === 'bottom') return 'top'; + if (side === 'left') return 'right'; + if (side === 'right') return 'left'; + } + + calculateHoverSide(x, y, elementRect) { + const hPixelHoverSize = (elementRect.right - elementRect.left) * this._edgeHoverSize / 100; + const vPixelHoverSize = (elementRect.bottom - elementRect.top) * this._edgeHoverSize / 100; + if (x <= elementRect.left + hPixelHoverSize) return 'left'; + if (x > elementRect.right - hPixelHoverSize) return 'right'; + if (y <= elementRect.top + vPixelHoverSize) return 'top'; + if (y > elementRect.bottom - vPixelHoverSize) return 'bottom'; + return 'center'; } onBrowserDrop = (event) => { @@ -298,7 +349,8 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { currentData.tabs[startIdx] = endTab; currentData.tabs[endIdx] = startTab; - this.applyGridToTabs(currentData.tabs, currentData.gridType); + this.dropZone.style.inset = + `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; } /** @@ -660,7 +712,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { splitter.className = 'zen-split-view-splitter'; splitter.setAttribute('orient', orient); splitter.setAttribute('gridIdx', idx); - this.splitterBox.insertAdjacentElement("afterbegin", splitter); + this.overlay.insertAdjacentElement("afterbegin", splitter); splitter.addEventListener('mousedown', this.handleSplitterMouseDown); return splitter; @@ -688,10 +740,18 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } removeSplitters() { - [...this.splitterBox.children].forEach(s => s.remove()); + [...this.overlay.children].filter(c => c.classList.contains('zen-split-view-splitter')).forEach(s => s.remove()); this._splitNodeToSplitters.clear(); } + /** + * @param {Tab} tab + * @return {SplitNode} splitNode + */ + getSplitNodeFromTab(tab) { + return this._tabToSplitNode.get(tab); + } + /** * Styles the container for a tab. * From cde3c6a48b8987b2426b1f754ea1a391ac776864 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:40:38 +0200 Subject: [PATCH 18/27] Animate splitview panels --- src/ZenViewSplitter.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index dbcb126..d1e77fd 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -780,6 +780,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { }; handleSplitterMouseDown = (event) => { + this.tabBrowserPanel.setAttribute('zen-split-resizing', true); const isVertical = event.target.getAttribute('orient') === 'vertical'; const dimension = isVertical ? 'width' : 'height'; const dimensionInParent = dimension + 'InParent'; @@ -821,6 +822,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', dragFunc); setCursor('auto'); + this.tabBrowserPanel.removeAttribute('zen-split-resizing'); }, {once: true}); } From 694cbc9d89f279299236c58356a1a47766371d25 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:10:13 +0200 Subject: [PATCH 19/27] Refactor: remove redundant height AND widthInParent from splitNode, fix weird splitTabs behaviour --- src/ZenViewSplitter.mjs | 85 ++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index d1e77fd..ef8a2d1 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -1,13 +1,10 @@ -class SplitNode { - /** - * @type {number} - * @type - */ - widthInParent ; +class SplitLeafNode { /** + * The percentage of the size of the parent the node takes up, dependent on parent direction this is either + * width or height. * @type {number} */ - heightInParent ; + sizeInParent; /** * @type {Object} */ @@ -16,16 +13,31 @@ class SplitNode { * @type {SplitNode} */ parent; + constructor(tab, sizeInParent) { + this.tab = tab; + this.sizeInParent = sizeInParent; + } + + get heightInParent() { + return this.parent.direction === 'column' ? this.sizeInParent : 100; + } + + get widthInParent() { + return this.parent.direction === 'row' ? this.sizeInParent : 100; + } +} + +class SplitNode extends SplitLeafNode { /** * @type {string} */ direction; + _children = []; - constructor(direction, widthInParent = 100, heightInParent = 100) { - this.widthInParent = widthInParent; - this.heightInParent = heightInParent; + constructor(direction, sizeInParent) { + super(null, sizeInParent); + this.sizeInParent = sizeInParent; this.direction = direction; // row or column - this._children = []; } set children(children) { @@ -42,13 +54,6 @@ class SplitNode { this._children.push(child); } } -class SplitLeafNode { - constructor(tab, widthInParent = 100, heightInParent = 100) { - this.tab = tab; - this.widthInParent = widthInParent; - this.heightInParent = heightInParent; - } -} class ZenViewSplitter extends ZenDOMOperatedFeature { currentView = -1; @@ -143,16 +148,14 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { parent.children.splice(childIndex, 1); if (parent.children.length !== 1) { const nodeToResize = parent.children[Math.max(0, childIndex - 1)]; - if (parent.direction === 'column') nodeToResize.heightInParent += toRemove.heightInParent; - else nodeToResize.widthInParent += toRemove.widthInParent; + nodeToResize.sizeInParent += toRemove.sizeInParent; this.applyGridLayout(parent); return; } // node that is not a leaf cannot have less than 2 children, this makes for better resizing // node takes place of parent const leftOverChild = parent.children[0]; - leftOverChild.widthInParent = parent.widthInParent; - leftOverChild.heightInParent = parent.heightInParent; + leftOverChild.sizeInParent = parent.sizeInParent; if (parent.parent) { leftOverChild.parent = parent.parent; parent.parent.children[parent.parent.children.indexOf(parent)] = leftOverChild; @@ -510,7 +513,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { * @param {Tab[]} tabs - The tabs to split. * @param {string} gridType - The type of grid layout. */ - splitTabs(tabs, gridType = 'grid') { + splitTabs(tabs, gridType) { if (tabs.length < 2) { return; } @@ -519,7 +522,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (existingSplitTab) { const groupIndex = this._data.findIndex((group) => group.tabs.includes(existingSplitTab)); const group = this._data[groupIndex]; - if (group.gridType !== gridType || tabs.length !== tabs.length) { + if (gridType && (group.gridType !== gridType)) { // reset layout group.gridType = gridType; group.layoutTree = this.calculateLayoutTree([...new Set(group.tabs.concat(tabs))], gridType); @@ -535,6 +538,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.activateSplitView(group, true); return; } + gridType ??= 'grid'; const splitData = { tabs, @@ -547,15 +551,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } addTabToSplit(tab, splitNode) { - if (splitNode.direction === 'row') { - const reduce = splitNode.children.length / (splitNode.children.length + 1); - splitNode.children.forEach(c => c.widthInParent *= reduce); - splitNode.addChild(new SplitLeafNode(tab, (1 - reduce) * 100, 100)); - } else if (splitNode.direction === 'column') { - const reduce = splitNode.children.length / (splitNode.children.length + 1); - splitNode.children.forEach(c => c.heightInParent *= reduce); - splitNode.addChild(new SplitLeafNode(tab, (1 - reduce) * 100, 100)); - } + const reduce = splitNode.children.length / (splitNode.children.length + 1); + splitNode.children.forEach(c => c.sizeInParent *= reduce); + splitNode.addChild(new SplitLeafNode(tab, (1 - reduce) * 100)); } /** @@ -620,20 +618,20 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { let rootNode; if (gridType === 'vsep' || (tabs.length === 2 && gridType === 'grid')) { rootNode = new SplitNode('row'); - rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100 / tabs.length, 100)); + rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100 / tabs.length)); } else if (gridType === 'hsep') { rootNode = new SplitNode('column'); - rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100, 100 / tabs.length)); + rootNode.children = tabs.map(tab => new SplitLeafNode(tab, 100 / tabs.length)); } else if (gridType === 'grid') { rootNode = new SplitNode('row'); const rowWidth = 100 / Math.ceil(tabs.length / 2); for (let i = 0; i < tabs.length - 1; i += 2) { const columnNode = new SplitNode('column', rowWidth, 100); - columnNode.children = [new SplitLeafNode(tabs[i], 100, 50), new SplitLeafNode(tabs[i + 1], 100, 50)]; + columnNode.children = [new SplitLeafNode(tabs[i], 50), new SplitLeafNode(tabs[i + 1], 50)]; rootNode.addChild(columnNode); } if (tabs.length % 2 !== 0) { - rootNode.addChild(new SplitLeafNode(tabs[tabs.length - 1], rowWidth, 100)); + rootNode.addChild(new SplitLeafNode(tabs[tabs.length - 1], rowWidth)); } } @@ -685,9 +683,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.applyGridLayout(childNode); if (splitNode.direction === 'column') { - topOffset += childNode.heightInParent * rootToNodeHeightRatio; + topOffset += childNode.sizeInParent * rootToNodeHeightRatio; } else { - leftOffset += childNode.widthInParent * rootToNodeWidthRatio; + leftOffset += childNode.sizeInParent * rootToNodeWidthRatio; } if (i < splittersNeeded) { @@ -783,7 +781,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.tabBrowserPanel.setAttribute('zen-split-resizing', true); const isVertical = event.target.getAttribute('orient') === 'vertical'; const dimension = isVertical ? 'width' : 'height'; - const dimensionInParent = dimension + 'InParent'; const clientAxis = isVertical ? 'screenX' : 'screenY'; const gridIdx = parseInt(event.target.getAttribute('gridIdx')); @@ -792,11 +789,11 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { let rootToNodeSize; if (isVertical) rootToNodeSize = 100 / (100 - splitNode.positionToRoot.right - splitNode.positionToRoot.left); else rootToNodeSize = 100 / (100 - splitNode.positionToRoot.bottom - splitNode.positionToRoot.top); - const originalSizes = splitNode.children.map(c => c[dimensionInParent]); + const originalSizes = splitNode.children.map(c => c.sizeInParent); const dragFunc = (dEvent) => { requestAnimationFrame(() => { - originalSizes.forEach((s, i) => splitNode.children[i][dimensionInParent] = s); // reset changes + originalSizes.forEach((s, i) => splitNode.children[i].sizeInParent = s); // reset changes const movement = dEvent[clientAxis] - startPosition; let movementPercent = (movement / this.tabBrowserPanel.getBoundingClientRect()[dimension] * rootToNodeSize) * 100; @@ -805,14 +802,14 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { for (let i = gridIdx + (movementPercent < 0 ? 0 : 1); 0 <= i && i < originalSizes.length; i += movementPercent < 0 ? -1 : 1) { const current = originalSizes[i]; const newSize = Math.max(this.minResizeWidth, current - reducingMovement); - splitNode.children[i][dimensionInParent] = newSize; + splitNode.children[i].sizeInParent = newSize; const amountReduced = current - newSize; reducingMovement -= amountReduced; if (reducingMovement <= 0) break; } const increasingMovement = Math.max(movementPercent, - movementPercent) - reducingMovement; const increaseIndex = gridIdx + (movementPercent < 0 ? 1 : 0); - splitNode.children[increaseIndex][dimensionInParent] = originalSizes[increaseIndex] + increasingMovement; + splitNode.children[increaseIndex].sizeInParent = originalSizes[increaseIndex] + increasingMovement; this.applyGridLayout(splitNode); }); } From a5db255ab7aedea91674fa3dd4cdf6f5810cab5e Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:24:34 +0200 Subject: [PATCH 20/27] Add splitview drag and drop functionality --- src/ZenViewSplitter.mjs | 117 +++++++++++++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index ef8a2d1..b94bf46 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -67,6 +67,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { _tabToSplitNode = new Map(); dropZone; _edgeHoverSize = 20; + _minResizeWidth; init() { XPCOMUtils.defineLazyPreferenceGetter(this, 'canChangeTabOnHover', 'zen.splitView.change-on-hover', false); @@ -133,15 +134,17 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.removeGroup(groupIndex); } else { const node = this.getSplitNodeFromTab(tab); - this.applyRemoveNode(node); + const toUpdate = this.removeNode(node); + this.applyGridLayout(toUpdate); } } /** * Remove a SplitNode from its tree and the view * @param {SplitNode} toRemove + * @return {SplitNode} that has to be updated */ - applyRemoveNode(toRemove) { + removeNode(toRemove) { this._removeNodeSplitters(toRemove, true); const parent = toRemove.parent; const childIndex = parent.children.indexOf(toRemove); @@ -149,8 +152,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (parent.children.length !== 1) { const nodeToResize = parent.children[Math.max(0, childIndex - 1)]; nodeToResize.sizeInParent += toRemove.sizeInParent; - this.applyGridLayout(parent); - return; + return parent; } // node that is not a leaf cannot have less than 2 children, this makes for better resizing // node takes place of parent @@ -160,12 +162,13 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { leftOverChild.parent = parent.parent; parent.parent.children[parent.parent.children.indexOf(parent)] = leftOverChild; this._removeNodeSplitters(parent, false); - this.applyGridLayout(parent.parent); + return parent.parent; } else { const viewData = Object.values(this._data).find(s => s.layoutTree === parent); - viewData.layoutTree = parent; - parent.positionToRoot = null; - this.applyGridLayout(parent); + viewData.layoutTree = leftOverChild; + leftOverChild.positionToRoot = null; + leftOverChild.parent = null; + return leftOverChild; } } @@ -196,6 +199,8 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); + this.tabBrowserPanel.addEventListener('dragend', this.onBrowserDragEnd) + document.addEventListener('click', this.disableTabSwitchView, {once: true}); } disableTabSwitchView = () => { @@ -217,6 +222,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (!browser) { return; } + this.dropZone.setAttribute('enabled', true); const browserContainer = browser.closest('.browserSidebarContainer'); event.dataTransfer.setData('text/plain', browserContainer.id); @@ -288,7 +294,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } onBrowserDragOver = (event) => { - if (!this.splitViewActive) return; event.preventDefault(); const browser = event.target.querySelector('browser'); if (!browser) return; @@ -313,6 +318,10 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } } + onBrowserDragEnd = (event) => { + this.dropZone.removeAttribute('enabled'); + } + _oppositeSide(side) { if (side === 'top') return 'bottom'; if (side === 'bottom') return 'top'; @@ -331,29 +340,89 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } onBrowserDrop = (event) => { - if (!this.splitViewActive) return; - console.log(event); - const containerId = event.dataTransfer.getData('text/plain'); + const browserDroppedOn = event.target.querySelector('browser'); + if (!browserDroppedOn) return; - const startTab = gBrowser.getTabForBrowser( - document.getElementById(containerId).querySelector('browser') + const droppedContainerId= event.dataTransfer.getData('text/plain'); + + const droppedTab = gBrowser.getTabForBrowser( + document.getElementById(droppedContainerId).querySelector('browser') ); - const endTab = gBrowser.getTabForBrowser( + if (!droppedTab) return; + const droppedOnTab = gBrowser.getTabForBrowser( event.target.querySelector('browser') ); - if (!startTab || !endTab) { + + const hoverSide = this.calculateHoverSide(event.clientX, event.clientY, browserDroppedOn.getBoundingClientRect()); + const droppedSplitNode = this.getSplitNodeFromTab(droppedTab); + const droppedOnSplitNode = this.getSplitNodeFromTab(droppedOnTab); + if (hoverSide === 'center') { + this.swapNodes(droppedSplitNode, droppedOnSplitNode); + this.applyGridLayout(this._data[this.currentView].layoutTree); return; } + this.removeNode(droppedSplitNode); + this.splitIntoNode(droppedOnSplitNode, droppedSplitNode, hoverSide, .5); + this.applyGridLayout(this._data[this.currentView].layoutTree); + } - const currentData = this._data[this.currentView]; + /** + * + * @param node1 + * @param node2 + */ + swapNodes(node1, node2) { + this._swapField('sizeInParent', node1, node2); - const startIdx = currentData.tabs.indexOf(startTab); - const endIdx = currentData.tabs.indexOf(endTab); + const node1Idx = node1.parent.children.indexOf(node1); + const node2Idx = node2.parent.children.indexOf(node2); + node1.parent.children[node1Idx] = node2; + node2.parent.children[node2Idx] = node1; - currentData.tabs[startIdx] = endTab; - currentData.tabs[endIdx] = startTab; - this.dropZone.style.inset = - `${nodeRootPosition.top}% ${nodeRootPosition.right}% ${nodeRootPosition.bottom}% ${nodeRootPosition.left}%`; + this._swapField('parent', node1, node2); + } + + /** + * + * @param node + * @param nodeToInsert + * @param side + * @param sizeOfInsertedNode percentage of node width or height that nodeToInsert will take + */ + splitIntoNode(node, nodeToInsert, side, sizeOfInsertedNode) { + const splitDirection = side === 'left' || side === 'right' ? 'row' : 'column'; + const splitPosition = side === 'left' || side === 'top' ? 0 : 1; + + let nodeSize; + let newParent; + if (splitDirection === node.parent.direction) { + newParent = node.parent; + nodeSize = node.sizeInParent; + } else { + nodeSize = 100; + const nodeIndex = node.parent.children.indexOf(node); + newParent = new SplitNode(splitDirection, node.sizeInParent); + if (node.parent) { + newParent.parent = node.parent; + } else { + const viewData = Object.values(this._data).find(s => s.layoutTree === node.parent); + viewData.layoutTree = newParent; + } + node.parent.children[nodeIndex] = newParent; + newParent.addChild(node); + } + node.sizeInParent = 100 - (nodeSize * sizeOfInsertedNode); + nodeToInsert.sizeInParent = nodeSize * sizeOfInsertedNode; + + const index = newParent.children.indexOf(node); + newParent.children.splice(index + splitPosition, 0, nodeToInsert); + nodeToInsert.parent = newParent; + } + + _swapField(fieldName, obj1, obj2) { + const swap = obj1[fieldName]; + obj1[fieldName] = obj2[fieldName]; + obj2[fieldName] = swap; } /** @@ -1003,4 +1072,4 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } } -window.gZenViewSplitter = new ZenViewSplitter(); +window.gZenViewSplitter = new ZenViewSplitter(); \ No newline at end of file From eca3ecd04cddd69554b2710b0437aec84856ff24 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:55:13 +0200 Subject: [PATCH 21/27] Fix splitview drag and drop wrong size --- src/ZenViewSplitter.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index b94bf46..821dd08 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -411,7 +411,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { node.parent.children[nodeIndex] = newParent; newParent.addChild(node); } - node.sizeInParent = 100 - (nodeSize * sizeOfInsertedNode); + node.sizeInParent = (1 - sizeOfInsertedNode) * nodeSize; nodeToInsert.sizeInParent = nodeSize * sizeOfInsertedNode; const index = newParent.children.indexOf(node); From 3c8006df0e52bbc9c723b81b69784830218c6082 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:30:33 +0200 Subject: [PATCH 22/27] Resize other splitNodes evenly when removing node --- src/ZenViewSplitter.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 821dd08..d903eb8 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -67,7 +67,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { _tabToSplitNode = new Map(); dropZone; _edgeHoverSize = 20; - _minResizeWidth; + minResizeWidth; init() { XPCOMUtils.defineLazyPreferenceGetter(this, 'canChangeTabOnHover', 'zen.splitView.change-on-hover', false); @@ -150,8 +150,8 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { const childIndex = parent.children.indexOf(toRemove); parent.children.splice(childIndex, 1); if (parent.children.length !== 1) { - const nodeToResize = parent.children[Math.max(0, childIndex - 1)]; - nodeToResize.sizeInParent += toRemove.sizeInParent; + const otherNodeIncrease = 100 / (100 - toRemove.sizeInParent); + parent.children.forEach(c => c.sizeInParent *= otherNodeIncrease); return parent; } // node that is not a leaf cannot have less than 2 children, this makes for better resizing From fdf81f2fe951102b34be6b6c9b7ec1095d189391 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:38:28 +0200 Subject: [PATCH 23/27] Make it impossible to drop splitview tab on itself, lower opacity of tab being dragged. Fix splitters not being cleared after browser drop. --- src/ZenViewSplitter.mjs | 48 ++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index d903eb8..3156228 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -185,6 +185,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } enableTabSwitchView() { + if (this.switchViewEnabled) return; + this.switchViewEnabled = true; + this.switchViewView = this.currentView; if (!this._thumnailCanvas) { this._thumnailCanvas = document.createElement("canvas"); this._thumnailCanvas.width = 280 * devicePixelRatio; @@ -194,26 +197,36 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser); browsers.forEach(b => { b.style.pointerEvents = 'none'; - b.style.opacity = '.7'; + b.style.opacity = '.85'; }); this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); this.tabBrowserPanel.addEventListener('dragend', this.onBrowserDragEnd) - document.addEventListener('click', this.disableTabSwitchView, {once: true}); + this.tabBrowserPanel.addEventListener('click', this.disableTabSwitchView); } - disableTabSwitchView = () => { - this.switchViewEnabled = false; + disableTabSwitchView = (event = null) => { + if (!this.switchViewEnabled) return; + if (event) { + if (event.type === 'click' && event.button !== 0) return; + } + + if (!this.switchViewEnabled || (event && event.target.classList.contains('zen-split-view-splitter'))) { + return; + } this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.removeEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.removeEventListener('drop', this.onBrowserDrop); - const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser); + this.tabBrowserPanel.removeEventListener('click', this.disableTabSwitchView); + const browsers = this._data[this.switchViewView].tabs.map(t => t.linkedBrowser); browsers.forEach(b => { b.style.pointerEvents = ''; b.style.opacity = ''; }); + this.switchViewEnabled = false; + this.switchViewView = null; } onBrowserDragStart = (event) => { @@ -222,9 +235,10 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (!browser) { return; } - this.dropZone.setAttribute('enabled', true); + browser.style.opacity = '.2'; const browserContainer = browser.closest('.browserSidebarContainer'); event.dataTransfer.setData('text/plain', browserContainer.id); + this._draggingTab = gBrowser.getTabForBrowser(browser); let dt = event.dataTransfer; let scale = window.devicePixelRatio; @@ -298,6 +312,15 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { const browser = event.target.querySelector('browser'); if (!browser) return; const tab = gBrowser.getTabForBrowser(browser); + if (tab === this._draggingTab) { + if (this.dropZone.hasAttribute('enabled')) { + this.dropZone.removeAttribute('enabled'); + } + return; + } + if (!this.dropZone.hasAttribute('enabled')) { + this.dropZone.setAttribute('enabled', true); + } const splitNode = this.getSplitNodeFromTab(tab); const posToRoot = {...splitNode.positionToRoot}; @@ -320,6 +343,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { onBrowserDragEnd = (event) => { this.dropZone.removeAttribute('enabled'); + const draggingBrowser = this._draggingTab.linkedBrowser; + draggingBrowser.style.opacity = '.85'; + this._draggingTab = null; } _oppositeSide(side) { @@ -343,15 +369,12 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { const browserDroppedOn = event.target.querySelector('browser'); if (!browserDroppedOn) return; - const droppedContainerId= event.dataTransfer.getData('text/plain'); - - const droppedTab = gBrowser.getTabForBrowser( - document.getElementById(droppedContainerId).querySelector('browser') - ); + const droppedTab = this._draggingTab; if (!droppedTab) return; const droppedOnTab = gBrowser.getTabForBrowser( event.target.querySelector('browser') ); + if (droppedTab === droppedOnTab) return; const hoverSide = this.calculateHoverSide(event.clientX, event.clientY, browserDroppedOn.getBoundingClientRect()); const droppedSplitNode = this.getSplitNodeFromTab(droppedTab); @@ -363,7 +386,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { } this.removeNode(droppedSplitNode); this.splitIntoNode(droppedOnSplitNode, droppedSplitNode, hoverSide, .5); - this.applyGridLayout(this._data[this.currentView].layoutTree); + this.activateSplitView(this._data[this.currentView], true); } /** @@ -640,6 +663,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.deactivateCurrentSplitView(); return; } + this.disableTabSwitchView(); this.activateSplitView(this._data[newView]); } From d6787fd2fd58398b9204a7793380e7afa4725095 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:58:36 +0200 Subject: [PATCH 24/27] Only reset splitView if gridType was changed or no new tab was added. --- src/ZenViewSplitter.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 3156228..05ea9ee 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -614,7 +614,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (existingSplitTab) { const groupIndex = this._data.findIndex((group) => group.tabs.includes(existingSplitTab)); const group = this._data[groupIndex]; - if (gridType && (group.gridType !== gridType)) { + const gridTypeChange = gridType && (group.gridType !== gridType); + const newTabsAdded = tabs.find(t => !group.tabs.includes(t)); + if (gridTypeChange || !newTabsAdded) { // reset layout group.gridType = gridType; group.layoutTree = this.calculateLayoutTree([...new Set(group.tabs.concat(tabs))], gridType); From c906ddca1098e90650d2b72dbf06b8de4faa5fa3 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:39:52 +0200 Subject: [PATCH 25/27] Fix docShell of selected tab being deactivated when unsplitting. --- src/ZenViewSplitter.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 05ea9ee..1306386 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -929,6 +929,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { // zenModeActive allow us to avoid setting docShellisActive to false later on, // see browser-custom-elements.js's patch tab.linkedBrowser.zenModeActive = active; + if (!active && tab === gBrowser.selectedTab) continue; try { tab.linkedBrowser.docShellIsActive = active; } catch (e) { From e5502d1a9b8dccbc4eb75d2d3ea3e6266186a7d7 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:46:46 +0200 Subject: [PATCH 26/27] Add pref for rearrange-edge-hover-size --- src/ZenViewSplitter.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 1306386..7b1c4be 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -66,12 +66,13 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { _splitNodeToSplitters = new Map(); _tabToSplitNode = new Map(); dropZone; - _edgeHoverSize = 20; + _edgeHoverSize; minResizeWidth; init() { XPCOMUtils.defineLazyPreferenceGetter(this, 'canChangeTabOnHover', 'zen.splitView.change-on-hover', false); XPCOMUtils.defineLazyPreferenceGetter(this, 'minResizeWidth', 'zen.splitView.min-resize-width', 7); + XPCOMUtils.defineLazyPreferenceGetter(this, '_edgeHoverSize', 'zen.splitView.rearrange-edge-hover-size', 24); ChromeUtils.defineLazyGetter( this, From c8e3885110df4b9bba2365658f8eb8fd70d13b15 Mon Sep 17 00:00:00 2001 From: brahim <92426196+BrhmDev@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:05:13 +0200 Subject: [PATCH 27/27] Rename tabSwitchView to tabRearrangeView --- src/ZenViewSplitter.mjs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ZenViewSplitter.mjs b/src/ZenViewSplitter.mjs index 7b1c4be..69d5a47 100644 --- a/src/ZenViewSplitter.mjs +++ b/src/ZenViewSplitter.mjs @@ -185,10 +185,10 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { if (node.children) node.children.forEach(c => this._removeNodeSplitters(c)); } - enableTabSwitchView() { - if (this.switchViewEnabled) return; - this.switchViewEnabled = true; - this.switchViewView = this.currentView; + enableTabRearrangeView() { + if (this.rearrangeViewEnabled) return; + this.rearrangeViewEnabled = true; + this.rearrangeViewView = this.currentView; if (!this._thumnailCanvas) { this._thumnailCanvas = document.createElement("canvas"); this._thumnailCanvas.width = 280 * devicePixelRatio; @@ -204,30 +204,30 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); this.tabBrowserPanel.addEventListener('dragend', this.onBrowserDragEnd) - this.tabBrowserPanel.addEventListener('click', this.disableTabSwitchView); + this.tabBrowserPanel.addEventListener('click', this.disableTabRearrangeView); } - disableTabSwitchView = (event = null) => { - if (!this.switchViewEnabled) return; + disableTabRearrangeView = (event = null) => { + if (!this.rearrangeViewEnabled) return; if (event) { if (event.type === 'click' && event.button !== 0) return; } - if (!this.switchViewEnabled || (event && event.target.classList.contains('zen-split-view-splitter'))) { + if (!this.rearrangeViewEnabled || (event && event.target.classList.contains('zen-split-view-splitter'))) { return; } this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.removeEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.removeEventListener('drop', this.onBrowserDrop); - this.tabBrowserPanel.removeEventListener('click', this.disableTabSwitchView); - const browsers = this._data[this.switchViewView].tabs.map(t => t.linkedBrowser); + this.tabBrowserPanel.removeEventListener('click', this.disableTabRearrangeView); + const browsers = this._data[this.rearrangeViewView].tabs.map(t => t.linkedBrowser); browsers.forEach(b => { b.style.pointerEvents = ''; b.style.opacity = ''; }); - this.switchViewEnabled = false; - this.switchViewView = null; + this.rearrangeViewEnabled = false; + this.rearrangeViewView = null; } onBrowserDragStart = (event) => { @@ -666,7 +666,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { this.deactivateCurrentSplitView(); return; } - this.disableTabSwitchView(); + this.disableTabRearrangeView(); this.activateSplitView(this._data[newView]); } @@ -863,7 +863,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature { * @param {Event} event - The event. */ handleTabEvent = (event) => { - if (this.switchViewEnabled || (event.type === 'mouseover' && !this.canChangeTabOnHover)) { + if (this.rearrangeViewEnabled || (event.type === 'mouseover' && !this.canChangeTabOnHover)) { return; } const container = event.currentTarget;