@@ -8,8 +8,12 @@ mod updater_tab;
88
99use crate :: config:: AppConfig ;
1010use crate :: Cmd ;
11+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
1112use std:: sync:: { Arc , Mutex } ;
1213
14+ // Prevents opening a second settings window while one is already showing.
15+ static SETTINGS_OPEN : AtomicBool = AtomicBool :: new ( false ) ;
16+
1317#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
1418pub enum Tab {
1519 Hotkeys ,
@@ -52,9 +56,10 @@ pub struct SettingsApp {
5256 pub config_shared : Arc < Mutex < AppConfig > > ,
5357 pub cmd_tx : std:: sync:: mpsc:: Sender < Cmd > ,
5458 pub current_tab : Tab ,
55- pub hotkey_error : Option < String > ,
5659 pub update_status : Option < String > ,
5760 pub startup_registered : bool ,
61+ /// Set to true by any tab that modifies config; triggers a save at end of frame.
62+ pub dirty : bool ,
5863}
5964
6065impl SettingsApp {
@@ -66,33 +71,22 @@ impl SettingsApp {
6671 config_shared,
6772 cmd_tx,
6873 current_tab : Tab :: Hotkeys ,
69- hotkey_error : None ,
7074 update_status : None ,
7175 startup_registered,
76+ dirty : false ,
7277 }
7378 }
7479
75- fn apply ( & mut self ) {
76- // Validate hotkeys are unique
77- let h = & self . config . hotkeys ;
78- if h. icons == h. taskbar || h. icons == h. windows || h. taskbar == h. windows {
79- self . hotkey_error = Some ( "All three hotkeys must be unique." . to_string ( ) ) ;
80- return ;
81- }
82- self . hotkey_error = None ;
83-
84- // Persist config to disk
80+ /// Persist the current config to disk and notify the main loop.
81+ /// Does not validate hotkeys — the hotkeys tab handles that inline.
82+ pub fn save_now ( & mut self ) {
8583 if let Err ( e) = crate :: config:: save_config ( & self . config ) {
8684 eprintln ! ( "Failed to save config: {e}" ) ;
8785 }
88-
89- // Update the shared config so other threads can see it
9086 {
9187 let mut shared = self . config_shared . lock ( ) . unwrap ( ) ;
9288 * shared = self . config . clone ( ) ;
9389 }
94-
95- // Notify main loop
9690 let _ = self . cmd_tx . send ( Cmd :: ConfigUpdated ( self . config . clone ( ) ) ) ;
9791 }
9892}
@@ -116,31 +110,28 @@ impl eframe::App for SettingsApp {
116110 Tab :: Discord => self . discord_tab ( ui) ,
117111 Tab :: About => self . about_tab ( ui) ,
118112 } ) ;
113+ } ) ;
119114
120- ui. separator ( ) ;
121-
122- // Hotkey validation error only applies on the Hotkeys tab
123- if self . current_tab == Tab :: Hotkeys {
124- if let Some ( ref err) = self . hotkey_error . clone ( ) {
125- ui. colored_label ( egui:: Color32 :: RED , err) ;
126- }
115+ // Auto-save at end of frame if anything changed.
116+ // Skip if hotkeys are currently invalid (duplicate) — the tab shows a warning.
117+ if self . dirty {
118+ self . dirty = false ;
119+ let h = & self . config . hotkeys ;
120+ let hotkeys_ok = h. icons != h. taskbar && h. icons != h. windows && h. taskbar != h. windows ;
121+ if hotkeys_ok {
122+ self . save_now ( ) ;
127123 }
128-
129- ui. horizontal ( |ui| {
130- // Apply & Save doesn't make sense on the About tab
131- if self . current_tab != Tab :: About && ui. button ( "Apply & Save" ) . clicked ( ) {
132- self . apply ( ) ;
133- }
134- if ui. button ( "Close" ) . clicked ( ) {
135- ctx. send_viewport_cmd ( egui:: ViewportCommand :: Close ) ;
136- }
137- } ) ;
138- } ) ;
124+ }
139125 }
140126}
141127
142- /// Open the Settings window on a background thread so the main loop keeps running.
128+ /// Open the settings window on a background thread so the main loop keeps running.
129+ /// If a settings window is already open, this is a no-op.
143130pub fn open_settings ( config_shared : Arc < Mutex < AppConfig > > , cmd_tx : std:: sync:: mpsc:: Sender < Cmd > ) {
131+ if SETTINGS_OPEN . swap ( true , Ordering :: SeqCst ) {
132+ return ;
133+ }
134+
144135 std:: thread:: spawn ( move || {
145136 let native_options = eframe:: NativeOptions {
146137 viewport : egui:: ViewportBuilder :: default ( )
@@ -164,5 +155,8 @@ pub fn open_settings(config_shared: Arc<Mutex<AppConfig>>, cmd_tx: std::sync::mp
164155 crate :: dlog!( "Settings window error: {}" , e) ;
165156 eprintln ! ( "Settings window error: {e}" ) ;
166157 }
158+
159+ // Allow the settings window to be opened again.
160+ SETTINGS_OPEN . store ( false , Ordering :: SeqCst ) ;
167161 } ) ;
168162}
0 commit comments