Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file partial_render_list.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391open!Core_kernelopenPolyopen!ImportopenUtilincludePartial_render_list_intfmoduleMake(Row_id:Row_id)(Sort_key:Sort_keywithtyperow_id:=Row_id.t)=structmoduleRow_id=Row_idmoduleSort_key=Sort_keymoduleHeight_cache=structtypet={cache:floatRow_id.Map.t;height_guess:float}[@@derivingfields,compare,sexp_of]letempty~height_guess={cache=Row_id.Map.empty;height_guess}letheighttrow_id=Option.value(Map.findt.cacherow_id)~default:t.height_guessendmoduleHeights=structincludeSplay_tree.Make_with_reduction(Sort_key)(Float)(structtypekey=Sort_key.ttypedata=float(* height *)typeaccum=floatletidentity=0.letsingleton~key:_~data=dataletcombineleftright=left+.rightend)(** Returns the row (if any) that is at the specified position *)letfind_by_position(heights:t)position=searchheights~f:(fun~left~right:_->ifposition<leftthen`Leftelse`Right)|>Option.map~f:fst;;(** The cumulative height of all the rows *)letheight=accumletget_position_and_heighttkey=letbefore,at,(_:t)=splittkeyinheightbefore,at;;endtype'vt={heights:Heights.t(** Acceleration structure for height queries *);render_range:Sort_key.tInterval.t(** Section of keys to put in DOM
This includes extra rows above and below what is actually visible *);rows_to_render:'vSort_key.Map.t(** Full map of [render_range] *);measurements:Measurements.toption;height_cache:Height_cache.t(** The height cache is stashed here after trimming so that it can be
accessed later in measure_heights. This way the app doesn't have to
store it in its derived model and pass it back to us. The app still
stores the height cache in its model, it just doesn't also have to store
a trimmed version in its derived model.*);min_key:Sort_key.toption;max_key:Sort_key.toption}[@@derivingfields](** How many extra rows will be rendered outside of visible range. Must be even to
preserve parity for alternating row colours. *)letrender_width=6letfind_by_positiont~position=Heights.find_by_positiont.heightspositionletfocus_offset_to_positiontkey~offset=letkey_position,key_height=Heights.get_position_and_heightt.heightskeyinletkey_height=Option.valuekey_height~default:0.inmatchOrdering.of_int(Float.compareoffset0.)with|Equal->key_position|Less->key_position+.offset+.key_height|Greater->key_position+.offset;;letfind_by_relative_positiontkey~offset=letfind~default=letposition=focus_offset_to_positiontkey~offsetinmatchfind_by_positiont~positionwith|Somekey->Somekey|None->defaultinmatchOrdering.of_int(Float.compareoffset0.)with|Equal->Somekey|Less->find~default:t.min_key|Greater->find~default:t.max_key;;letget_visible_range~(measurements:Measurements.toptionIncr.t)~(heights:Heights.tIncr.t)~(rows:_Sort_key.Map.tIncr.t)=let%mapmeasurements=measurementsandheights=heightsandrows=rowsinmatchmeasurementswith|None->Interval.Empty|Some{list_rect;view_rect}->letmoduleRect=Js_misc.Rectin(* The top of the view_rect, as measured from the top of the table body. Note that
the top of tbody_rect is measured against the viewport, and so is a negative
number when the top row is above the top of the viewport. *)letscroll_top=Rect.topview_rect-.Rect.toplist_rectin(* The height of the table, which excludes the height of the header *)letscroll_bot=scroll_top+.Rect.float_heightview_rectinletvisible_range:_Interval.t=ifscroll_top>=Heights.heightheights||scroll_bot<=0.thenEmptyelse(letkey_top=matchHeights.find_by_positionheightsscroll_topwith|Somex->Somex|None->Map.min_eltrows|>Option.map~f:fstinletkey_bot=matchHeights.find_by_positionheightsscroll_botwith|Somex->Somex|None->Map.max_eltrows|>Option.map~f:fstinmatchkey_top,key_botwith|None,_|_,None->Empty|Sometop,Somebot->Range(top,bot))invisible_range;;letcreate~rows~height_cache~measurements=letjust_keys=Incr.Map.mapi~data_equal:(fun__->true)rows~f:(fun~key:_~data:_->())inletrow_ids=Incr.Map.unordered_foldjust_keys~init:Row_id.Map.empty~add:(fun~key~data:()map->letrow_id=Sort_key.row_idkeyinletcount=Option.value(Map.findmaprow_id)~default:0inMap.setmap~key:row_id~data:(count+1))~remove:(fun~key~data:()map->letrow_id=Sort_key.row_idkeyinmatchMap.findmaprow_idwith|None->assertfalse|Some1->Map.removemaprow_id|Somecount->assert(count>1);Map.setmap~key:row_id~data:(count-1))in(* Removes elements from the cache that are no longer in the set of all data so it
doesn't grow monotonically even while rows are removed. *)lettrimmed_height_cache=Incr.Map.mergerow_ids(height_cache>>|Height_cache.cache)(* Efficiency optimization, we don't care if the rows change, only the heights *)~data_equal_left:(fun__->true)~f:(fun~key:_v->matchvwith|`Left_|`Right_->None|`Both(_,h)->Someh)inletheight_lookup=Incr.Map.Lookup.createtrimmed_height_cache~comparator:Row_id.comparator~data_equal:Float.equalinletheight_guess=height_cache>>|Height_cache.height_guessinletrow_heights=let%bindheight_guess=height_guessinIncr.Map.mapi'just_keys~data_equal:Unit.equal~f:(fun~key~data:_->match%mapIncr.Map.Lookup.findheight_lookup(Sort_key.row_idkey)with|Someheight->height|None->height_guess)inletheights=Incr.Map.unordered_foldrow_heights~init:Heights.empty~add:(fun~key~dataacc->Heights.setacc~key~data)~remove:(fun~key~data:_acc->Heights.removeacckey)inletvisible_range=get_visible_range~measurements~heights~rowsinletrender_range=let%mapvisible_range=visible_rangeandjust_keys=just_keysandheights=heightsin(* Hack to make CSS-based alternating row colours with :nth-of-type(odd) continue to
work. Ensures that the parity of the number of tr elements before a given element
is preserved even with partial rendering by sometimes rendering an extra element.
This is way easier for clients than rolling their own alternating colours. CSS
frameworks like Bootstrap which provide alternating colours also continue to
work. *)letparity_fixkey=letnum_before=Heights.rankheightskeyinnum_before%2inletrecmovestartnget_next=ifn<=0thenstartelse(matchget_nextstartwith|None->start|Somenext->movenext(n-1)get_next)inletmovestartdirectionamount=movestartamount(funx->Map.closest_keyjust_keysdirectionx|>Option.map~f:fst)inmatch(visible_range:_Interval.t)with|Empty->Interval.Empty|Range(top,bot)->Interval.Range(movetop`Less_than(render_width+parity_fixtop),movebot`Greater_thanrender_width)inletrows_to_render=letsub_range=match%maprender_rangewith|Empty->None|Range(x,y)->Some(Inclx,Incly)inIncr.Map.subrangerowssub_rangeinletmin_and_max_key=let%mapjust_keys=just_keysin(Option.map(Map.min_eltjust_keys)~f:fst,Option.map(Map.max_eltjust_keys)~f:fst)inletheight_cache=let%mapheight_guess=height_guessandtrimmed_height_cache=trimmed_height_cachein{Height_cache.height_guess;cache=trimmed_height_cache}inlet%mapheights=heightsandrows_to_render=rows_to_renderandrender_range=render_rangeandheight_cache=height_cacheandmeasurements=measurementsandmin_key,max_key=min_and_max_keyin{heights;render_range;rows_to_render;measurements;height_cache;min_key;max_key};;letspacer_heightst=let%maprender_range=t>>|render_rangeandheights=t>>|heightsinmatch(render_range:_Interval.t)with|Empty->0.,Heights.heightheights|Range(min_key,max_key)->let{Heights.Partition.lt;gt;_}=Heights.partitionheights~min_key~max_keyinHeights.heightlt,Heights.heightgt;;letcall_scroll_functiont~key~f=Option.bindt.measurements~f:(fun{Measurements.list_rect;view_rect}->letposition,height=Heights.get_position_and_heightt.heightskeyinOption.mapheight~f:(funheight->letelem_start=position+.list_rect.topinf~scroll_region_start:view_rect.top~scroll_region_end:view_rect.bottom~elem_start~elem_end:(elem_start+.height)));;letscroll_into_scroll_region?in_t~top_margin~bottom_margin~key=letf=Scroll.scroll_into_region?in_Vertical~start_margin:top_margin~end_margin:bottom_margininOption.value(call_scroll_functiont~key~f)~default:`Didn't_scroll;;letscroll_to_position?in_t~position~key=letf~scroll_region_start~scroll_region_end:_~elem_start~elem_end:_=Scroll.scroll_to_position?in_Vertical~position~scroll_region_start~elem_startinOption.value(call_scroll_functiont~key~f)~default:`Didn't_scroll;;letscroll_to_position_and_into_region?in_t~position~top_margin~bottom_margin~key=letf=Scroll.scroll_to_position_and_into_region?in_Vertical~position~start_margin:top_margin~end_margin:bottom_margininOption.value(call_scroll_functiont~key~f)~default:`Didn't_scroll;;letis_in_regiont~top_margin~bottom_margin~key=letf=Scroll.is_in_region~start_margin:top_margin~end_margin:bottom_marginincall_scroll_functiont~key~f;;letget_positiont~key=letf~scroll_region_start~scroll_region_end:_~elem_start~elem_end:_=Scroll.get_position~scroll_region_start~elem_startincall_scroll_functiont~key~f;;letget_top_and_bottomt~key=letf~scroll_region_start~scroll_region_end:_~elem_start~elem_end=lettop=Scroll.get_position~scroll_region_start~elem_startintop,top+.elem_end-.elem_startincall_scroll_functiont~key~f;;letupdate_cachecache~keyheight=matchheightwith|None->cache|Someheight->(* Optimization: Don't bother adding measured height to [height_cache] if it
is approximately equal to the existing height for that key. *)letfloat_approx_equalf1f2=Float.(abs(f1-f2)<0.001)inifOption.equalfloat_approx_equal(Map.findcachekey)(Someheight)thencacheelseMap.setcache~key~data:height;;letmeasure_heights_simplet~measure=letcache=Map.foldt.rows_to_render~init:t.height_cache.cache~f:(fun~key~data:_cache->update_cachecache~key:(Sort_key.row_idkey)(measurekey))in{t.height_cachewithcache};;type'mmeasure_heights_acc={cache:floatRow_id.Map.t;prev:'moption;current:(Row_id.t*'moption)option}letmeasure_heightst~measure_row~get_row_height=letupdate_cachecache~current~prev~next=matchcurrentwith|None->cache|Some(key,curr)->update_cachecache~key(get_row_height~prev~curr~next)inletcache=let{cache;prev;current}=Map.foldt.rows_to_render~init:{cache=t.height_cache.cache;prev=None;current=None}~f:(fun~key:next_key~data:_{cache;prev;current}->letnext=measure_rownext_keyin{cache=update_cachecache~current~prev~next;prev=Option.bindcurrent~f:Tuple2.get2;current=Some(Sort_key.row_idnext_key,next)})inupdate_cachecache~current~prev~next:Nonein{t.height_cachewithcache};;endmoduleMake_simple(Row_id:Row_id)=Make(Row_id)(structincludeRow_idletrow_id=Fn.idend)