11use iced:: event:: Status ;
22use iced:: keyboard;
3- use std:: borrow:: Cow ;
43
5- use super :: { FAVORITES_TAB , HonkHonk , Message , ViewMode , sorting} ;
6- use crate :: state:: SoundEntry ;
7- use crate :: ui:: list_controls:: filter:: { Activation , ActivationContext , filter_items} ;
4+ use super :: { HonkHonk , Message , ViewMode } ;
5+ use crate :: ui:: list_controls:: filter:: { Activation , ActivationContext } ;
86use crate :: ui:: search_bar;
97
8+ mod cache;
9+
1010pub ( super ) fn type_to_filter_text ( event : & iced:: Event , status : Status ) -> Option < String > {
1111 if status != Status :: Ignored {
1212 return None ;
@@ -30,6 +30,22 @@ pub(super) fn type_to_filter_text(event: &iced::Event, status: Status) -> Option
3030}
3131
3232impl HonkHonk {
33+ pub ( super ) fn select_sound_category ( & mut self , category : Option < String > ) {
34+ if self . active_category == category {
35+ return ;
36+ }
37+ self . active_category = category;
38+ self . refresh_filtered_sounds ( ) ;
39+ }
40+
41+ pub ( super ) fn replace_filter_query ( & mut self , query : String ) {
42+ let changed = self . filter . query ( ) != query;
43+ self . filter . replace ( query) ;
44+ if changed {
45+ self . refresh_filtered_sounds ( ) ;
46+ }
47+ }
48+
3349 fn filter_context ( & self ) -> ActivationContext {
3450 let activation = match self . view_mode {
3551 ViewMode :: Main => Activation :: TypeToFilter ,
@@ -52,6 +68,9 @@ impl HonkHonk {
5268 }
5369
5470 self . filter . insert ( text) ;
71+ if !text. is_empty ( ) {
72+ self . refresh_filtered_sounds ( ) ;
73+ }
5574 iced:: widget:: operation:: focus ( search_bar:: input_id ( ) )
5675 }
5776
@@ -75,40 +94,14 @@ impl HonkHonk {
7594 } else if event_was_captured {
7695 self . filter . consume_focus ( ) ;
7796 } else {
97+ let query_was_present = !self . filter . query ( ) . is_empty ( ) ;
7898 self . filter . escape ( ) ;
99+ if query_was_present && self . filter . query ( ) . is_empty ( ) {
100+ self . refresh_filtered_sounds ( ) ;
101+ }
79102 }
80103 iced:: Task :: none ( )
81104 }
82-
83- /// Returns sounds matching the shared query and active category filters.
84- pub fn filtered_sounds ( & self ) -> Vec < & SoundEntry > {
85- let sounds = filter_items ( & self . sounds , self . filter . query ( ) , |sound| {
86- let display_name = self
87- . sound_meta
88- . get_ref ( & sound. id )
89- . and_then ( |meta| meta. display_name . as_deref ( ) )
90- . unwrap_or ( "" ) ;
91- let filename = sound
92- . path
93- . file_name ( )
94- . map ( std:: ffi:: OsStr :: to_string_lossy)
95- . unwrap_or_default ( ) ;
96- [
97- Cow :: Borrowed ( display_name) ,
98- filename,
99- Cow :: Borrowed ( sound. name . as_str ( ) ) ,
100- Cow :: Borrowed ( sound. category . as_str ( ) ) ,
101- ]
102- } )
103- . into_iter ( )
104- . filter ( |sound| match self . active_category . as_deref ( ) {
105- Some ( FAVORITES_TAB ) => self . sound_meta . is_favorite ( & sound. id ) ,
106- Some ( category) => sound. category == category,
107- None => true ,
108- } )
109- . collect ( ) ;
110- sorting:: sorted_sounds ( sounds, self . sound_sort , & self . sound_meta )
111- }
112105}
113106
114107#[ cfg( test) ]
@@ -118,7 +111,27 @@ mod tests {
118111 use iced:: keyboard:: { self , Key , Location , Modifiers } ;
119112
120113 use super :: * ;
121- use crate :: state:: { AudioFormat , Macro } ;
114+ use crate :: app:: FAVORITES_TAB ;
115+ use crate :: state:: { AudioFormat , Macro , SoundEntry } ;
116+
117+ fn sound ( id : & str , name : & str , duration_ms : Option < u64 > , category : & str ) -> SoundEntry {
118+ SoundEntry {
119+ id : id. into ( ) ,
120+ name : name. into ( ) ,
121+ path : format ! ( "/sounds/{category}/{id}.wav" ) . into ( ) ,
122+ format : AudioFormat :: Wav ,
123+ duration_ms,
124+ category : category. into ( ) ,
125+ modified_ms : None ,
126+ }
127+ }
128+
129+ fn filtered_ids ( app : & HonkHonk ) -> Vec < & str > {
130+ app. filtered_sounds ( )
131+ . into_iter ( )
132+ . map ( |sound| sound. id . as_str ( ) )
133+ . collect ( )
134+ }
122135
123136 fn key_event ( text : Option < & str > , modifiers : Modifiers ) -> iced:: Event {
124137 iced:: Event :: Keyboard ( keyboard:: Event :: KeyPressed {
@@ -268,23 +281,92 @@ mod tests {
268281 #[ test]
269282 fn main_grid_filter_results_follow_the_active_sort_state ( ) {
270283 let mut app = HonkHonk :: new_for_test ( ) ;
271- app. sounds = [ "Zulu" , "alpha" ]
272- . into_iter ( )
273- . map ( |name| SoundEntry {
274- id : name. into ( ) ,
275- name : name. into ( ) ,
276- path : format ! ( "/sounds/{name}.wav" ) . into ( ) ,
277- format : AudioFormat :: Wav ,
278- duration_ms : None ,
279- category : "Other" . into ( ) ,
280- modified_ms : None ,
281- } )
282- . collect ( ) ;
284+ app. sounds = vec ! [
285+ sound( "zulu" , "Zulu" , None , "Other" ) ,
286+ sound( "alpha" , "alpha" , None , "Other" ) ,
287+ ] ;
288+ app. refresh_filtered_sounds ( ) ;
283289
284290 assert_eq ! ( app. filtered_sounds( ) [ 0 ] . name, "alpha" ) ;
285291
286292 let _ = app. update ( Message :: ToggleSoundSortDirection ) ;
287293
288294 assert_eq ! ( app. filtered_sounds( ) [ 0 ] . name, "Zulu" ) ;
289295 }
296+
297+ #[ test]
298+ fn filtered_sounds_reads_cached_order_without_resorting ( ) {
299+ let mut app = HonkHonk :: new_for_test ( ) ;
300+ app. sounds = vec ! [
301+ sound( "zulu" , "Zulu" , None , "Other" ) ,
302+ sound( "alpha" , "alpha" , None , "Other" ) ,
303+ ] ;
304+ app. refresh_filtered_sounds ( ) ;
305+
306+ assert_eq ! ( filtered_ids( & app) , vec![ "alpha" , "zulu" ] ) ;
307+ app. sound_sort . toggle_direction ( ) ;
308+
309+ assert_eq ! (
310+ filtered_ids( & app) ,
311+ vec![ "alpha" , "zulu" ] ,
312+ "reading filtered sounds must not recompute their order"
313+ ) ;
314+ }
315+
316+ #[ test]
317+ fn query_category_and_favorite_updates_refresh_cached_membership ( ) {
318+ let mut app = HonkHonk :: new_for_test ( ) ;
319+ app. sounds = vec ! [
320+ sound( "alpha" , "Alpha" , None , "Animals" ) ,
321+ sound( "beta" , "Beta" , None , "Memes" ) ,
322+ ] ;
323+ app. refresh_filtered_sounds ( ) ;
324+
325+ let _ = app. update ( Message :: SearchChanged ( "beta" . into ( ) ) ) ;
326+ assert_eq ! ( filtered_ids( & app) , vec![ "beta" ] ) ;
327+
328+ let _ = app. update ( Message :: SearchChanged ( String :: new ( ) ) ) ;
329+ let _ = app. update ( Message :: SelectCategory ( Some ( "Animals" . into ( ) ) ) ) ;
330+ assert_eq ! ( filtered_ids( & app) , vec![ "alpha" ] ) ;
331+
332+ let _ = app. update ( Message :: SelectCategory ( None ) ) ;
333+ let _ = app. update ( Message :: TypeToFilter ( "beta" . into ( ) ) ) ;
334+ assert_eq ! ( filtered_ids( & app) , vec![ "beta" ] ) ;
335+ let _ = app. update ( Message :: EscapePressed ) ;
336+ let _ = app. update ( Message :: EscapePressed ) ;
337+ assert_eq ! ( filtered_ids( & app) , vec![ "alpha" , "beta" ] ) ;
338+
339+ let _ = app. update ( Message :: ToggleFavorite ( "beta" . into ( ) ) ) ;
340+ let _ = app. update ( Message :: SelectCategory ( Some ( FAVORITES_TAB . into ( ) ) ) ) ;
341+ assert_eq ! ( filtered_ids( & app) , vec![ "beta" ] ) ;
342+
343+ let _ = app. update ( Message :: ToggleFavorite ( "beta" . into ( ) ) ) ;
344+ assert_eq ! ( filtered_ids( & app) , vec![ "alpha" , "beta" ] ) ;
345+ }
346+
347+ #[ test]
348+ fn duration_and_display_name_updates_refresh_cached_order ( ) {
349+ let mut app = HonkHonk :: new_for_test ( ) ;
350+ app. sounds = vec ! [
351+ sound( "alpha" , "Alpha" , Some ( 200 ) , "Other" ) ,
352+ sound( "zulu" , "Zulu" , None , "Other" ) ,
353+ ] ;
354+ app. refresh_filtered_sounds ( ) ;
355+
356+ let _ = app. update ( Message :: SelectSoundSort ( "length" ) ) ;
357+ assert_eq ! ( filtered_ids( & app) , vec![ "alpha" , "zulu" ] ) ;
358+
359+ let durations = std:: collections:: HashMap :: from ( [ ( "zulu" . to_owned ( ) , 100 ) ] ) ;
360+ let _ = app. update ( Message :: DurationsLoaded ( durations) ) ;
361+ assert_eq ! ( filtered_ids( & app) , vec![ "zulu" , "alpha" ] ) ;
362+
363+ let _ = app. update ( Message :: SelectSoundSort ( "name" ) ) ;
364+ let _ = app. update ( Message :: OpenSoundEditor ( "zulu" . into ( ) ) ) ;
365+ let _ = app. update ( Message :: SoundEditorNameChanged ( "Aardvark" . into ( ) ) ) ;
366+ let _ = app. update ( Message :: SaveSoundMeta ( "zulu" . into ( ) ) ) ;
367+ assert_eq ! ( filtered_ids( & app) , vec![ "zulu" , "alpha" ] ) ;
368+
369+ let _ = app. update ( Message :: SearchChanged ( "aardvark" . into ( ) ) ) ;
370+ assert_eq ! ( filtered_ids( & app) , vec![ "zulu" ] ) ;
371+ }
290372}
0 commit comments