feat: tmux 动态 resize(iPadOS 拖拽窗口/旋转自适应)

- ContentView 观测 onChange(geo.size)/onAppear → controller.containerResized
- containerResized: 点尺寸 × displayScale ÷ cell 像素 = 目标 cols×rows,去重 + debounce(200ms)
  → refresh-client -C WxH → tmux 重排回 %layout-change → 更新 pane frame
- cell 像素来自 raw 终端/pane surface metrics(InMemoryTerminalViewport.cellWidthPixels);
  surface resize 回调只 noteCellPixels 不驱动 tmux(防反馈环,单向数据流)
- 验证:容器 1032x1280@2x cell16x35→129x73,tmux 根 rect 精确采纳,2 pane 复用不重建

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-24 17:42:45 +08:00
parent 9a27338222
commit 7abe61a71f
4 changed files with 85 additions and 32 deletions

View File

@@ -167,31 +167,39 @@ struct TmuxTabChip: View {
struct TmuxWindowView: View {
@ObservedObject var window: TmuxWindow
let controller: TmuxController
@Environment(\.displayScale) private var displayScale
var body: some View {
GeometryReader { geo in
if let layout = window.visibleLayout {
let root = layout.root.rect
let cw = geo.size.width / CGFloat(max(1, root.width))
let ch = geo.size.height / CGFloat(max(1, root.height))
ZStack(alignment: .topLeading) {
ForEach(leaves(layout.root), id: \.pane.raw) { item in
if let surface = window.panes[item.pane] {
TmuxPaneView(
surface: surface,
isActive: window.activePane == item.pane,
onTap: { controller.selectPane(item.pane, in: window.id) },
onReady: { controller.markPaneReady(item.pane) }
)
.frame(width: CGFloat(item.rect.width) * cw,
height: CGFloat(item.rect.height) * ch)
.position(x: (CGFloat(item.rect.x) + CGFloat(item.rect.width) / 2) * cw,
y: (CGFloat(item.rect.y) + CGFloat(item.rect.height) / 2) * ch)
Group {
if let layout = window.visibleLayout {
let root = layout.root.rect
let cw = geo.size.width / CGFloat(max(1, root.width))
let ch = geo.size.height / CGFloat(max(1, root.height))
ZStack(alignment: .topLeading) {
ForEach(leaves(layout.root), id: \.pane.raw) { item in
if let surface = window.panes[item.pane] {
TmuxPaneView(
surface: surface,
isActive: window.activePane == item.pane,
onTap: { controller.selectPane(item.pane, in: window.id) },
onReady: { controller.markPaneReady(item.pane) }
)
.frame(width: CGFloat(item.rect.width) * cw,
height: CGFloat(item.rect.height) * ch)
.position(x: (CGFloat(item.rect.x) + CGFloat(item.rect.width) / 2) * cw,
y: (CGFloat(item.rect.y) + CGFloat(item.rect.height) / 2) * ch)
}
}
}
} else {
Color.clear
}
} else {
Color.clear
}
// iPadOS refresh-client -Ccontroller debounce
.onAppear { controller.containerResized(widthPt: geo.size.width, heightPt: geo.size.height, scale: displayScale) }
.onChange(of: geo.size) { _, s in
controller.containerResized(widthPt: s.width, heightPt: s.height, scale: displayScale)
}
}
}