z_mirror/RecipeBook
git clone https://git.raiguard.me/z_mirror/RecipeBook
a949b6c
trunk
1local flib_dictionary =require ( "__flib__.dictionary" ) 2local flib_format =require ( "__flib__.format" ) 3local flib_gui =require ( "__flib__.gui" ) 4local researched =require ( "scripts.database.researched" ) 5local search_tree =require ( "scripts.database.search-tree" ) 6local util =require ( "scripts.util" ) 7 8--- @param context MainGuiContext 9--- @return SearchTree 10local function get_search_tree ( context ) 11if context . search_materials_only then 12return search_tree . materials 13elseif context . grouping_mode == "all" then 14return search_tree . all 15elseif context . grouping_mode == "exclude-recipes" then 16return search_tree . separate_recipes 17else -- "none" 18return search_tree . plain 19end 20end 21 22--- @class SearchPane.Results 23--- @field first_group_with_hits string 24--- @field hits integer 25--- @field groups table<string, SearchPane.GroupResults> 26 27--- @class SearchPane.GroupResults 28--- @field filter_hits integer 29--- @field query_hits integer 30--- @field results table<SpritePath, SearchPane.Result> 31 32--- @class SearchPane.Result 33--- @field group string 34--- @field subgroup string 35--- @field is_hidden boolean 36--- @field is_researched boolean 37--- @field prototype GenericPrototype 38--- @field path SpritePath 39 40--- @class SearchPane 41--- @field textfield LuaGuiElement 42--- @field count_label LuaGuiElement 43--- @field group_buttons table<string, LuaGuiElement> 44--- @field results_pane LuaGuiElement 45--- @field dictionary_warning LuaGuiElement 46--- @field context MainGuiContext 47--- @field results SearchPane.Results? 48--- @field query string 49--- @field selected_group string 50--- @field selected_result SpritePath? 51local search_pane ={} 52local mt ={ __index =search_pane } 53script . register_metatable ( "search_pane" , mt ) 54 55--- @type function? 56search_pane . on_result_clicked =nil 57 58--- @param parent LuaGuiElement 59--- @param context MainGuiContext 60function search_pane . build ( parent , context ) 61-- TODO: Respect "lists everywhere" setting and make a list instead of a grid. 62local outer =parent . add ( { 63type ="frame" , 64name ="filter_outer_frame" , 65style ="inside_deep_frame" , 66direction ="vertical" , 67index =1 , 68} ) 69 70local subheader =outer . add ( { type ="frame" , style ="subheader_frame" } ) 71subheader . style . horizontally_stretchable =true 72 73local textfield =subheader . add ( { 74type ="textfield" , 75name ="search_textfield" , 76lose_focus_on_confirm =true , 77clear_and_focus_on_right_click =true , 78tooltip ={ "gui.flib-search-instruction" } , 79tags =flib_gui . format_handlers ( { [ defines . events . on_gui_text_changed ] =search_pane . on_query_changed } ), 80} ) 81textfield . add ( { 82type ="label" , 83name ="placeholder" , 84caption ={ "gui.rb-search-placeholder" } , 85ignored_by_interaction =true , 86} ). style . font_color = 87{ 0 , 0 , 0 , 0.6 } 88subheader . add ( { type ="empty-widget" , style ="flib_horizontal_pusher" } ) 89local count_label =subheader . add ( { type ="label" } ) 90count_label . style . right_padding =8 91 92local dictionary_warning =outer . add ( { 93type ="frame" , 94name ="filter_warning_frame" , 95style ="negative_subheader_frame" , 96visible =false , 97} ) 98dictionary_warning . style . bottom_margin =0 99dictionary_warning . style . horizontally_stretchable =true 100dictionary_warning . add ( { 101type ="label" , 102style ="bold_label" , 103caption ={ "gui.rb-localised-search-unavailable" } , 104} ). style . left_margin = 1058 106 107local groups_table =outer . add ( { type ="table" , style ="rb_filter_group_button_table" , column_count =6 } ) 108 109local results_pane =outer 110. add ( { type ="frame" , style ="rb_filter_frame" } ) 111. add ( { type ="frame" , style ="rb_filter_deep_frame" } ) 112. add ( { type ="scroll-pane" , style ="rb_filter_scroll_pane" , vertical_scroll_policy ="always" } ) 113 114local tree =get_search_tree ( context ) 115 116--- @type table<string, LuaGuiElement> 117local group_buttons ={} 118for group_name in pairs ( tree . groups ) do 119group_buttons [ group_name ] =groups_table . add ( { 120type ="sprite-button" , 121name =group_name , 122style ="rb_filter_group_button_tab" , 123sprite ="item-group/" .. group_name , 124elem_tooltip ={ type ="item-group" , name =group_name } , 125tags =flib_gui . format_handlers ( { [ defines . events . on_gui_click ] =search_pane . on_group_clicked } ), 126} ) 127end 128 129--- @type SearchPane 130local self ={ 131textfield =textfield , 132count_label =count_label , 133group_buttons =group_buttons , 134results_pane =results_pane , 135dictionary_warning =dictionary_warning , 136context =context , 137query ="" , 138selected_group =next ( group_buttons ) --[[@as string]] , 139} 140setmetatable ( self , mt ) 141self : update () 142return self 143end 144 145function search_pane : update_results () 146local profiler =game . create_profiler () 147 148local to_lower_func =helpers . compare_versions ( helpers . game_version , "2.0.67" ) >= 0 and helpers . multilingual_to_lower 149or string . lower 150 151local show_hidden =self . context . show_hidden 152local show_unresearched =self . context . show_unresearched 153local query =to_lower_func ( self . query ) 154local force_index =self . context . player . force . index 155local search_strings =flib_dictionary . get ( self . context . player . index , "search" ) or {} 156 157self . dictionary_warning . visible =next ( search_strings ) == nil 158 159self . textfield . placeholder . visible =# query == 0 160 161local tree =get_search_tree ( self . context ) 162 163--- @type SearchPane.Results 164local results ={ 165first_group_with_hits ="" , 166hits =0 , 167groups ={} , 168} 169 170for group_name , group in pairs ( tree . groups ) do 171--- @type SearchPane.GroupResults 172local group_results ={ 173filter_hits =0 , 174query_hits =0 , 175results ={} , 176} 177results . groups [ group_name ] =group_results 178 179for subgroup_name , subgroup in pairs ( group ) do 180for _ , prototype in pairs ( subgroup ) do 181local is_hidden =prototype . hidden or prototype . hidden_in_factoriopedia 182local is_researched =researched . is ( prototype , force_index ) 183local filters_match =( show_hidden or not is_hidden ) and ( show_unresearched or is_researched ) 184if not filters_match then 185goto continue 186end 187 188group_results . filter_hits =group_results . filter_hits + 1 189 190local path =util . get_path ( prototype ) 191 192local query_match =# query == 0 193if not query_match then 194local comp =search_strings [ path ] or string . gsub ( path , "-" , " " ) 195query_match =string . find ( to_lower_func ( comp ), query , 1 , true ) ~= nil 196end 197if not query_match then 198goto continue 199end 200 201group_results . query_hits =group_results . query_hits + 1 202 203group_results . results [ path ] ={ 204button =nil , 205group =group_name , 206subgroup =subgroup_name , 207is_hidden =is_hidden , 208is_researched =is_researched , 209prototype =prototype , 210path =path , 211} 212 213:: continue :: 214end 215end 216 217results . hits =results . hits + group_results . query_hits 218if group_results . query_hits > 0 and results . first_group_with_hits == "" then 219results . first_group_with_hits =group_name 220end 221end 222 223self . results =results 224 225profiler . stop () 226log ( { "" , "Update Results " , profiler } ) 227end 228 229function search_pane : update_gui () 230local profiler =helpers . create_profiler () 231 232local results =self . results 233if not results then 234return 235end 236 237local selected_group =self . selected_group 238 239if selected_group == "" or results . groups [ selected_group ]. query_hits == 0 then 240selected_group =results . first_group_with_hits 241end 242 243self . count_label . caption ={ "gui.rb-count-results" , flib_format . number ( results . hits ) } 244 245local group_buttons =self . group_buttons 246for group_name , group_results in pairs ( results . groups ) do 247local button =group_buttons [ group_name ] 248button . visible =group_results . filter_hits > 0 249button . enabled =group_results . query_hits > 0 250button . toggled =selected_group == group_name 251button . tooltip ={ "gui.rb-count-results" , flib_format . number ( group_results . query_hits ) } 252end 253 254local results_pane =self . results_pane 255results_pane . clear () 256 257if results . hits == 0 then 258local no_results_frame =results_pane . add ( { 259type ="frame" , 260name ="filter_no_results_label" , 261style ="negative_subheader_frame" , 262} ) 263no_results_frame . style . horizontally_stretchable =true 264no_results_frame . add ( { 265type ="label" , 266style ="rb_subheader_bold_label" , 267caption ={ "" , "[img=warning-white] " , { "gui.nothing-found" } } , 268} ) 269end 270 271local group_results =results . groups [ selected_group ] 272if not group_results then 273return 274end 275 276local selected_result =self . selected_result 277 278--- @type string? 279local current_subgroup 280--- @type LuaGuiElement? 281local current_table 282for result_path , result in pairs ( group_results . results ) do 283if result . subgroup ~= current_subgroup then 284current_table =results_pane . add ( { type ="table" , style ="slot_table" , column_count =10 } ) 285current_subgroup =result . subgroup 286end 287--- @cast current_table -? 288 289local style ="flib_slot_button_default" 290if result . is_hidden then 291style ="flib_slot_button_grey" 292elseif not result . is_researched then 293style ="flib_slot_button_red" 294end 295local result_id =util . get_id ( result . prototype ) 296local button =current_table . add ( { 297type ="sprite-button" , 298style =style , 299sprite =result_path , 300elem_tooltip =result_id --[[@as ElemID]] , 301tooltip ={ "" , { "gui.rb-control-hint" } , "\n" , { "gui.rb-pipette-control-hint" } } , 302toggled =result_path == selected_result , 303tags =flib_gui . format_handlers ( { 304[ defines . events . on_gui_click ] =search_pane . on_result_clicked , 305} , { rb_id =result_id } ), 306} ) 307if result_path == selected_result then 308results_pane . scroll_to_element ( button ) 309end 310end 311 312profiler . stop () 313log ( { "" , "Update GUI elements " , profiler } ) 314end 315 316function search_pane : update () 317self : update_results () 318self : update_gui () 319end 320 321--- @param prototype GenericPrototype? 322function search_pane : select_result ( prototype ) 323if not prototype then 324self . selected_result =nil 325return 326end 327self . selected_result =util . get_path ( prototype ) 328local group_name =prototype . group . name 329local tree =get_search_tree ( self . context ) 330if tree . groups [ group_name ] then 331self . selected_group =prototype . group . name 332end 333self : update_gui () 334end 335 336function search_pane : focus_search () 337self . textfield . select_all () 338self . textfield . focus () 339end 340 341--- @private 342--- @param e EventData.on_gui_text_changed 343function search_pane : on_query_changed ( e ) 344self . query =e . text 345self : update () 346end 347 348--- @private 349--- @param e EventData.on_gui_text_changed 350function search_pane : on_group_clicked ( e ) 351self . selected_group =e . element . name 352self : update_gui () 353end 354 355flib_gui . add_handlers ( search_pane , function ( e , handler ) 356local main =storage . guis [ e . player_index ] 357if not main or not main . window . valid then 358return 359end 360handler ( main . search_pane , e ) 361end , "search_pane" ) 362 363return search_pane