diff --git a/src/renderer/css/split.css b/src/renderer/css/split.css index 8b8d00a..62a78c2 100644 --- a/src/renderer/css/split.css +++ b/src/renderer/css/split.css @@ -14,8 +14,11 @@ .split-container { display: flex; + flex: 1 1 0%; width: 100%; height: 100%; + min-width: 0; + min-height: 0; overflow: hidden; position: relative; } @@ -29,14 +32,14 @@ } .split-pane { - flex: 1; + flex: 1 1 0%; display: flex; position: relative; overflow: hidden; border: 1px solid transparent; transition: border-color 0.15s ease, box-shadow 0.15s ease; - min-width: 120px; - min-height: 80px; + min-width: 0; + min-height: 0; } .split-pane.focused { @@ -49,6 +52,7 @@ height: 100%; padding: 6px 10px; overflow: hidden; + box-sizing: border-box; } .xterm { @@ -65,19 +69,24 @@ .split-divider { background: var(--border-color); z-index: 10; - transition: background 0.2s ease; + flex-shrink: 0; + flex-grow: 0; + transition: background 0.15s ease, box-shadow 0.15s ease; + position: relative; } .split-container.horizontal > .split-divider { - width: 2px; + width: 4px; cursor: col-resize; } .split-container.vertical > .split-divider { - height: 2px; + height: 4px; cursor: row-resize; } -.split-divider:hover { +.split-divider:hover, +.split-divider.dragging { background: var(--accent-color); + box-shadow: 0 0 10px var(--accent-glow); } diff --git a/src/renderer/js/split-manager.js b/src/renderer/js/split-manager.js index 5365dec..4d43920 100644 --- a/src/renderer/js/split-manager.js +++ b/src/renderer/js/split-manager.js @@ -8,7 +8,7 @@ export class TabSplitTree { this.onEmpty = options.onEmpty || (() => {}); this.onTitleChange = options.onTitleChange || (() => {}); - this.terminals = new Map(); // id -> TerminalView + this.terminals = new Map(); // id -> { termView, paneElement, parentElement } this.activeTerminal = null; this.rootElement = document.createElement('div'); @@ -62,10 +62,14 @@ export class TabSplitTree { const currentPane = currentInfo.paneElement; const parent = currentPane.parentElement; - // 创建一个新的 split-container 容器替换原有 pane,实现嵌套分屏 + // 创建一个新的 split-container 容器替换原有 pane,实现嵌套等比分屏 const splitWrapper = document.createElement('div'); splitWrapper.className = `split-container ${direction}`; + // 继承原窗格在祖父容器中的 flex 权重 + splitWrapper.style.flex = currentPane.style.flex || '1 1 0%'; + currentPane.style.flex = '1 1 0%'; + parent.replaceChild(splitWrapper, currentPane); splitWrapper.appendChild(currentPane); @@ -76,7 +80,12 @@ export class TabSplitTree { // 添加新窗格 const newTerm = this.addPane(splitWrapper, direction); - + const newPane = this.terminals.get(newTerm.id).paneElement; + newPane.style.flex = '1 1 0%'; + + // 为分割线绑定拖拽调节事件 + this.setupDividerDrag(divider, currentPane, newPane, direction); + // 刷新自适应尺寸 setTimeout(() => { this.fitAll(); @@ -84,6 +93,43 @@ export class TabSplitTree { }, 50); } + setupDividerDrag(divider, firstElement, secondElement, direction) { + const isHorizontal = direction === 'horizontal'; + + const onMouseDown = (e) => { + e.preventDefault(); + divider.classList.add('dragging'); + const startPos = isHorizontal ? e.clientX : e.clientY; + const firstSize = isHorizontal ? firstElement.offsetWidth : firstElement.offsetHeight; + const secondSize = isHorizontal ? secondElement.offsetWidth : secondElement.offsetHeight; + + const onMouseMove = (moveEvent) => { + const currentPos = isHorizontal ? moveEvent.clientX : moveEvent.clientY; + const delta = currentPos - startPos; + const newFirst = firstSize + delta; + const newSecond = secondSize - delta; + + if (newFirst > 50 && newSecond > 50) { + firstElement.style.flex = `${newFirst} ${newFirst} 0%`; + secondElement.style.flex = `${newSecond} ${newSecond} 0%`; + this.fitAll(); + } + }; + + const onMouseUp = () => { + divider.classList.remove('dragging'); + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + this.fitAll(); + }; + + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + }; + + divider.addEventListener('mousedown', onMouseDown); + } + setActiveTerminal(termView) { this.activeTerminal = termView; @@ -110,8 +156,30 @@ export class TabSplitTree { const parent = pane.parentElement; this.terminals.delete(termId); + + // 找到相邻的分割线并移除 + const prev = pane.previousElementSibling; + const next = pane.nextElementSibling; + if (prev && prev.classList.contains('split-divider')) { + prev.remove(); + } else if (next && next.classList.contains('split-divider')) { + next.remove(); + } + pane.remove(); + // 如果非根容器中仅剩 1 个有效窗格,消除冗余包裹层将其提升至上一层 + if (parent !== this.rootElement && parent.parentElement) { + const remainingChildren = Array.from(parent.children).filter( + (el) => !el.classList.contains('split-divider') + ); + if (remainingChildren.length === 1) { + const singleChild = remainingChildren[0]; + singleChild.style.flex = parent.style.flex || '1 1 0%'; + parent.parentElement.replaceChild(singleChild, parent); + } + } + if (this.terminals.size === 0) { this.onEmpty(); return; @@ -121,7 +189,7 @@ export class TabSplitTree { const nextTerm = this.terminals.values().next().value.termView; this.setActiveTerminal(nextTerm); nextTerm.focus(); - this.fitAll(); + setTimeout(() => this.fitAll(), 40); } fitAll() {