fix: restore sidebar scrolling and glass input capsule

This commit is contained in:
baiqing committed 2026-04-27 14:34:17 +08:00
1 parent 9866773f34
commit cb00914423
3 files changed
+94 -88

No files matched your search

@@ -8,7 +8,7 @@ final class CapsuleWindowController {
private let hostingView: NSHostingView<CapsuleView>
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 = {}
+69 -71
View File
@@ -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
+24 -16
View File
@@ -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 {