@@ -5,21 +5,43 @@ use std::time::{Duration, Instant};
55use windows:: Win32 :: Foundation :: { COLORREF , HWND , LPARAM , LRESULT , RECT , WPARAM } ;
66use windows:: Win32 :: Graphics :: Gdi :: {
77 BeginPaint , CreateFontW , DeleteObject , EndPaint , FillRect , GetStockObject , HBRUSH ,
8- InvalidateRect , PAINTSTRUCT , SelectObject , SetBkMode , SetTextColor , TRANSPARENT , TextOutW ,
9- WHITE_BRUSH ,
8+ InvalidateRect , PAINTSTRUCT , SelectObject , SetBkMode , SetTextColor , TRANSPARENT , WHITE_BRUSH ,
109} ;
1110use windows:: Win32 :: UI :: WindowsAndMessaging :: {
12- CreateWindowExW , DefWindowProcW , DestroyWindow , GetClientRect , GetSystemMetrics , HWND_TOPMOST ,
13- KillTimer , RegisterClassW , SM_CXSCREEN , SM_CYSCREEN , SW_HIDE , SW_SHOW , SWP_NOMOVE , SWP_NOSIZE ,
14- SetTimer , SetWindowPos , ShowWindow , WM_CLOSE , WM_PAINT , WM_TIMER , WNDCLASSW , WS_EX_TOOLWINDOW ,
15- WS_EX_TOPMOST , WS_POPUP , WS_VISIBLE ,
11+ CreateWindowExW , DT_CENTER , DT_SINGLELINE , DT_VCENTER , DefWindowProcW , DestroyWindow ,
12+ DrawTextW , GetClientRect , GetSystemMetrics , HWND_TOPMOST , KillTimer , RegisterClassW ,
13+ SM_CXVIRTUALSCREEN , SM_CYVIRTUALSCREEN , SM_XVIRTUALSCREEN , SM_YVIRTUALSCREEN , SW_HIDE , SW_SHOW ,
14+ SWP_NOMOVE , SWP_NOSIZE , SetTimer , SetWindowPos , ShowWindow , WM_CLOSE , WM_KEYDOWN ,
15+ WM_LBUTTONDOWN , WM_PAINT , WM_TIMER , WNDCLASSW , WS_EX_TOOLWINDOW , WS_EX_TOPMOST , WS_POPUP ,
16+ WS_VISIBLE ,
1617} ;
1718use windows:: core:: PCWSTR ;
1819
1920use super :: super :: constants:: { APP_NAME_ZH , COUNTDOWN_TIMER_ID , COUNTDOWN_WINDOW_CLASS } ;
2021use super :: super :: state:: with_state;
2122use super :: super :: utils:: { SoundType , format_countdown, play_sound, to_wide_string} ;
2223
24+ fn clamp_i32 ( v : i32 , min : i32 , max : i32 ) -> i32 {
25+ if v < min {
26+ min
27+ } else if v > max {
28+ max
29+ } else {
30+ v
31+ }
32+ }
33+
34+ fn line_rect ( full : & RECT , center_y : i32 , line_height : i32 ) -> RECT {
35+ let top = center_y - ( line_height / 2 ) ;
36+ let bottom = center_y + ( line_height / 2 ) ;
37+ RECT {
38+ left : full. left ,
39+ top,
40+ right : full. right ,
41+ bottom,
42+ }
43+ }
44+
2345/// 倒计时窗口过程
2446#[ allow( unsafe_op_in_unsafe_fn, clippy:: too_many_lines) ]
2547pub unsafe extern "system" fn countdown_wndproc (
@@ -60,11 +82,23 @@ pub unsafe extern "system" fn countdown_wndproc(
6082 )
6183 } ) ;
6284
63- // 绘制标题
85+ let width = rect. right - rect. left ;
86+ let height = rect. bottom - rect. top ;
87+ let base = width. min ( height) . max ( 1 ) ;
88+
89+ let title_px = clamp_i32 ( base / 18 , 28 , 56 ) ;
90+ let countdown_px = clamp_i32 ( base / 9 , 64 , 144 ) ;
91+ let hint_px = clamp_i32 ( base / 28 , 18 , 36 ) ;
92+
93+ let title_center_y = rect. top + height * 32 / 100 ;
94+ let countdown_center_y = rect. top + height * 50 / 100 ;
95+ let hint_center_y = rect. top + height * 68 / 100 ;
96+
97+ // 绘制标题(自动居中,避免 DPI/字体导致的错位)
6498 let title = format ! ( "{APP_NAME_ZH} · 休息倒计时" ) ;
6599 let title_wide = to_wide_string ( & title) ;
66100 let title_font = CreateFontW (
67- 48 ,
101+ -title_px ,
68102 0 ,
69103 0 ,
70104 0 ,
@@ -77,18 +111,22 @@ pub unsafe extern "system" fn countdown_wndproc(
77111 0 ,
78112 0 ,
79113 0 ,
80- windows:: core:: w!( "Microsoft YaHei " ) ,
114+ windows:: core:: w!( "Segoe UI " ) ,
81115 ) ;
82116 let old_font = SelectObject ( hdc, title_font) ;
83- let title_y = rect. bottom / 2 - 100 ;
84- #[ allow( clippy:: cast_possible_truncation, clippy:: cast_possible_wrap) ]
85- let title_x = ( rect. right - ( title. chars ( ) . count ( ) as i32 * 24 ) ) / 2 ;
86- let _ = TextOutW ( hdc, title_x, title_y, & title_wide[ ..title_wide. len ( ) - 1 ] ) ;
117+ let mut title_rect = line_rect ( & rect, title_center_y, title_px * 2 ) ;
118+ let _ = DrawTextW (
119+ hdc,
120+ PCWSTR ( title_wide. as_ptr ( ) ) ,
121+ -1 ,
122+ & raw mut title_rect,
123+ DT_CENTER | DT_VCENTER | DT_SINGLELINE ,
124+ ) ;
87125
88126 // 绘制倒计时数字
89127 let countdown_wide = to_wide_string ( & countdown_text) ;
90128 let countdown_font = CreateFontW (
91- 96 ,
129+ -countdown_px ,
92130 0 ,
93131 0 ,
94132 0 ,
@@ -101,24 +139,23 @@ pub unsafe extern "system" fn countdown_wndproc(
101139 0 ,
102140 0 ,
103141 0 ,
104- windows:: core:: w!( "Microsoft YaHei " ) ,
142+ windows:: core:: w!( "Segoe UI " ) ,
105143 ) ;
106144 let _ = SelectObject ( hdc, countdown_font) ;
107- let countdown_y = rect. bottom / 2 - 30 ;
108- #[ allow( clippy:: cast_possible_truncation, clippy:: cast_possible_wrap) ]
109- let countdown_x = ( rect. right - ( countdown_text. chars ( ) . count ( ) as i32 * 48 ) ) / 2 ;
110- let _ = TextOutW (
145+ let mut countdown_rect = line_rect ( & rect, countdown_center_y, countdown_px * 2 ) ;
146+ let _ = DrawTextW (
111147 hdc,
112- countdown_x,
113- countdown_y,
114- & countdown_wide[ ..countdown_wide. len ( ) - 1 ] ,
148+ PCWSTR ( countdown_wide. as_ptr ( ) ) ,
149+ -1 ,
150+ & raw mut countdown_rect,
151+ DT_CENTER | DT_VCENTER | DT_SINGLELINE ,
115152 ) ;
116153
117154 // 绘制提示文本
118155 let hint = "放松眼睛,伸展身体" ;
119156 let hint_wide = to_wide_string ( hint) ;
120157 let hint_font = CreateFontW (
121- 32 ,
158+ -hint_px ,
122159 0 ,
123160 0 ,
124161 0 ,
@@ -131,46 +168,24 @@ pub unsafe extern "system" fn countdown_wndproc(
131168 0 ,
132169 0 ,
133170 0 ,
134- windows:: core:: w!( "Microsoft YaHei " ) ,
171+ windows:: core:: w!( "Segoe UI " ) ,
135172 ) ;
136173 let _ = SelectObject ( hdc, hint_font) ;
137174 let _ = SetTextColor ( hdc, COLORREF ( 0x0080_8080 ) ) ; // 灰色
138- let hint_y = rect. bottom / 2 + 80 ;
139- #[ allow( clippy:: cast_possible_truncation, clippy:: cast_possible_wrap) ]
140- let hint_x = ( rect. right - ( hint. chars ( ) . count ( ) as i32 * 16 ) ) / 2 ;
141- let _ = TextOutW ( hdc, hint_x, hint_y, & hint_wide[ ..hint_wide. len ( ) - 1 ] ) ;
142-
143- // 绘制跳过按钮提示
144- let skip_hint = "按 ESC 或点击任意位置跳过" ;
145- let skip_wide = to_wide_string ( skip_hint) ;
146- let skip_font = CreateFontW (
147- 24 ,
148- 0 ,
149- 0 ,
150- 0 ,
151- 400 ,
152- 0 ,
153- 0 ,
154- 0 ,
155- 0 ,
156- 0 ,
157- 0 ,
158- 0 ,
159- 0 ,
160- windows:: core:: w!( "Microsoft YaHei" ) ,
175+ let mut hint_rect = line_rect ( & rect, hint_center_y, hint_px * 2 ) ;
176+ let _ = DrawTextW (
177+ hdc,
178+ PCWSTR ( hint_wide. as_ptr ( ) ) ,
179+ -1 ,
180+ & raw mut hint_rect,
181+ DT_CENTER | DT_VCENTER | DT_SINGLELINE ,
161182 ) ;
162- let _ = SelectObject ( hdc, skip_font) ;
163- let skip_y = rect. bottom / 2 + 140 ;
164- #[ allow( clippy:: cast_possible_truncation, clippy:: cast_possible_wrap) ]
165- let skip_x = ( rect. right - ( skip_hint. chars ( ) . count ( ) as i32 * 12 ) ) / 2 ;
166- let _ = TextOutW ( hdc, skip_x, skip_y, & skip_wide[ ..skip_wide. len ( ) - 1 ] ) ;
167183
168184 // 清理
169185 let _ = SelectObject ( hdc, old_font) ;
170186 let _ = DeleteObject ( title_font) ;
171187 let _ = DeleteObject ( countdown_font) ;
172188 let _ = DeleteObject ( hint_font) ;
173- let _ = DeleteObject ( skip_font) ;
174189
175190 let _ = EndPaint ( hwnd, & raw const ps) ;
176191 LRESULT ( 0 )
@@ -187,19 +202,10 @@ pub unsafe extern "system" fn countdown_wndproc(
187202 }
188203 LRESULT ( 0 )
189204 }
190- // 处理鼠标点击和键盘事件 - 跳过休息
191- 0x0201 | 0x0100 => {
192- // WM_LBUTTONDOWN | WM_KEYDOWN
193- // ESC 键或鼠标点击跳过
194- if msg == 0x0100 && wparam. 0 != 0x1B {
195- // 不是 ESC 键
196- return DefWindowProcW ( hwnd, msg, wparam, lparam) ;
197- }
198- close_countdown_window ( ) ;
199- LRESULT ( 0 )
200- }
205+ // 禁止跳过休息:吞掉鼠标点击 / 键盘事件
206+ WM_LBUTTONDOWN | WM_KEYDOWN => LRESULT ( 0 ) ,
201207 WM_CLOSE => {
202- close_countdown_window ( ) ;
208+ // 禁止用户关闭窗口;程序结束/计时结束时会主动 DestroyWindow
203209 LRESULT ( 0 )
204210 }
205211 _ => DefWindowProcW ( hwnd, msg, wparam, lparam) ,
@@ -230,9 +236,11 @@ pub fn show_countdown_window(seconds: u64, play_start_sound: bool) {
230236 play_sound ( SoundType :: BreakStart ) ;
231237 }
232238
233- // 获取屏幕尺寸
234- let screen_width = unsafe { GetSystemMetrics ( SM_CXSCREEN ) } ;
235- let screen_height = unsafe { GetSystemMetrics ( SM_CYSCREEN ) } ;
239+ // 覆盖所有显示器(虚拟屏幕),避免多屏幕下窗口尺寸/位置错乱
240+ let screen_x = unsafe { GetSystemMetrics ( SM_XVIRTUALSCREEN ) } ;
241+ let screen_y = unsafe { GetSystemMetrics ( SM_YVIRTUALSCREEN ) } ;
242+ let screen_width = unsafe { GetSystemMetrics ( SM_CXVIRTUALSCREEN ) } ;
243+ let screen_height = unsafe { GetSystemMetrics ( SM_CYVIRTUALSCREEN ) } ;
236244
237245 let class_name = to_wide_string ( COUNTDOWN_WINDOW_CLASS ) ;
238246
@@ -243,8 +251,8 @@ pub fn show_countdown_window(seconds: u64, play_start_sound: bool) {
243251 PCWSTR ( class_name. as_ptr ( ) ) ,
244252 PCWSTR :: null ( ) ,
245253 WS_POPUP | WS_VISIBLE ,
246- 0 ,
247- 0 ,
254+ screen_x ,
255+ screen_y ,
248256 screen_width,
249257 screen_height,
250258 None ,
0 commit comments