@@ -345,6 +345,7 @@ impl App {
345345 else { Task :: done ( Message :: NextImage ) }
346346 }
347347
348+ #[ expect( clippy:: too_many_lines, reason = "There's a lot to update. Haha." ) ]
348349 /// # Update.
349350 ///
350351 /// This method serves as the entrypoint for the application's
@@ -406,7 +407,7 @@ impl App {
406407 self . flags &= ! OTHER_BSIDE ;
407408 self . current = None ;
408409 while let Some ( src) = self . paths . pop_first ( ) {
409- if let Some ( mut current) = CurrentImage :: new ( src, self . flags ) {
410+ if let Some ( mut current) = CurrentImage :: new ( src. clone ( ) , self . flags ) {
410411 // Add an entry for it.
411412 cli_log ( & current. src , None ) ;
412413 self . done . push ( ImageResults {
@@ -423,6 +424,13 @@ impl App {
423424 return self . update_switch_encoder__ ( ) ;
424425 }
425426 }
427+ // Decode error?
428+ else {
429+ cli_log_sad ( & src) ;
430+ if self . paths . is_empty ( ) {
431+ return Task :: done ( Message :: Error ( MessageError :: NoImages ) ) ;
432+ }
433+ }
426434 }
427435
428436 // If we're here, there are no more images. If --exit-auto,
@@ -548,16 +556,8 @@ impl App {
548556 /// This method sets up listeners for the program's keyboard shortcuts,
549557 /// bubbling up `Message`s as needed.
550558 pub ( super ) fn subscription ( & self ) -> Subscription < Message > {
551- let whenever = iced:: keyboard:: on_key_press ( subscribe_whenever) ;
552-
553- // Some actions are only applicable in A/B contexts.
554- if self . has_candidate ( ) {
555- Subscription :: batch ( vec ! [
556- whenever,
557- iced:: keyboard:: on_key_press( subscribe_ab) ,
558- ] )
559- }
560- else { whenever }
559+ if self . has_candidate ( ) { iced:: keyboard:: on_key_press ( subscribe_ab) }
560+ else { iced:: keyboard:: on_key_press ( subscribe_home) }
561561 }
562562
563563 /// # View.
@@ -697,7 +697,6 @@ impl App {
697697 . width ( Fill )
698698 }
699699
700- #[ expect( clippy:: too_many_lines, reason = "There's lots to do!" ) ]
701700 /// # View: Activity Log.
702701 ///
703702 /// This returns a table containing detailed information about each of the
@@ -738,8 +737,7 @@ impl App {
738737 // The rows, interspersed with dividers for each new source.
739738 let mut last_dir = OsStr :: new ( "" ) ;
740739 for ActivityTableRow { src, kind, quality, len, ratio, time } in & table. 0 {
741- let Some ( dir) = src. parent ( ) . map ( Path :: as_os_str) else { continue ; } ;
742- let Some ( file) = src. file_name ( ) else { continue ; } ;
740+ let Some ( ( dir, file) ) = split_path ( src) else { continue ; } ;
743741 let is_src = matches ! ( kind, ImageKind :: Png | ImageKind :: Jpeg ) ;
744742 let skipped = is_src && time. is_some ( ) ;
745743 let color =
@@ -1246,16 +1244,12 @@ impl App {
12461244
12471245 column ! (
12481246 // Path.
1249- rich_text!(
1250- span( format!(
1251- "{}/" ,
1252- current. src. parent( ) . map_or( Cow :: Borrowed ( "" ) , Path :: to_string_lossy)
1253- ) )
1254- . color( NiceColors :: GREY ) ,
1255- span(
1256- current. src. file_name( ) . map_or( Cow :: Borrowed ( "" ) , |o| o. to_string_lossy( ) ) . into_owned( )
1257- )
1258- . color( fg) ,
1247+ split_path( & current. src) . map_or_else(
1248+ Rich :: new,
1249+ |( dir, name) | rich_text!(
1250+ span( format!( "{}/" , dir. to_string_lossy( ) ) ) . color( NiceColors :: GREY ) ,
1251+ span( name. to_string_lossy( ) . into_owned( ) ) . color( fg) ,
1252+ ) ,
12591253 ) ,
12601254
12611255 // Formats.
@@ -1994,12 +1988,14 @@ impl ImageResultWrapper {
19941988
19951989 // Update the path if they picked one.
19961990 if let Some ( dst) = dst {
1997- self . dst = crate :: with_ng_extension ( dst. path ( ) . to_path_buf ( ) , self . kind ) ;
1991+ let dst = dst. path ( ) . to_path_buf ( ) ;
1992+ if dst. parent ( ) . is_some_and ( Path :: is_dir) && dst. file_name ( ) . is_some ( ) {
1993+ self . dst = crate :: with_ng_extension ( dst, self . kind ) ;
1994+ }
1995+ else { self . best = None ; }
19981996 }
19991997 // Or nuke the result.
2000- else {
2001- let _res = self . best . take ( ) ;
2002- }
1998+ else { self . best = None ; }
20031999
20042000 // Return for saving.
20052001 Message :: SaveImage ( self )
@@ -2167,13 +2163,12 @@ impl Default for WidgetCache {
21672163///
21682164/// Print a quick timestamped message to STDERR in case anybody's watching.
21692165fn cli_log ( src : & Path , quality : Option < Quality > ) {
2170- let Some ( dir) = src. parent ( ) else { return ; } ;
2171- let Some ( name) = src. file_name ( ) else { return ; } ;
2166+ let Some ( ( dir, name) ) = split_path ( src) else { return ; } ;
21722167 let now = FmtUtc2k :: now_local ( ) ;
21732168 let mut out = format ! (
21742169 "\x1b [2m[\x1b [0;34m{}\x1b [0;2m] {}/\x1b [0m{} \x1b [2m(" ,
21752170 now. time( ) ,
2176- dir. display ( ) ,
2171+ dir. to_string_lossy ( ) ,
21772172 name. to_string_lossy( ) ,
21782173 ) ;
21792174
@@ -2193,14 +2188,13 @@ fn cli_log(src: &Path, quality: Option<Quality>) {
21932188///
21942189/// Print a quick timestamped summary of a failed conversion to STDERR.
21952190fn cli_log_sad ( src : & Path ) {
2196- let Some ( dir) = src. parent ( ) else { return ; } ;
2197- let Some ( name) = src. file_name ( ) else { return ; } ;
2191+ let Some ( ( dir, name) ) = split_path ( src) else { return ; } ;
21982192 let now = FmtUtc2k :: now_local ( ) ;
21992193
22002194 eprintln ! (
22012195 "\x1b [2m[\x1b [0;34m{}\x1b [0;2m]\x1b [91m {}/\x1b [0;91m{}\x1b [0m" ,
22022196 now. time( ) ,
2203- dir. display ( ) ,
2197+ dir. to_string_lossy ( ) ,
22042198 name. to_string_lossy( ) ,
22052199 ) ;
22062200}
@@ -2217,11 +2211,21 @@ fn cli_log_error(src: MessageError) {
22172211 ) ;
22182212}
22192213
2220- /// # General Subscriptions .
2214+ /// # Split Path .
22212215///
2222- /// This callback for `on_key_press` binds listeners for the night mode
2223- /// and file dialogue titles, which can be triggered at any time.
2224- fn subscribe_whenever ( key : Key , modifiers : Modifiers ) -> Option < Message > {
2216+ /// Split the `parent` and `file_name`, returning `None` if either fail for
2217+ /// whatever reason.
2218+ fn split_path ( src : & Path ) -> Option < ( & OsStr , & OsStr ) > {
2219+ let dir = src. parent ( ) ?;
2220+ let name = src. file_name ( ) ?;
2221+ Some ( ( dir. as_os_str ( ) , name) )
2222+ }
2223+
2224+ /// # Home Subscriptions.
2225+ ///
2226+ /// This callback for `on_key_press` binds listeners for events available on
2227+ /// the home screen.
2228+ fn subscribe_home ( key : Key , modifiers : Modifiers ) -> Option < Message > {
22252229 // These require CTRL and not ALT.
22262230 if modifiers. command ( ) && ! modifiers. alt ( ) {
22272231 if let Key :: Character ( c) = key {
@@ -2235,15 +2239,23 @@ fn subscribe_whenever(key: Key, modifiers: Modifiers) -> Option<Message> {
22352239
22362240/// # A/B Subscriptions.
22372241///
2238- /// This callback for `on_key_press` binds listeners for A/B-related
2239- /// feedback, applicable only when a candidate image is available, though we
2240- /// can't actually guarantee that's when they'll be triggered.
2242+ /// This callback for `on_key_press` binds listeners for events available on
2243+ /// the A/B screen.
22412244fn subscribe_ab ( key : Key , modifiers : Modifiers ) -> Option < Message > {
2242- // No modifiers required or expected.
2243- if modifiers. command ( ) || modifiers. alt ( ) { None }
2245+ // Nothing needs ALT.
2246+ if modifiers. alt ( ) { None }
2247+ // CTRL+N toggles Night Mode.
2248+ else if modifiers. command ( ) {
2249+ if let Key :: Character ( c) = key {
2250+ if c == "n" { return Some ( Message :: ToggleFlag ( OTHER_NIGHT ) ) ; }
2251+ }
2252+ None
2253+ }
22442254 else {
22452255 match key {
2256+ // Toggle A/B.
22462257 Key :: Named ( Named :: Space ) => Some ( Message :: ToggleFlag ( OTHER_BSIDE ) ) ,
2258+ // Feedback.
22472259 Key :: Character ( c) =>
22482260 if c == "d" { Some ( Message :: Feedback ( false ) ) }
22492261 else if c == "k" { Some ( Message :: Feedback ( true ) ) }
@@ -2252,3 +2264,23 @@ fn subscribe_ab(key: Key, modifiers: Modifiers) -> Option<Message> {
22522264 }
22532265 }
22542266}
2267+
2268+
2269+
2270+ #[ cfg( test) ]
2271+ mod test {
2272+ use super :: * ;
2273+
2274+ #[ test]
2275+ fn t_flags ( ) {
2276+ // Make sure the flags are actually unique.
2277+ let all = [
2278+ FMT_AVIF , FMT_JXL , FMT_WEBP ,
2279+ MODE_LOSSLESS , MODE_LOSSY , MODE_LOSSY_YCBCR ,
2280+ OTHER_BSIDE , OTHER_EXIT_AUTO , OTHER_NIGHT , OTHER_SAVE_AUTO ,
2281+ SWITCHED_ENCODER ,
2282+ ] ;
2283+ let set = all. iter ( ) . copied ( ) . collect :: < BTreeSet < u16 > > ( ) ;
2284+ assert_eq ! ( all. len( ) , set. len( ) ) ;
2285+ }
2286+ }
0 commit comments