@@ -32,6 +32,27 @@ impl Thickness {
3232 }
3333 }
3434}
35+ /// Whether scanning moves on by itself after a selection that performed
36+ /// an action.
37+ #[ derive( Debug , Clone , Copy , Default , PartialEq , Eq , Serialize , Deserialize ) ]
38+ #[ serde( rename_all = "camelCase" ) ]
39+ pub enum NextScan {
40+ /// What the scanner has always done.
41+ #[ default]
42+ Standard ,
43+ Automatic ,
44+ Wait ,
45+ }
46+ /// Where scanning starts after a selection that performed an action.
47+ #[ derive( Debug , Clone , Copy , Default , PartialEq , Eq , Serialize , Deserialize ) ]
48+ #[ serde( rename_all = "camelCase" ) ]
49+ pub enum StartFrom {
50+ /// What the scanner has always done.
51+ #[ default]
52+ Standard ,
53+ Beginning ,
54+ Selection ,
55+ }
3556#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
3657pub enum Area {
3758 Point ,
@@ -49,6 +70,8 @@ pub struct Overrides {
4970 pub pattern : Option < Pattern > ,
5071 pub color : Option < ScannerColor > ,
5172 pub thickness : Option < Thickness > ,
73+ pub next_scan : Option < NextScan > ,
74+ pub start_from : Option < StartFrom > ,
5275}
5376#[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
5477#[ serde( rename_all = "camelCase" , default ) ]
@@ -57,6 +80,8 @@ pub struct Preferences {
5780 pub pass_limit : usize ,
5881 pub pattern : Pattern ,
5982 pub thickness : Thickness ,
83+ pub next_scan : NextScan ,
84+ pub start_from : StartFrom ,
6085 pub point : Overrides ,
6186 pub menu : Overrides ,
6287 pub keyboard : Overrides ,
@@ -69,6 +94,8 @@ impl Default for Preferences {
6994 pass_limit : 3 ,
7095 pattern : Pattern :: Grouped ,
7196 thickness : Thickness :: Standard ,
97+ next_scan : NextScan :: Standard ,
98+ start_from : StartFrom :: Standard ,
7299 point : Default :: default ( ) ,
73100 menu : Default :: default ( ) ,
74101 keyboard : Default :: default ( ) ,
@@ -85,6 +112,8 @@ pub struct Resolved {
85112 pub pattern : Pattern ,
86113 pub color : ScannerColor ,
87114 pub thickness : Thickness ,
115+ pub next_scan : NextScan ,
116+ pub start_from : StartFrom ,
88117}
89118impl Default for Resolved {
90119 fn default ( ) -> Self {
@@ -134,13 +163,22 @@ impl Preferences {
134163 } ,
135164 color : local. color . unwrap_or ( color) ,
136165 thickness : local. thickness . unwrap_or ( self . thickness ) ,
166+ next_scan : local. next_scan . unwrap_or ( self . next_scan ) ,
167+ start_from : local. start_from . unwrap_or ( self . start_from ) ,
137168 }
138169 }
139170}
140171pub fn deserialize_preferences < ' de , D : serde:: Deserializer < ' de > > (
141172 deserializer : D ,
142173) -> Result < Preferences , D :: Error > {
143- let value = serde_json:: Value :: deserialize ( deserializer) ?;
174+ let mut value = serde_json:: Value :: deserialize ( deserializer) ?;
175+ // A choice this version does not know must not cost the other settings.
176+ forget_unknown_choices ( & mut value, false ) ;
177+ for area in [ "point" , "menu" , "keyboard" , "mouse" ] {
178+ if let Some ( local) = value. get_mut ( area) {
179+ forget_unknown_choices ( local, true ) ;
180+ }
181+ }
144182 let mut preferences: Preferences = serde_json:: from_value ( value) . unwrap_or_default ( ) ;
145183 if !valid_passes ( preferences. pass_limit ) {
146184 preferences. pass_limit = 3 ;
@@ -156,10 +194,77 @@ pub fn deserialize_preferences<'de, D: serde::Deserializer<'de>>(
156194 }
157195 Ok ( preferences)
158196}
197+ /// Only a scanner's own choice may be absent, which is saved as null.
198+ fn forget_unknown_choices ( value : & mut serde_json:: Value , optional : bool ) {
199+ let Some ( fields) = value. as_object_mut ( ) else {
200+ return ;
201+ } ;
202+ fields. retain ( |name, choice| match name. as_str ( ) {
203+ "nextScan" => ( optional && choice. is_null ( ) ) || NextScan :: deserialize ( & * choice) . is_ok ( ) ,
204+ "startFrom" => ( optional && choice. is_null ( ) ) || StartFrom :: deserialize ( & * choice) . is_ok ( ) ,
205+ _ => true ,
206+ } ) ;
207+ }
159208
160209#[ cfg( test) ]
161210mod tests {
162211 use super :: * ;
212+ fn read ( value : serde_json:: Value ) -> Preferences {
213+ deserialize_preferences ( value) . unwrap ( )
214+ }
215+ #[ test]
216+ fn after_selection_choices_default_to_standard_and_round_trip ( ) {
217+ let saved = read ( serde_json:: json!( { "direction" : "reverse" } ) ) ;
218+ assert_eq ! ( saved. next_scan, NextScan :: Standard ) ;
219+ assert_eq ! ( saved. start_from, StartFrom :: Standard ) ;
220+ for area in [ Area :: Point , Area :: Menu , Area :: Keyboard , Area :: Mouse ] {
221+ let resolved = saved. resolve ( area, true , 1000 , ScannerColor :: Blue ) ;
222+ assert_eq ! ( resolved. next_scan, NextScan :: Standard ) ;
223+ assert_eq ! ( resolved. start_from, StartFrom :: Standard ) ;
224+ }
225+ let mut chosen = Preferences {
226+ next_scan : NextScan :: Wait ,
227+ start_from : StartFrom :: Selection ,
228+ ..Default :: default ( )
229+ } ;
230+ chosen. menu . next_scan = Some ( NextScan :: Automatic ) ;
231+ chosen. mouse . start_from = Some ( StartFrom :: Beginning ) ;
232+ let stored = serde_json:: to_value ( & chosen) . unwrap ( ) ;
233+ assert_eq ! ( stored[ "nextScan" ] , "wait" ) ;
234+ assert_eq ! ( stored[ "startFrom" ] , "selection" ) ;
235+ assert_eq ! ( stored[ "menu" ] [ "nextScan" ] , "automatic" ) ;
236+ assert_eq ! ( stored[ "mouse" ] [ "startFrom" ] , "beginning" ) ;
237+ assert_eq ! ( read( stored) , chosen) ;
238+ let menu = chosen. resolve ( Area :: Menu , true , 1000 , ScannerColor :: Blue ) ;
239+ assert_eq ! ( menu. next_scan, NextScan :: Automatic ) ;
240+ assert_eq ! ( menu. start_from, StartFrom :: Selection ) ;
241+ let mouse = chosen. resolve ( Area :: Mouse , true , 1000 , ScannerColor :: Blue ) ;
242+ assert_eq ! ( mouse. next_scan, NextScan :: Wait ) ;
243+ assert_eq ! ( mouse. start_from, StartFrom :: Beginning ) ;
244+ }
245+ #[ test]
246+ fn an_unknown_choice_falls_back_alone ( ) {
247+ let saved = read ( serde_json:: json!( {
248+ "direction" : "reverse" ,
249+ "passLimit" : 5 ,
250+ "nextScan" : null,
251+ "startFrom" : 7 ,
252+ "keyboard" : { "intervalMs" : 250 , "nextScan" : "later" , "startFrom" : "selection" } ,
253+ "menu" : { "nextScan" : "wait" , "startFrom" : null} ,
254+ } ) ) ;
255+ assert_eq ! ( saved. direction, Direction :: Reverse ) ;
256+ assert_eq ! ( saved. pass_limit, 5 ) ;
257+ assert_eq ! ( saved. next_scan, NextScan :: Standard ) ;
258+ assert_eq ! ( saved. start_from, StartFrom :: Standard ) ;
259+ assert_eq ! ( saved. keyboard. interval_ms, Some ( 250 ) ) ;
260+ assert_eq ! ( saved. keyboard. next_scan, None ) ;
261+ assert_eq ! ( saved. keyboard. start_from, Some ( StartFrom :: Selection ) ) ;
262+ assert_eq ! ( saved. menu. next_scan, Some ( NextScan :: Wait ) ) ;
263+ assert_eq ! ( saved. menu. start_from, None ) ;
264+ let saved = read ( serde_json:: json!( { "passLimit" : 5 , "nextScan" : "sometimes" } ) ) ;
265+ assert_eq ! ( saved. pass_limit, 5 ) ;
266+ assert_eq ! ( saved. next_scan, NextScan :: Standard ) ;
267+ }
163268 #[ test]
164269 fn areas_inherit_and_override_independently ( ) {
165270 let mut settings = Preferences :: default ( ) ;
0 commit comments