- 侧边栏 4 → 6 项:首页 / 历史记录 / 词汇表 / 风格 / 帮助中心 / 设置 - 词典 → 词汇表,整页改 chip 流式布局:phrase + hitCount,hover ✕ 删除, 批量输入(每行一个),重置统计 / 清除全部 - DictionaryEntry 加 hitCount + 兼容旧 dictionary.json 解码 - DictationCoordinator 在润色后扫描最终文本递增命中数; 删除 learnTerms 自动学词路径 - 润色模式 → 风格 (StyleTab):4 张模式卡 + 启用开关; 关闭时跳过 Ark 直接插入原文 (UserPreferences.polishEnabled) - 帮助中心:快速上手 4 步、快捷键速查、FAQ、GitHub 外链 - 设置 Tab 合并 Volc ASR + Ark 凭据、录音、授权、隐私、关于 + 检查更新 - 胶囊:NSPanel 220×96, hasShadow=false;macOS 26 分支去掉 SwiftUI 阴影 → 解决"红绿灯外圈黑边" - 侧边栏视觉:strokeBorder 0.08 → 0.04、阴影 radius 16 → 6 → 解决"灰色边栏 + 黑色卡顿" - 首页底部新增今日概览 + 风格预览
75 lines
2.8 KiB
Swift
75 lines
2.8 KiB
Swift
import AppKit
|
||
import SwiftUI
|
||
import OpenLessUI
|
||
|
||
@MainActor
|
||
final class CapsuleWindowController {
|
||
private let window: NSPanel
|
||
private let hostingView: NSHostingView<CapsuleView>
|
||
private var currentState: CapsuleState = .hidden
|
||
private var currentLevel: Float = 0
|
||
// 比 CapsuleView (176×42) 留出足够空白,让 SwiftUI 的 .shadow(radius 18, y:8)
|
||
// 在 NSHostingView 内自然 fade 到透明,避免被 panel bounds 裁出一圈暗矩形。
|
||
private let panelSize = NSSize(width: 220, height: 96)
|
||
|
||
var onCancel: () -> Void = {}
|
||
var onConfirm: () -> Void = {}
|
||
|
||
init() {
|
||
let initialView = CapsuleView(state: .hidden, level: 0)
|
||
hostingView = NSHostingView(rootView: initialView)
|
||
hostingView.frame = NSRect(origin: .zero, size: panelSize)
|
||
|
||
let panel = NSPanel(
|
||
contentRect: NSRect(origin: .zero, size: panelSize),
|
||
styleMask: [.borderless, .nonactivatingPanel],
|
||
backing: .buffered,
|
||
defer: false
|
||
)
|
||
panel.isOpaque = false
|
||
panel.backgroundColor = .clear
|
||
// 系统阴影按 contentView 矩形 bounds 算,会和 SwiftUI 自带的胶囊阴影叠加成深色矩形边。
|
||
// SwiftUI 那一层已经够看,关掉系统阴影。
|
||
panel.hasShadow = false
|
||
panel.level = .statusBar
|
||
panel.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle, .fullScreenAuxiliary]
|
||
// 必须接受鼠标点击,否则胶囊上的叉/勾按钮无法响应。
|
||
// .nonactivatingPanel 保证点击不会抢走前台 app 的焦点。
|
||
panel.ignoresMouseEvents = false
|
||
panel.contentView = hostingView
|
||
self.window = panel
|
||
|
||
repositionToBottomCenter()
|
||
}
|
||
|
||
func update(state: CapsuleState, level: Float = 0) {
|
||
currentState = state
|
||
currentLevel = level
|
||
hostingView.rootView = CapsuleView(
|
||
state: state,
|
||
level: level,
|
||
onCancel: { [weak self] in self?.onCancel() },
|
||
onConfirm: { [weak self] in self?.onConfirm() }
|
||
)
|
||
if state == .hidden {
|
||
window.orderOut(nil)
|
||
} else {
|
||
if !window.isVisible {
|
||
repositionToBottomCenter()
|
||
window.orderFrontRegardless()
|
||
}
|
||
}
|
||
}
|
||
|
||
private func repositionToBottomCenter() {
|
||
guard let screen = NSScreen.main else { return }
|
||
let screenFrame = screen.visibleFrame
|
||
let size = window.frame.size
|
||
let x = screenFrame.midX - size.width / 2
|
||
// 旧版 panel(196×56) 时胶囊视觉中心位于 visibleFrame.minY + 52。
|
||
// 改大 panel 后保持视觉位置不变:y = 52 - panelHeight/2。
|
||
let y = screenFrame.minY + 52 - size.height / 2
|
||
window.setFrameOrigin(NSPoint(x: x, y: y))
|
||
}
|
||
}
|