feat: 顶带 Shimo 化重设计 + 第一版功能减法

- 顶带:实心圆窗口按钮、无边框 tab 胶囊、右上 mosh/SSH 协议徽标
- 减法:删「新建 window/回首页」按钮、tab 组 +、pane 拖拽调宽,
  window/pane 管理交还 tmux(连带清理 resizePane/newWindow/ui-resize)
- 底部状态行去重:只留链路 + tmux 概览,协议由顶带徽标独占
- 徽标全局统一:淡染底+同色文字胶囊(线框废除),拼写走 TXTech 规范
- CLAUDE.md 记录无头截图横屏帧缓冲右侧裁切大坑

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-26 16:52:19 +08:00
parent e59051a6de
commit 8da725114f
8 changed files with 97 additions and 305 deletions

View File

@@ -3,17 +3,17 @@ import SwiftUI
// `1b` ** chrome **tab
// **** bgDeep pane 86pt
//
//
// [ ] · · [tab ]
// tab = tmux window window tab
// TerminalActionBar.statusLine
// Shimo ** + **
//
// [ ] · · · [ tab ] [Mosh/SSH ]
// tab = tmux window window tab
/// + + tmux window tab
/// + + tmux window tab +
///
/// 稿 / ****
/// ****
/// tab
/// window
/// Shimo / ****macOS +
/// ` · cwd · ` `·`
/// tab = accent Mosh/SSH
/// mosh 线
struct TerminalTopBar: View {
@ObservedObject var session: TerminalSession
let onClose: () -> Void
@@ -24,7 +24,7 @@ struct TerminalTopBar: View {
var body: some View {
HStack(spacing: TX.Space.m) {
// 沿 macOS
HStack(spacing: TX.Space.xs) {
HStack(spacing: TX.Space.s) {
windowButton(icon: "xmark", tint: TXWindowDot.close,
label: "关闭会话", action: onClose)
// chevron =
@@ -36,23 +36,28 @@ struct TerminalTopBar: View {
Spacer(minLength: 0)
}
.frame(height: 40)
// overlay HStack tab ScrollView
// tab chip
.overlay(alignment: .trailing) { transportBadge }
}
/// + (text2) + window title · cwd(textWeak) + (textFaint)
/// text2
/// + ` · cwd · ``·` Shimo
///
private var titleLine: some View {
HStack(spacing: TX.Space.xs) {
TXStatusDot(color: RailSidebarStyle.statusColor(session, p), size: 5)
Text(session.displayName)
.font(TX.Font.mono(12, .medium))
.foregroundStyle(p.text2)
.foregroundStyle(p.text3)
.lineLimit(1)
if !contextLine.isEmpty {
dotSeparator
Text(contextLine)
.font(TX.Font.mono(11))
.foregroundStyle(p.textWeak)
.lineLimit(1)
}
dotSeparator
// resize GridBox.onGridChange
// session.objectWillChange
Text(gridLabel)
@@ -61,6 +66,30 @@ struct TerminalTopBar: View {
}
}
private var dotSeparator: some View {
Text("·")
.font(TX.Font.mono(11))
.foregroundStyle(p.textFaint)
}
/// Shimo + mosh/SSH
/// ****
/// `TXTech`mosh SSH
@ViewBuilder private var transportBadge: some View {
if session.isLive || session.moshEngaged {
let t = TXTech.transport(session.moshEngaged, p)
Text(t.text)
.font(TX.Font.mono(11.5, .medium))
.foregroundStyle(t.tint)
.padding(.horizontal, 12)
.frame(height: 26)
// tint bgDeep badge tab
// chip
.background(t.tint.opacity(0.14), in: Capsule())
.background(p.bgDeep, in: Capsule())
}
}
/// cwd window title tab chip
/// tmux tab 退
private var contextLine: String {
@@ -93,19 +122,17 @@ struct TerminalTopBar: View {
return "\(g.cols)×\(g.rows)"
}
/// / 28pt + **** hover
/// == 14% app
/// / ****Shimo + hover
/// ==
///
private func windowButton(icon: String, tint: Color, label: String,
action: @escaping () -> Void) -> some View {
Button(action: action) {
Image(systemName: icon)
.font(.system(size: 10.5, weight: .bold))
.foregroundStyle(tint)
.frame(width: 28, height: 28)
.background(tint.opacity(0.14), in: RoundedRectangle(cornerRadius: 8))
.overlay(
RoundedRectangle(cornerRadius: 8).strokeBorder(tint.opacity(0.45), lineWidth: 1)
)
.font(.system(size: 11, weight: .bold))
.foregroundStyle(Color.black.opacity(0.55))
.frame(width: 26, height: 26)
.background(tint, in: Circle())
.frame(width: 30, height: 40)
.contentShape(Rectangle())
}
@@ -114,10 +141,11 @@ struct TerminalTopBar: View {
}
}
/// tmux window **tab ** surfaceDark window chip
/// tmux window **tab **Shimo +
/// chip
///
/// tab tmux chip
/// accent + chip `+` window tab
/// **** chip accent chip
/// `+` window tab
/// controller attach tmux / fixture
struct TmuxWindowTabGroup: View {
@ObservedObject var session: TerminalSession
@@ -142,47 +170,28 @@ struct TmuxWindowTabGroup: View {
/// tmux tab controller ObservableObject
private struct WindowTabGroupRow: View {
@ObservedObject var controller: TmuxController
@EnvironmentObject var theme: TXThemeManager
private var p: TXPalette { theme.palette }
var body: some View {
// `+` window tmux
// prefix+c / prefix+&tab ****
TabGroupSlot {
ForEach(controller.windows) { win in
WindowTabChip(window: win, isSelected: win.id == controller.activeWindowID) {
controller.selectWindow(win.id)
}
}
// `+` window
Button { controller.newWindow() } label: {
Image(systemName: "plus")
.font(.system(size: 10.5, weight: .medium))
.foregroundStyle(p.text4)
.frame(width: 28, height: 28)
.contentShape(Rectangle())
}
.buttonStyle(.txPressTight)
.accessibilityLabel("新建 window")
}
}
}
/// tab 3ptchip
/// tab ****chip Shimo
///
private struct TabGroupSlot<Content: View>: View {
@ViewBuilder let content: Content
@EnvironmentObject var theme: TXThemeManager
private var p: TXPalette { theme.palette }
var body: some View {
// +**** ScrollView ScrollView
// chip
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 2) { content }
.padding(3)
.background(p.surfaceDark, in: RoundedRectangle(cornerRadius: TX.Radius.capsule))
.overlay(
RoundedRectangle(cornerRadius: TX.Radius.capsule)
.strokeBorder(p.border, lineWidth: 1)
)
HStack(spacing: TX.Space.hairline) { content }
}
.frame(height: 36)
}
@@ -203,7 +212,8 @@ private struct WindowTabChip: View {
}
}
/// chip window ` `
/// chip window ` ` = accent
/// Shimo =
private struct WindowTabChipBody: View {
let index: String
let title: String
@@ -218,17 +228,12 @@ private struct WindowTabChipBody: View {
.foregroundStyle(isSelected ? TXAccent.text : p.textFaint)
Text(title)
.font(TX.Font.mono(12, isSelected ? .medium : .regular))
.foregroundStyle(isSelected ? p.text : p.text3)
.foregroundStyle(isSelected ? TXAccent.textBright : p.textWeak)
.lineLimit(1)
}
.padding(.horizontal, TX.Space.s)
.padding(.horizontal, 12)
.frame(height: 28)
.background(isSelected ? TXAccent.selectedBg : .clear,
in: RoundedRectangle(cornerRadius: TX.Radius.capsule - 3))
.overlay(
RoundedRectangle(cornerRadius: TX.Radius.capsule - 3)
.strokeBorder(isSelected ? TXAccent.selectedStroke : .clear, lineWidth: 1)
)
.background(isSelected ? TXAccent.selectedBg : .clear, in: Capsule())
.contentShape(Rectangle())
.animation(TX.Motion.quick, value: isSelected)
}
@@ -424,82 +429,39 @@ struct TmuxInstallSheet: View {
}
}
/// + ****
/// ****window tmux
/// /
///
/// **** +
/// `[dot ] | | `
/// **** /
/// tmux tmux ·
/// tab
/// **/**`[dot ] | tmux `mosh/SSH
/// **** mosh
///
/// ****/ /tmux
/// tmux ·
///
/// ****iPad window /
/// `T`
/// UI-5 线
///
/// = 34pt + home indicator **** 齿
/// = 34pt + home indicator ****
/// 线 34pt 24pt ""
struct TerminalActionBar: View {
@ObservedObject var session: TerminalSession
let actions: [Action]
let safeBottom: CGFloat
@EnvironmentObject var theme: TXThemeManager
@Environment(\.horizontalSizeClass) private var hSize
private var p: TXPalette { theme.palette }
struct Action: Identifiable {
let id: String
let icon: String
let title: String
/// window
var enabled: Bool = true
let run: () -> Void
}
/// iPhone 34pt
private var shown: [Action] { hSize == .compact ? Array(actions.prefix(2)) : actions }
var body: some View {
HStack(spacing: TX.Space.s) {
statusLine
Spacer(minLength: TX.Space.s)
ForEach(shown) { action in
Button(action: action.run) {
HStack(spacing: 5) {
Image(systemName: action.icon)
.font(.system(size: 10.5, weight: .medium))
Text(action.title)
.font(TX.Font.mono(11.5, .medium))
}
.foregroundStyle(action.enabled ? p.text2 : p.textFaint)
.padding(.horizontal, TX.Space.xs)
.frame(height: 26)
.background(action.enabled ? p.surface : .clear,
in: RoundedRectangle(cornerRadius: TX.Radius.badge))
.overlay(
RoundedRectangle(cornerRadius: TX.Radius.badge)
.strokeBorder(action.enabled ? p.border : p.divider, lineWidth: 1)
)
// ****
.lineLimit(1)
.fixedSize()
}
.buttonStyle(.txPress)
.disabled(!action.enabled)
}
}
.padding(.horizontal, TX.Space.m)
.frame(height: TX.Layout.footBand(safeBottom))
.animation(TX.Motion.standard, value: contextText)
}
// MARK: -
// MARK: -
private var statusLine: some View {
HStack(spacing: 0) {
cell(egressText, color: egressColor, dot: true)
separator
cell(transportText, color: transportColor, dot: false)
separator
cell(contextText, color: p.text3, dot: false)
}
}
@@ -519,13 +481,13 @@ struct TerminalActionBar: View {
}
}
// tsnet /
// tsnet / / TXTech tsnet
private var egressText: String {
switch session.link {
case .relay: "tsnet"
case .direct: "direct"
case .direct: "直连"
case .connecting: "连接中"
case .reconnecting: "tsnet"
case .reconnecting(let n): "重连中 · 第 \(n)"
case .offline: "离线"
}
}
@@ -539,21 +501,7 @@ struct TerminalActionBar: View {
}
}
// mosh / SSH
private var transportText: String {
if case .reconnecting(let n) = session.link {
return "\(session.moshEngaged ? "mosh" : "ssh") 重连 \(n)"
}
return session.moshEngaged ? "mosh" : "ssh"
}
private var transportColor: Color {
if case .reconnecting = session.link { return p.reconnecting }
// RTT 绿/ RTT
return p.text3
}
// windows
// windows
private var contextText: String {
if let t = session.tmuxSummary {
let name = t.name.isEmpty ? "tmux" : "tmux \(t.name)"