11//! Theme System
22
33use std:: sync:: { OnceLock , RwLock } ;
4- #[ cfg( target_os = "windows" ) ]
5- use std:: time:: Duration ;
64use std:: time:: Instant ;
75
86use bitfun_core:: infrastructure:: try_get_path_manager_arc;
@@ -20,109 +18,13 @@ const AGENT_COMPANION_WINDOW_MAX_WIDTH: f64 = 360.0;
2018const AGENT_COMPANION_WINDOW_MAX_HEIGHT : f64 = 240.0 ;
2119const AGENT_COMPANION_WINDOW_MARGIN : i32 = 64 ;
2220const AGENT_COMPANION_WINDOW_EDGE_MARGIN : f64 = 8.0 ;
23- #[ cfg( target_os = "windows" ) ]
24- const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX : Duration = Duration :: from_millis ( 150 ) ;
25- #[ cfg( target_os = "windows" ) ]
26- const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN : Duration = Duration :: from_millis ( 16 ) ;
27- #[ cfg( target_os = "windows" ) ]
28- const WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL : Duration = Duration :: from_millis ( 8 ) ;
29-
3021static AGENT_COMPANION_WINDOW_OPS : OnceLock < tokio:: sync:: Mutex < ( ) > > = OnceLock :: new ( ) ;
3122static AGENT_COMPANION_WINDOW_LAST_POSITION : OnceLock < RwLock < Option < tauri:: LogicalPosition < f64 > > > > =
3223 OnceLock :: new ( ) ;
3324static STARTUP_THEME_BOOTSTRAP_MANIFEST : OnceLock < StartupThemeBootstrapManifest > = OnceLock :: new ( ) ;
3425
3526const STARTUP_THEME_BOOTSTRAP_JSON : & str = include_str ! ( "generated/startup_theme_bootstrap.json" ) ;
3627
37- #[ cfg( target_os = "windows" ) ]
38- #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
39- enum WindowsMaximizeShowWaitAction {
40- Ready ,
41- Sleep ( Duration ) ,
42- TimedOut ,
43- }
44-
45- #[ cfg( target_os = "windows" ) ]
46- fn windows_maximize_show_wait_action (
47- is_maximized : Option < bool > ,
48- elapsed : Duration ,
49- ) -> WindowsMaximizeShowWaitAction {
50- if is_maximized == Some ( true ) && elapsed >= WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN {
51- return WindowsMaximizeShowWaitAction :: Ready ;
52- }
53-
54- if elapsed >= WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX {
55- return WindowsMaximizeShowWaitAction :: TimedOut ;
56- }
57-
58- let target_wait = if is_maximized == Some ( true ) {
59- WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN
60- } else {
61- WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX
62- } ;
63- WindowsMaximizeShowWaitAction :: Sleep (
64- target_wait
65- . saturating_sub ( elapsed)
66- . min ( WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL ) ,
67- )
68- }
69-
70- #[ cfg( target_os = "windows" ) ]
71- fn read_windows_maximize_state (
72- window : & tauri:: WebviewWindow ,
73- logged_error : & mut bool ,
74- ) -> Option < bool > {
75- match window. is_maximized ( ) {
76- Ok ( is_maximized) => Some ( is_maximized) ,
77- Err ( error) => {
78- if !* logged_error {
79- warn ! (
80- "Failed to read main window maximize state during startup show wait: {}" ,
81- error
82- ) ;
83- * logged_error = true ;
84- }
85- None
86- }
87- }
88- }
89-
90- #[ cfg( target_os = "windows" ) ]
91- fn wait_for_windows_maximize_before_show ( window : & tauri:: WebviewWindow ) -> & ' static str {
92- let started_at = Instant :: now ( ) ;
93- let mut logged_error = false ;
94-
95- loop {
96- match windows_maximize_show_wait_action (
97- read_windows_maximize_state ( window, & mut logged_error) ,
98- started_at. elapsed ( ) ,
99- ) {
100- WindowsMaximizeShowWaitAction :: Ready => return "ready" ,
101- WindowsMaximizeShowWaitAction :: TimedOut => return "timeout" ,
102- WindowsMaximizeShowWaitAction :: Sleep ( duration) => std:: thread:: sleep ( duration) ,
103- }
104- }
105- }
106-
107- #[ cfg( target_os = "windows" ) ]
108- async fn wait_for_windows_maximize_before_show_async (
109- window : & tauri:: WebviewWindow ,
110- ) -> & ' static str {
111- let started_at = Instant :: now ( ) ;
112- let mut logged_error = false ;
113-
114- loop {
115- match windows_maximize_show_wait_action (
116- read_windows_maximize_state ( window, & mut logged_error) ,
117- started_at. elapsed ( ) ,
118- ) {
119- WindowsMaximizeShowWaitAction :: Ready => return "ready" ,
120- WindowsMaximizeShowWaitAction :: TimedOut => return "timeout" ,
121- WindowsMaximizeShowWaitAction :: Sleep ( duration) => tokio:: time:: sleep ( duration) . await ,
122- }
123- }
124- }
125-
12628fn agent_companion_window_ops ( ) -> & ' static tokio:: sync:: Mutex < ( ) > {
12729 AGENT_COMPANION_WINDOW_OPS . get_or_init ( || tokio:: sync:: Mutex :: new ( ( ) ) )
12830}
@@ -575,6 +477,7 @@ pub fn create_main_window(
575477 let mut builder = tauri:: WebviewWindowBuilder :: new ( app_handle, "main" , main_url)
576478 . title ( "BitFun" )
577479 . inner_size ( 1200.0 , 800.0 )
480+ . center ( )
578481 . resizable ( true )
579482 . fullscreen ( false )
580483 . visible ( false )
@@ -660,34 +563,6 @@ fn show_main_window_for_startup(
660563 total_started_at : Instant ,
661564 startup_trace : & DesktopStartupTrace ,
662565) {
663- #[ cfg( target_os = "windows" ) ]
664- {
665- let step_started_at = Instant :: now ( ) ;
666- if let Err ( error) = window. maximize ( ) {
667- warn ! ( "Failed to maximize main window during startup: {}" , error) ;
668- } else {
669- startup_trace. record_elapsed_step ( "native_window" , "windows_maximize" , step_started_at) ;
670- debug ! (
671- "Main window startup show step completed: step=maximize duration_ms={} since_create_start_ms={}" ,
672- step_started_at. elapsed( ) . as_millis( ) ,
673- total_started_at. elapsed( ) . as_millis( )
674- ) ;
675- }
676- let show_delay_started_at = Instant :: now ( ) ;
677- let show_wait_outcome = wait_for_windows_maximize_before_show ( window) ;
678- startup_trace. record_elapsed_step (
679- "native_window" ,
680- "windows_show_after_maximize_wait" ,
681- show_delay_started_at,
682- ) ;
683- debug ! (
684- "Main window startup show step completed: step=wait_for_maximize_state outcome={} duration_ms={} since_create_start_ms={}" ,
685- show_wait_outcome,
686- show_delay_started_at. elapsed( ) . as_millis( ) ,
687- total_started_at. elapsed( ) . as_millis( )
688- ) ;
689- }
690-
691566 let show_started_at = Instant :: now ( ) ;
692567 if let Err ( error) = window. show ( ) {
693568 warn ! ( "Failed to show main window during startup: {}" , error) ;
@@ -1012,29 +887,6 @@ pub async fn hide_agent_companion_desktop_pet(app: tauri::AppHandle) -> Result<(
1012887pub async fn show_main_window ( app : tauri:: AppHandle ) -> Result < ( ) , String > {
1013888 let total_started_at = Instant :: now ( ) ;
1014889 if let Some ( main_window) = app. get_webview_window ( "main" ) {
1015- #[ cfg( target_os = "windows" ) ]
1016- {
1017- // Work around Windows startup flicker: avoid creating the native window
1018- // in maximized mode, and maximize it right before showing instead.
1019- let step_started_at = Instant :: now ( ) ;
1020- main_window. maximize ( ) . map_err ( |e| {
1021- error ! ( "Failed to maximize main window: {}" , e) ;
1022- format ! ( "Failed to maximize main window: {}" , e)
1023- } ) ?;
1024- debug ! (
1025- "Main window show step completed: step=maximize duration_ms={}" ,
1026- step_started_at. elapsed( ) . as_millis( )
1027- ) ;
1028-
1029- let wait_started_at = Instant :: now ( ) ;
1030- let show_wait_outcome = wait_for_windows_maximize_before_show_async ( & main_window) . await ;
1031- debug ! (
1032- "Main window show step completed: step=wait_for_maximize_state outcome={} duration_ms={}" ,
1033- show_wait_outcome,
1034- wait_started_at. elapsed( ) . as_millis( )
1035- ) ;
1036- }
1037-
1038890 let step_started_at = Instant :: now ( ) ;
1039891 main_window. show ( ) . map_err ( |e| {
1040892 error ! ( "Failed to show main window: {}" , e) ;
@@ -1071,44 +923,3 @@ pub async fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
1071923 ) ;
1072924 Ok ( ( ) )
1073925}
1074-
1075- #[ cfg( test) ]
1076- mod tests {
1077- #[ cfg( target_os = "windows" ) ]
1078- use super :: * ;
1079-
1080- #[ cfg( target_os = "windows" ) ]
1081- #[ test]
1082- fn windows_maximize_show_wait_releases_when_maximized ( ) {
1083- assert_eq ! (
1084- windows_maximize_show_wait_action( Some ( true ) , Duration :: ZERO ) ,
1085- WindowsMaximizeShowWaitAction :: Sleep ( WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL )
1086- ) ;
1087- assert_eq ! (
1088- windows_maximize_show_wait_action( Some ( true ) , WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MIN ) ,
1089- WindowsMaximizeShowWaitAction :: Ready
1090- ) ;
1091- }
1092-
1093- #[ cfg( target_os = "windows" ) ]
1094- #[ test]
1095- fn windows_maximize_show_wait_polls_until_max_wait ( ) {
1096- assert_eq ! (
1097- windows_maximize_show_wait_action( Some ( false ) , Duration :: from_millis( 20 ) ) ,
1098- WindowsMaximizeShowWaitAction :: Sleep ( WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_POLL )
1099- ) ;
1100- assert_eq ! (
1101- windows_maximize_show_wait_action( None , Duration :: from_millis( 148 ) ) ,
1102- WindowsMaximizeShowWaitAction :: Sleep ( Duration :: from_millis( 2 ) )
1103- ) ;
1104- }
1105-
1106- #[ cfg( target_os = "windows" ) ]
1107- #[ test]
1108- fn windows_maximize_show_wait_times_out_at_original_bound ( ) {
1109- assert_eq ! (
1110- windows_maximize_show_wait_action( Some ( false ) , WINDOWS_STARTUP_MAXIMIZE_SHOW_WAIT_MAX ) ,
1111- WindowsMaximizeShowWaitAction :: TimedOut
1112- ) ;
1113- }
1114- }
0 commit comments