Files
gentpan d68fbce42a 主窗口改用液态玻璃(macOS 26),分段切换改为苹果标准样式,新增全局加载框
- 新增 Design/LiquidGlass.swift:dsGlass 修饰器(macOS 26 用 glassEffect,更早系统与离屏截图退回实色)、isInsideGlass 环境值避免玻璃叠玻璃、LoadingHUD 全局加载框
- 侧边栏是浮在窗口里的玻璃面板,选中项为滑动的淡蓝圆角底;顶栏“在菜单栏显示”一组收进玻璃胶囊
- DSButtonStyle:主按钮蓝色玻璃胶囊、次要按钮透明玻璃;IconButton / MiniIconButton 为圆形玻璃;ChipButton 为玻璃胶囊;DSToggle 在 macOS 26 用系统开关
- SegmentedControl 统一为苹果标准样式:灰色圆角底槽、等宽分段、选中段实心品牌蓝白字并滑动;PillSwitch 同样灰底
- 清理、卸载、导出诊断、修改 DNS 进行中显示全局加载框
- 没有用 safeAreaBar 让页面从顶栏下滚过:macOS 上交界处会画一条分隔线,柔和或隐藏滚动边缘都去不掉
2026-09-16 18:26:09 +05:00

112 lines
4.0 KiB
Swift

import Localization
import SwiftUI
// MARK: - 液态玻璃
/// macOS 26 起,侧边栏、顶栏控件、按钮与切换控件用系统的液态玻璃;更早的系统退回原来的实色样式。
/// 玻璃只给浮在内容之上的导航与控件用,卡片、设置分组这类内容本身保持实色(与系统设置一致)。
extension EnvironmentValues {
/// 已经在一块玻璃里面(比如侧边栏面板):里面的控件不再叠一层玻璃,改用普通的悬停底色
@Entry var isInsideGlass = false
}
extension DS {
enum Glass {
/// 当前系统是否支持液态玻璃
static var isAvailable: Bool {
if #available(macOS 26.0, *) { true } else { false }
}
}
}
extension View {
/// 给控件加一层液态玻璃。`tint` 为品牌色时是蓝色玻璃(主按钮、选中项);
/// `interactive` 为真时按下会有玻璃的形变反馈。不支持玻璃的系统上、以及离屏截图时(拍不出玻璃)画 `fallback` 实色
func dsGlass<S: InsettableShape>(in shape: S, tint: Color? = nil, interactive: Bool = false,
fallback: Color = DS.Palette.elevated) -> some View {
modifier(GlassSurface(shape: shape, tint: tint, interactive: interactive, fallback: fallback))
}
}
private struct GlassSurface<S: InsettableShape>: ViewModifier {
let shape: S
let tint: Color?
let interactive: Bool
let fallback: Color
@Environment(\.isSnapshot) private var isSnapshot
func body(content: Content) -> some View {
if #available(macOS 26.0, *), !isSnapshot {
content.glassEffect(DS.Glass.style(tint: tint, interactive: interactive), in: shape)
} else {
content.background(tint ?? fallback, in: shape)
}
}
}
@available(macOS 26.0, *)
extension DS.Glass {
static func style(tint: Color?, interactive: Bool) -> SwiftUI.Glass {
var glass = SwiftUI.Glass.regular
if let tint { glass = glass.tint(tint) }
if interactive { glass = glass.interactive() }
return glass
}
}
// MARK: - 全局加载
/// 需要等待、期间不宜继续操作的任务(清理、卸载、导出诊断、修改 DNS):
/// 整个窗口压暗,中间浮一块玻璃加载框,写明正在做什么;任务结束自动消失
struct LoadingHUD: View {
let message: String
var body: some View {
ZStack {
// 压暗并拦住点击,避免任务进行中又触发别的操作
Rectangle()
.fill(DS.Palette.scrim)
.contentShape(Rectangle())
.onTapGesture {}
HStack(spacing: DS.Space.s3) {
ProgressView().controlSize(.regular)
Text(verbatim: message)
.dsFont(.base, weight: .medium)
.foregroundStyle(DS.Palette.textPrimary)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
}
.padding(.horizontal, DS.Space.s8)
.padding(.vertical, DS.Space.s6)
.frame(minWidth: DS.Size.sidebarWidth + DS.Space.s16)
.modifier(HUDBackground())
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.updatesFrequently)
}
.transition(.opacity)
}
}
private struct HUDBackground: ViewModifier {
func body(content: Content) -> some View {
if #available(macOS 26.0, *) {
content.glassEffect(.regular, in: RoundedRectangle(cornerRadius: DS.Radius.xl, style: .continuous))
} else {
content
.background(DS.Palette.elevated, in: RoundedRectangle(cornerRadius: DS.Radius.xl, style: .continuous))
.dsShadow(DS.Shadow.level2)
}
}
}
extension View {
/// 有文字时在上方盖一层全局加载框
func loadingHUD(_ message: String?) -> some View {
overlay {
if let message { LoadingHUD(message: message) }
}
.animation(DS.Motion.quick, value: message)
}
}