diff --git a/Sources/OpenLessApp/Capsule/CapsuleWindowController.swift b/Sources/OpenLessApp/Capsule/CapsuleWindowController.swift index 6d515278..96651199 100644 --- a/Sources/OpenLessApp/Capsule/CapsuleWindowController.swift +++ b/Sources/OpenLessApp/Capsule/CapsuleWindowController.swift @@ -8,7 +8,7 @@ final class CapsuleWindowController { private let hostingView: NSHostingView private var currentState: CapsuleState = .hidden private var currentLevel: Float = 0 - private let panelSize = NSSize(width: 172, height: 44) + private let panelSize = NSSize(width: 196, height: 56) var onCancel: () -> Void = {} var onConfirm: () -> Void = {} diff --git a/Sources/OpenLessApp/Settings/SettingsView.swift b/Sources/OpenLessApp/Settings/SettingsView.swift index aca9a748..2f0865c4 100644 --- a/Sources/OpenLessApp/Settings/SettingsView.swift +++ b/Sources/OpenLessApp/Settings/SettingsView.swift @@ -74,71 +74,81 @@ private struct FixedSidebar: View { @State private var stats = SidebarStatsSnapshot.load() var body: some View { - VStack(alignment: .leading, spacing: 12) { - WindowControlGlassDock() + ScrollView(.vertical) { + VStack(alignment: .leading, spacing: 12) { + Color.clear + .frame(height: 38) - VStack(alignment: .leading, spacing: 7) { - Text("OpenLess") - .font(.system(size: 23, weight: .semibold)) - Text("自然说话,完美书写") - .font(.caption) - .foregroundStyle(.secondary) - } - .padding(.horizontal, 14) - .padding(.vertical, 12) - .frame(maxWidth: .infinity, alignment: .leading) - .glassPanel(cornerRadius: 18) - - VStack(spacing: 8) { - ForEach(OpenLessMainTab.allCases) { tab in - Button { - selection = tab - } label: { - HStack(spacing: 11) { - Image(systemName: tab.symbol) - .symbolRenderingMode(.hierarchical) - .frame(width: 22) - Text(tab.title) - .font(.system(size: 14, weight: selection == tab ? .semibold : .regular)) - Spacer() - } - .padding(.horizontal, 14) - .padding(.vertical, 10) - .frame(maxWidth: .infinity, alignment: .leading) - .foregroundStyle(selection == tab ? .primary : .secondary) - .background( - selection == tab ? Color.accentColor.opacity(0.13) : Color.clear, - in: RoundedRectangle(cornerRadius: 15, style: .continuous) - ) - .glassPanel(cornerRadius: 15) - } - .buttonStyle(.plain) - .help(tab.title) + VStack(alignment: .leading, spacing: 7) { + Text("OpenLess") + .font(.system(size: 23, weight: .semibold)) + .foregroundStyle(.primary) + Text("自然说话,完美书写") + .font(.caption) + .foregroundStyle(.secondary) } + .padding(.horizontal, 14) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) + + VStack(spacing: 8) { + ForEach(OpenLessMainTab.allCases) { tab in + Button { + selection = tab + } label: { + HStack(spacing: 11) { + Image(systemName: tab.symbol) + .symbolRenderingMode(.hierarchical) + .foregroundStyle(selection == tab ? Color.accentColor : .secondary) + .frame(width: 22) + Text(tab.title) + .font(.system(size: 14, weight: selection == tab ? .semibold : .regular)) + Spacer() + } + .padding(.horizontal, 14) + .padding(.vertical, 10) + .frame(maxWidth: .infinity, alignment: .leading) + .foregroundStyle(selection == tab ? .primary : .secondary) + .background( + selection == tab ? Color.accentColor.opacity(0.12) : Color.primary.opacity(0.035), + in: RoundedRectangle(cornerRadius: 15, style: .continuous) + ) + .overlay( + RoundedRectangle(cornerRadius: 15, style: .continuous) + .strokeBorder(selection == tab ? Color.accentColor.opacity(0.32) : Color.primary.opacity(0.055), lineWidth: 1) + ) + } + .buttonStyle(.plain) + .contentShape(RoundedRectangle(cornerRadius: 15, style: .continuous)) + .help(tab.title) + } + } + + SidebarUsageCard(stats: stats) + SidebarConnectionCard(stats: stats) + + VStack(alignment: .leading, spacing: 7) { + Label("右 Option 开始录音", systemImage: "keyboard") + Label("Esc 取消", systemImage: "escape") + } + .font(.caption) + .foregroundStyle(.secondary) + .padding(.horizontal, 14) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) } - - SidebarUsageCard(stats: stats) - SidebarConnectionCard(stats: stats) - - Spacer() - - VStack(alignment: .leading, spacing: 7) { - Label("右 Option 开始录音", systemImage: "keyboard") - Label("Esc 取消", systemImage: "escape") - } - .font(.caption) - .foregroundStyle(.secondary) - .padding(.horizontal, 14) - .padding(.vertical, 12) - .frame(maxWidth: .infinity, alignment: .leading) - .glassPanel(cornerRadius: 18) + .padding(12) } - .padding(.horizontal, 12) - .padding(.top, 0) - .padding(.bottom, 12) + .scrollIndicators(.hidden) .frame(width: 264) .frame(maxHeight: .infinity, alignment: .top) - .glassPanel(cornerRadius: 32) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 32, style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: 32, style: .continuous) + .strokeBorder(Color.primary.opacity(0.07), lineWidth: 1) + ) .onAppear { refresh() } .onReceive(NotificationCenter.default.publisher(for: .openLessHistoryChanged)) { _ in refresh() } .onReceive(NotificationCenter.default.publisher(for: .openLessDictionaryChanged)) { _ in refresh() } @@ -168,18 +178,6 @@ private struct WindowCanvasBackground: View { } } -private struct WindowControlGlassDock: View { - var body: some View { - RoundedRectangle(cornerRadius: 18, style: .continuous) - .fill(Color.clear) - .frame(width: 92, height: 36) - .glassPanel(cornerRadius: 18) - .padding(.top, 2) - .padding(.leading, 2) - .help("窗口控制") - } -} - private struct SidebarUsageCard: View { let stats: SidebarStatsSnapshot diff --git a/Sources/OpenLessUI/CapsuleView.swift b/Sources/OpenLessUI/CapsuleView.swift index 111406df..bc5232b6 100644 --- a/Sources/OpenLessUI/CapsuleView.swift +++ b/Sources/OpenLessUI/CapsuleView.swift @@ -24,19 +24,21 @@ public struct CapsuleView: View { centerView confirmButton } - .padding(.horizontal, 10) - .frame(width: 152, height: 32) + .padding(.horizontal, 8) + .frame(width: 176, height: 42) .openLessGlass() - .modifier(LegacyCapsuleChrome(state: state)) + .modifier(InputBarChrome(state: state)) } @ViewBuilder private var cancelButton: some View { Button(action: onCancel) { Image(systemName: "xmark") - .font(.system(size: 10, weight: .semibold)) - .frame(width: 14, height: 14) - .foregroundStyle(state == .cancelled ? Color.red : Color.gray.opacity(0.85)) + .font(.system(size: 15, weight: .semibold)) + .frame(width: 28, height: 28) + .foregroundStyle(state == .cancelled ? Color.red : Color.primary.opacity(0.78)) + .background(.thinMaterial, in: Circle()) + .overlay(Circle().strokeBorder(Color.primary.opacity(0.08), lineWidth: 0.8)) .opacity(state == .hidden ? 0 : 1) } .buttonStyle(.plain) @@ -48,9 +50,11 @@ public struct CapsuleView: View { private var confirmButton: some View { Button(action: onConfirm) { Image(systemName: "checkmark") - .font(.system(size: 10, weight: .semibold)) - .frame(width: 14, height: 14) - .foregroundStyle(state == .inserted ? Color.green : Color.white) + .font(.system(size: 15, weight: .semibold)) + .frame(width: 28, height: 28) + .foregroundStyle(state == .inserted ? Color.green : Color.primary) + .background(Color.white.opacity(0.92), in: Circle()) + .overlay(Circle().strokeBorder(Color.primary.opacity(0.08), lineWidth: 0.8)) .opacity(state == .hidden ? 0 : 1) } .buttonStyle(.plain) @@ -84,7 +88,7 @@ public struct CapsuleView: View { } } - private var centerWidth: CGFloat { 82 } + private var centerWidth: CGFloat { 84 } private var isControlEnabled: Bool { state == .listening @@ -99,19 +103,23 @@ public struct CapsuleView: View { } } -/// macOS 15 上保留旧的描边和投影;macOS 26 上完全交给 Liquid Glass。 -private struct LegacyCapsuleChrome: ViewModifier { +private struct InputBarChrome: ViewModifier { let state: CapsuleState func body(content: Content) -> some View { if #available(macOS 26.0, *) { content + .overlay( + Capsule().strokeBorder(Color.white.opacity(0.34), lineWidth: 1) + ) + .shadow(color: .black.opacity(0.18), radius: 18, y: 8) } else { content + .background(Color.white.opacity(0.18), in: Capsule()) .overlay( - Capsule().strokeBorder(Color.primary.opacity(0.08), lineWidth: 0.5) + Capsule().strokeBorder(Color.white.opacity(0.36), lineWidth: 1) ) - .shadow(color: .black.opacity(0.16), radius: 10, y: 4) + .shadow(color: .black.opacity(0.18), radius: 18, y: 8) } } } @@ -123,12 +131,12 @@ private struct AudioBars: View { HStack(spacing: 3) { ForEach(0..<5, id: \.self) { i in Capsule() - .fill(Color.accentColor.opacity(0.9)) + .fill(Color.accentColor.opacity(0.82)) .frame(width: 3, height: barHeight(index: i)) .animation(.spring(response: 0.18, dampingFraction: 0.7), value: level) } } - .frame(width: 35) + .frame(width: 42) } private func barHeight(index: Int) -> CGFloat {