@@ -17,7 +17,7 @@ class OverlayWindowController {
1717
1818 // 创建一个无边框的悬浮窗口
1919 window = NSWindow (
20- contentRect: NSRect ( x: 0 , y: 0 , width: 320 , height: 80 ) ,
20+ contentRect: NSRect ( x: 0 , y: 0 , width: 400 , height: 200 ) ,
2121 styleMask: [ . borderless] ,
2222 backing: . buffered,
2323 defer: false
@@ -112,12 +112,17 @@ class OverlayViewModel: ObservableObject {
112112 }
113113}
114114
115- /// 悬浮窗口视图 - 类似截图中的样式
115+ /// 悬浮窗口视图 - 自适应宽度与滚动效果
116116struct OverlayView : View {
117117 @ObservedObject var viewModel : OverlayViewModel
118118
119+ // 布局常量
120+ private let maxWidth : CGFloat = 400 // 最大宽度
121+ private let maxLines : Int = 5 // 最大行数
122+ private let lineHeight : CGFloat = 20 // 每行高度
123+
119124 var body : some View {
120- VStack ( spacing: 8 ) {
125+ VStack ( alignment : . leading , spacing: 8 ) {
121126 // 主状态指示器
122127 HStack ( spacing: 12 ) {
123128 // 左侧图标区域 - 固定宽度
@@ -146,28 +151,62 @@ struct OverlayView: View {
146151 . foregroundColor ( . white)
147152 . frame ( width: 75 , alignment: . leading)
148153 }
154+ . frame ( maxWidth: . infinity, alignment: . center)
149155
150156 // 识别结果显示(当有文字时)
151157 if !viewModel. recognizedText. isEmpty {
152- Text ( viewModel. recognizedText)
153- . font ( . system( size: 13 ) )
154- . foregroundColor ( . white. opacity ( 0.9 ) )
155- . lineLimit ( 2 )
156- . truncationMode ( . head)
157- . frame ( maxWidth: 280 )
158- . multilineTextAlignment ( . center)
158+ textContentView
159159 }
160160 }
161161 . padding ( . horizontal, 16 )
162162 . padding ( . vertical, 10 )
163+ . frame ( maxWidth: maxWidth)
163164 . background (
164- Capsule ( )
165+ RoundedRectangle ( cornerRadius : 16 )
165166 . fill ( Color . black. opacity ( 0.75 ) )
166167 )
167- . animation ( . easeInOut( duration: 0.1 ) , value: viewModel. animationPhase)
168+ . animation ( . easeInOut( duration: 0.15 ) , value: viewModel. animationPhase)
168169 . animation ( . easeInOut( duration: 0.2 ) , value: viewModel. recognizedText)
169170 }
170171
172+ @ViewBuilder
173+ private var textContentView : some View {
174+ let textHeight = calculateTextHeight ( viewModel. recognizedText)
175+ let displayHeight = min ( textHeight, CGFloat ( maxLines) * lineHeight)
176+ let needsScroll = textHeight > displayHeight
177+
178+ if needsScroll {
179+ ScrollViewReader { proxy in
180+ ScrollView ( . vertical, showsIndicators: false ) {
181+ Text ( viewModel. recognizedText)
182+ . font ( . system( size: 13 ) )
183+ . foregroundColor ( . white. opacity ( 0.9 ) )
184+ . frame ( width: maxWidth - 48 , alignment: . leading)
185+ . id ( " bottom " )
186+ }
187+ . frame ( width: maxWidth - 32 , height: displayHeight)
188+ . onChange ( of: viewModel. recognizedText) { _, _ in
189+ withAnimation ( . easeOut( duration: 0.15 ) ) {
190+ proxy. scrollTo ( " bottom " , anchor: . bottom)
191+ }
192+ }
193+ }
194+ } else {
195+ Text ( viewModel. recognizedText)
196+ . font ( . system( size: 13 ) )
197+ . foregroundColor ( . white. opacity ( 0.9 ) )
198+ . frame ( maxWidth: maxWidth - 32 , alignment: . leading)
199+ . fixedSize ( horizontal: false , vertical: true )
200+ }
201+ }
202+
203+ private func calculateTextHeight( _ text: String ) -> CGFloat {
204+ // 估算文字行数(简化计算)
205+ let avgCharsPerLine = 25 // 每行大约25个字符
206+ let lines = max ( 1 , ( text. count + avgCharsPerLine - 1 ) / avgCharsPerLine)
207+ return CGFloat ( lines) * lineHeight
208+ }
209+
171210 private func waveHeight( for index: Int ) -> CGFloat {
172211 let phase = viewModel. animationPhase + CGFloat( index) * 0.6
173212 let baseHeight : CGFloat = 6
0 commit comments