fix: restore sidebar scrolling and glass input capsule
This commit is contained in:
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 = {}
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
Reference in new issue
Block a user