mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-29 13:43:49 +00:00
ui(app): command address menu items with native title/subtitle for #72
Picker(.menu) 的选项桥接会把多 Text 压平成独立菜单项(注记成了单独
item)。改为 Menu + adaptive-controls 桥接(Apple technote 文档化的
title/subtitle 机制):
- 菜单项主行 = IP,副标题 = 「注记 - 接口名称」(无注记时仅接口名称)
- 收起状态显示当前选中项的 {IP} - {name}(borderedButton 样式)
- 选择器下方的类型说明行保留,选中地址类型收起时也可见
This commit is contained in:
@@ -200,14 +200,25 @@ struct TerminalProxyEnvironmentCard: View {
|
||||
content: {
|
||||
VStack(spacing: 10) {
|
||||
if control.snapshot.commandAddressPicker.isVisible {
|
||||
Picker("命令地址", selection: commandAddressBinding) {
|
||||
// Menu(而非 Picker):菜单项经 adaptive-controls 机制桥接,
|
||||
// 两个 Text 映射为 NSMenuItem 的原生 title/subtitle;Picker 的
|
||||
// 选项桥接会把多 Text 压平成独立菜单项。
|
||||
Menu {
|
||||
ForEach(
|
||||
control.snapshot.commandAddressPicker.candidates, id: \.identity
|
||||
) { candidate in
|
||||
menuItemLabel(for: candidate).tag(candidate)
|
||||
Button {
|
||||
control.selectCommandAddress(candidate)
|
||||
} label: {
|
||||
Text(candidate.address)
|
||||
Text(menuSubtitle(for: candidate))
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(control.snapshot.commandAddressPicker.selected.label)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.menuStyle(.borderedButton)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
if let annotation = control.snapshot.commandAddressPicker.selected.annotation {
|
||||
Text("地址类型:\(annotation)")
|
||||
@@ -232,20 +243,11 @@ struct TerminalProxyEnvironmentCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var commandAddressBinding: Binding<TerminalCommandAddress> {
|
||||
Binding(
|
||||
get: { control.snapshot.commandAddressPicker.selected },
|
||||
set: { address in control.selectCommandAddress(address) })
|
||||
}
|
||||
|
||||
/// 菜单项文案:主行 `{IP} - {name}`;IPv6 类型注记作为第二个 Text——桥接
|
||||
/// 为 NSMenuItem 的原生 subtitle 次行(macOS 14+),注记缺失时仅主行。
|
||||
@ViewBuilder
|
||||
private func menuItemLabel(for candidate: TerminalCommandAddress) -> some View {
|
||||
Text(candidate.label)
|
||||
if let annotation = candidate.annotation {
|
||||
Text(annotation)
|
||||
}
|
||||
/// 菜单项副标题:接口名称 + IPv6 类型注记(ifconfig 同源),注记在前便于
|
||||
/// 先辨认地址类型;无注记时仅接口名称。
|
||||
private func menuSubtitle(for candidate: TerminalCommandAddress) -> String {
|
||||
guard let annotation = candidate.annotation else { return candidate.displayName }
|
||||
return "\(annotation) - \(candidate.displayName)"
|
||||
}
|
||||
|
||||
private func commandButton(for shell: TerminalCommandShell) -> some View {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
| 所有 IPv4 与 IPv6 接口 | IPv4、IPv6,包含两个回环地址 | 127.0.0.1 |
|
||||
| 仅所有 IPv6 接口 | IPv6 | ::1 |
|
||||
|
||||
每项主行显示 `{IP} - {name}`;带内核标志的 IPv6 地址附与 ifconfig 同源的类型注记(如 `autoconf secured`、`dynamic`),呈现为菜单项副标题行(两个 `Text` 桥接为 `NSMenuItem` 的原生 subtitle)与选择器下方的说明行,便于识别地址稳定性。接口名称优先取 `SCNetworkInterfaceGetLocalizedDisplayName(interface)`;名称缺失或无法映射到 SystemConfiguration 接口时,回退到 BSD 接口名,但不会因此放宽接口类型过滤。回环项回退名称为 lo0。
|
||||
下拉菜单项主行显示 `{IP}`,副标题行显示「注记 - 接口名称」(无注记时仅接口名称;菜单项经 adaptive-controls 机制把两个 `Text` 桥接为 `NSMenuItem` 的原生 title/subtitle,注记与 ifconfig 同源,如 `autoconf secured`、`dynamic`),收起状态显示当前选中项的 `{IP} - {name}`,其下另有类型说明行,便于识别地址稳定性。接口名称优先取 `SCNetworkInterfaceGetLocalizedDisplayName(interface)`;名称缺失或无法映射到 SystemConfiguration 接口时,回退到 BSD 接口名,但不会因此放宽接口类型过滤。回环项回退名称为 lo0。
|
||||
|
||||
回环项置顶,双栈时 127.0.0.1 在 ::1 前。其余按接口名称排序,同一接口先 IPv4 后 IPv6,再按地址稳定排序。同一接口的重复 IP 合并;不同接口即使 IP 相同也保留接口身份。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user