Source file b_long_list.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
open Tsdl
open B_utils
module Layout = B_layout
module Widget = B_widget
module Avar = B_avar
module Theme = B_theme
module Time = B_time
module Var = B_var
module Tvar = B_tvar
module Trigger = B_trigger
module Draw = B_draw
module Slider = B_slider
module Sync = B_sync
module Update = B_update
type entry =
| Void
| Freed
| Computed of Layout.t
type direction =
| Up
| Down
let factor = 5
let min_tick_size = 10
let scroll_margin = 70
type internal = {
length : int;
mutable total_height : int option;
mutable computed_height : int;
offset : (int Avar.t) Var.t;
mutable computed : int;
mutable min_rendering_height : int;
generate : (int -> Layout.t);
cleanup : (Layout.t -> unit);
max_memory : int option;
mutable used_memory : int;
array : entry array;
linear : bool;
mutable first : int;
mutable last : int;
mutable first_mem : int;
mutable last_mem : int;
mutable container_voffset : int;
heights : (int option) array;
mutable width_warning : bool;
scale_width : bool
}
type t = {
layout : Layout.t;
slider : Widget.t;
regenerate : unit -> unit;
ll : internal
}
let to_str = function
| Up -> "Up"
| Down -> "Down"
let update_last_mem ll i =
printd debug_memory "New memory range for Long_list = [%u,_%u_]"
ll.first_mem ll.last_mem;
let rec loop j =
if j < 0 then (ll.last_mem <- 0; failwith "BOOOh")
else match ll.array.(j) with
| Computed _ -> ll.last_mem <- j
| Void | Freed -> loop (j-1)
in loop i
let update_first_mem ll i =
printd debug_memory "New memory range for Long_list = [_%u_,%u]"
ll.first_mem ll.last_mem;
let rec loop j =
if j >= ll.length then (ll.last_mem <- ll.length - 1; failwith "BAAAAh")
else match ll.array.(j) with
| Computed _ -> ll.first_mem <- j
| Void | Freed -> loop (j+1)
in loop i
let reduce_memory ll direction =
printd debug_memory "Long list: Reduce_memory...";
let mm = match ll.max_memory with
| Some mm -> mm
| None ->
failwith "[reduce_memory] is only called when [ll.max_memory] is not None" in
let rec loop j next =
if j < 0 || j >= ll.length
then printd (debug_error + debug_memory)
"Memory usage for LongList exceeds maximum value. Beware."
else
let j' = next j in
match ll.array.(j) with
| Void
| Freed -> loop j' next
| Computed l ->
if j >= ll.first && j <= ll.last
then printd debug_error
"OOPS! cannot remove Long_list entry #%u because it belongs to the \
current room..." j
else begin
let mem = let (w,h) = Layout.get_physical_size l in w*h in
printd debug_memory "Cleaning up entry #%d of LongList" j;
ll.cleanup l;
Layout.send_to_cemetery l;
ll.array.(j) <- Freed;
if j >= ll.last_mem then update_last_mem ll j
else if j <= ll.first_mem then update_first_mem ll j;
ll.used_memory <- ll.used_memory - mem;
if ll.used_memory > mm then loop j' next
end
in
match direction with
| Down ->
printd debug_memory "...from top";
loop ll.first_mem (fun i -> i+1)
| Up ->
printd debug_memory "...from bottom";
loop ll.last_mem (fun i -> i-1)
let total_height ll =
match ll.total_height with
| Some h -> h
| None -> round (float (ll.computed_height * ll.length) /. (float ll.computed))
let compute_min_rendering_height ll (w,h) =
assert (w*h <> 0);
let mh = match ll.max_memory with
| Some mm when Theme.scale_was_init () ->
let container_area = Theme.((scale_int w) * (scale_int h)) + 1 in
let x = float mm /. float container_area in
let min_factor = 3. *. (1. +. float scroll_margin /. float h) in
if x < min_factor then printd (debug_error + debug_memory + debug_user)
"[max_memory=%i] for Long_list is too small; we need at least %i"
mm (round (min_factor *. float container_area));
let x = max x min_factor in
round (2. *. x *. (float h) /. 3.)
| _ -> 2 * factor * h / 3 in
let mh = imax (2 * scroll_margin + 2 * h + 2) mh in
printd debug_memory "Long_list [min_rendering_height] = %i (h=%i, w=%i)" mh h w;
mh
let get ll i direction =
match ll.array.(i) with
| Computed l -> if not (Layout.is_detached l) then Layout.detach l;
l
| Void
| Freed -> begin
let entry = ll.generate i in
let (w,h) = Layout.get_physical_size entry in
ll.used_memory <- ll.used_memory + w*h;
if i > ll.last_mem then ll.last_mem <- i
else if i < ll.first_mem then ll.first_mem <- i;
printd debug_memory "Long list: used memory: %d" ll.used_memory;
if ll.array.(i) = Void then
begin
let h = Layout.height entry in
match ll.heights.(i) with
| None ->
ll.computed_height <- ll.computed_height + h;
ll.computed <- ll.computed + 1;
if ll.computed = ll.length
then ll.total_height <- Some ll.computed_height;
ll.heights.(i) <- Some h
| Some hh ->
if hh <> h
then begin
printd debug_error "Computed height (%u) for long_list element #%u \
differs from given height (%u)" h i hh;
ll.heights.(i) <- Some h;
ll.computed_height <- ll.computed_height + h - hh;
do_option ll.total_height (fun _ ->
ll.total_height <- Some ll.computed_height)
end
end;
ll.array.(i) <- Computed entry;
do_option ll.max_memory (fun mm ->
if ll.used_memory > mm then reduce_memory ll direction);
entry
end
let compute_room ~height ~width ll i_start direction =
printd debug_custom "[Long_list.compute_room] start = %i" i_start;
if ll.length > 0 then begin
assert (i_start >= 0);
ll.first <- i_start;
let rec loop i ~h list =
if h >= height || i >= ll.length then List.rev list, (i-1)
else begin
ll.last <- i;
let line = get ll i direction in
do_option width (Layout.set_width line);
let dh = Layout.height line in
loop (i+1) ~h:(h+dh) (line::list)
end
in
let list, i_final = loop i_start ~h:0 [] in
let room = Layout.tower ~name:(Printf.sprintf "long_list room %i" i_start)
~margins:0 list in
if !debug then assert (ll.last = i_final);
room, i_final
end else begin
printd (debug_warning + debug_user) "Long_list is empty.";
Layout.empty ~w:0 ~h:0 (), -1
end
let add_heights_NO ~first ~last ll direction =
let heights = ll.heights in
let rec loop i h =
if i > last then h
else let dh = match heights.(i) with
| None -> Layout.height (get ll i direction)
| Some dh -> dh in
loop (i+1) (h+dh) in
loop first 0
let addup_entries ll ~start ~height direction =
assert (height >= 0);
let heights = ll.heights in
let time = Time.now () in
let slow = ref false in
let cursor = ref None in
let rec loop i h =
if h >= height || i < 0 || i > ll.length - 1 then h,i
else let dh = match heights.(i) with
| None -> Layout.height (get ll i direction)
| Some dh -> dh
in
if not !slow && Time.now () - time > 100 then
(slow := true;
cursor := Sdl.get_cursor ();
Sdl.set_cursor (Some (go (Draw.create_system_cursor Sdl.System_cursor.wait))));
loop (if direction = Up then i-1 else i+1) (h+dh)
in
let h,i = loop start 0 in
if h < height then printd debug_warning
"Long_list: [addup_entries] bottom reached before desired height.";
if !slow then Sdl.set_cursor !cursor;
printd debug_memory "Long_list ADDUP dir=%s start=%d height=%d ==> h=%u, i=%d"
(to_str direction) start height h i;
h,i
let shift_voffset container dv =
if dv <> 0
then Layout.shift_voffset container dv
let check_width ll w room =
match ll.width_warning, Layout.width room > w with
| true, true -> ()
| false, true -> printd debug_user "Long_list rows are larger than the room width.";
ll.width_warning <- true
| true, false -> ll.width_warning <- false
| false, false -> ()
let update_room ?(force=false) ll container o =
let scrolling, room =
let open Layout in
match container.content with
| Rooms [scrolling] ->
(match scrolling.content with
| Rooms [_active_bg; room] -> scrolling, room
| _ -> failwith "The container should contain a single layout with a list \
of 2 rooms!")
| _ -> failwith "The container should contain a single layout with a list of \
2 rooms!"
in
let h = Layout.height container in
let ll_height = total_height ll in
let offset = Avar.get (Var.get ll.offset) in
let voffset = Layout.get_voffset container in
let offset, o =
if voffset <> ll.container_voffset
then let offset = offset + ll.container_voffset - voffset in
Avar.set (Var.get ll.offset) offset;
let o = o + ll.container_voffset - voffset in
ll.container_voffset <- voffset;
offset, o
else offset, o in
Var.protect_do ll.offset (fun () ->
let voffset2 = voffset + offset - o in
if not force
&& ((voffset2 + scroll_margin < 0)
|| (ll.first = 0)
)
&& ((h - voffset2 < Layout.height room - scroll_margin )
||
(ll.last = ll.length - 1)
)
then begin
printd debug_custom "Long_list: room still usable, o=%i" o;
shift_voffset container (voffset2 - voffset);
ll.container_voffset <- voffset2;
check_width ll (Layout.width container) room
end
else begin
let width = if ll.scale_width then Some (Layout.width scrolling) else None in
let room2 =
printd debug_memory
"Update Long_list [%d,%d] => newoffset=%d oldoffset=%d voffset=%d \
voffset2=%d (approx)height=%d, min_rendering_height=%d, \
room.height=%d, MEM=[%d,%d] " ll.first ll.last o offset voffset voffset2
ll_height ll.min_rendering_height (Layout.height room)
ll.first_mem ll.last_mem;
if voffset2+scroll_margin >= 0 && ll.first > 0
then begin
let direction = Up in
let add_h = imax
((ll.min_rendering_height - h) / 2 - scroll_margin)
(voffset2 + scroll_margin)
in
let add_h = min (offset + voffset2) add_h in
let dh, i_first = addup_entries ll ~start:(ll.first-1)
~height:add_h direction in
let room', _ = compute_room ~width ~height:ll.min_rendering_height
ll (i_first+1) direction in
let new_voffset = voffset2 - dh in
shift_voffset container (new_voffset - voffset);
ll.container_voffset <- new_voffset;
room'
end
else begin
let direction = Down in
let add_h =
h - voffset2 - Layout.height room + scroll_margin in
let add_h = imax add_h
((ll.min_rendering_height - h) / 2 - scroll_margin) in
let hdown, i_last = addup_entries ll ~start:(ll.last+1)
~height:add_h direction in
let hup, i_first = addup_entries ll ~start:(i_last-1)
~height:ll.min_rendering_height Up in
let dh = hup - Layout.height room - hdown in
let room', _ = compute_room ~width ~height:hup
ll (i_first+1) direction in
let new_voffset = voffset2 - dh in
shift_voffset container (new_voffset - voffset);
ll.container_voffset <- new_voffset;
room'
end
in
printd debug_graphics
"Room for Long_list is replaced with new range [%d,%d]"
ll.first ll.last;
Layout.(set_height ~keep_resize:true scrolling (height room2));
assert (Layout.replace_room ~by:room2 room);
Layout.update_current_geom container;
if width = None then begin
check_width ll (Layout.width container) room2;
Layout.disable_resize room2
end else Layout.resize_follow_width room2;
Layout.remove room;
Layout.send_to_cemetery room
end)
let free_all container ll =
if ll.length > 0 then begin
ll.used_memory <- 0;
let a = ll.array in
for i = 0 to ll.length - 1 do a.(i) <- Void done;
ll.first_mem <- 0;
ll.last_mem <- 0;
update_room ~force:true ll container (Avar.get (Var.get ll.offset))
end
let dummy_clip (w,h) =
let slider = Widget.slider 0 in
let dum = Layout.resident ~w ~h slider in
Layout.set_show dum false;
Layout.hide ~duration:0 dum;
dum, dum, slider
let make_clip ?name ~w ~h ~scrollbar_width ll room =
check_width ll w room;
let module L = Layout in
let active_bg = Widget.empty ~w ~h:(L.height room) ()
|> L.resident ~name:"active_bg" in
let scrolling = L.superpose ~name:"scrolling" ~w [active_bg; room] in
if ll.scale_width then L.resize_follow_width room else L.disable_resize room;
let container =
L.(tower ~name:"long_list container" ~clip:true ~margins:0 [scrolling]) in
L.set_size ~keep_resize:true container ~w ~h;
L.resize_follow_width scrolling;
let ll_height = total_height ll in
let clicked_value = ref None in
let tick_size = max min_tick_size ((h * h) / ll_height) in
let steps = imax ll.length h in
let var = Tvar.create ll.offset
~t_from:
(fun v -> let o = Avar.get v in
update_room ll container o;
let tt_height = total_height ll in
let o_new = Avar.get v in
let h = L.height container in
steps - round (float (steps * o_new) /. (float (tt_height - h))))
~t_to:
(fun s ->
let lf = float steps in
let ss = if ll.linear
then lf -. float s
else match !clicked_value with
| None -> lf -. float s
| Some cv -> let x0 = 1. -. float cv /. lf in
Slider.slow 4 lf x0 (1. -. float s /. lf) in
let tt_height = total_height ll in
let h = L.height container in
let o = imax 0 (round (float (tt_height - h) *. ss /. lf)) in
update_room ll container o;
let o2 = imin (total_height ll - h) o in
if o <> o2 then begin
printd debug_custom
"Long_list: slider went too far; o=%i o2=%i h=%i" o o2 h;
shift_voffset container (o - o2);
ll.container_voffset <- ll.container_voffset + o - o2
end;
Avar.var o2) in
let slider = Widget.slider ~kind:Slider.Vertical ~length:h ~step:1
~thickness:scrollbar_width ~tick_size ~var steps in
if not ll.linear then begin
let on_click sl _ _ =
clicked_value := Slider.clicked_value (Widget.get_slider sl) in
let c = Widget.connect_main slider slider on_click Trigger.buttons_down in
Widget.add_connection slider c;
let on_release _ _ _ =
clicked_value := None in
let c2 = Widget.connect_main slider slider on_release
Trigger.buttons_up in
Widget.add_connection slider c2
end;
let bar = L.(
resident ~name:"bar" ~background:(color_bg Draw.scrollbar_color) slider) in
let name = default name "long_list" in
let layout = L.(superpose ~name [container; bar]) in
L.disable_resize bar;
container.resize <- (fun (w, hh) ->
let open L in let open Resize in
let th = total_height ll in
let h = imin hh th in
set_height bar hh;
set_size container ~w ~h;
setx bar (w - width bar);
ll.min_rendering_height <- compute_min_rendering_height ll (w, h);
let s = Tvar.get var in
let sli = Widget.get_slider slider in
if s < 0 then Slider.set sli 0;
if h <> hh then begin
set_voffset container 0;
if is_shown bar then rec_set_show false bar;
end else begin
Slider.set_tick_size sli (imax min_tick_size (h * h / th));
if not (is_shown bar) then rec_set_show true bar;
set_width container (imax 0 (w - width bar));
end);
container.resize (w, h);
container, layout, slider
let pixel_area w h =
Theme.((scale_int w) * (scale_int (h+2*scroll_margin)))
let adjust_max_memory ~w ~h = function
| None -> None
| Some mm -> let wh = pixel_area w h in
if mm < wh * factor
then (printd (debug_user + debug_memory)
"Memory for this long_list should be at least %u for smoother \
behaviour" (wh * factor);
Some (wh * factor))
else Some mm
let create ?name ~w ~h ~length ?(first=0) ~generate ?height_fn
?(cleanup=Layout.delete_textures) ?max_memory ?(linear=true)
?(scrollbar_width=10) ?(scale_width=false) () : t =
let length = if length >= 0 then length else begin
printd (debug_error + debug_user)
"[Long_list.create]: the [length] argument should be non negative, got \
[%i] instead." length; 0 end in
let h = if h > 0 then h else begin
printd (debug_error + debug_user)
"Long_list height should be positive, got [%i] instead." h; 10 end in
let w = if w > 0 then w else begin
printd (debug_error + debug_user)
"Long_list width should be positive, got [%i] instead." w; 20 end in
let max_memory = adjust_max_memory ~h ~w max_memory in
let min_rendering_height = 2 * factor * h / 3 in
let no_height_fn_provided = (height_fn = None) in
let height_fn = default height_fn (fun _ -> None) in
let heights = Array.init length height_fn in
let computed_height, computed =
if no_height_fn_provided then 0, 0
else let rec loop i comp h =
if i >= length then h, comp
else let comp', h' = match height_fn i with
| None -> comp, h
| Some y -> (comp+1), (h+y) in
loop (i+1) comp' h' in
loop 0 0 0 in
let first = if first < length && first >= 0 then first
else begin
if length <> 0 then printd (debug_error + debug_user)
"[Long_list.create]: the [first] argument should be between 0 and \
[length-1], got [%i] instead." first;
if first < 0 then 0 else length - 1
end in
let ll =
{ total_height = if computed = length then Some computed_height else None;
computed_height;
length;
offset = Var.create (Avar.var 0);
computed;
min_rendering_height;
generate;
cleanup;
max_memory;
used_memory = 0;
array = Array.make length Void;
linear;
first;
last = first;
first_mem = 0;
last_mem = 0;
container_voffset = 0;
heights;
width_warning = false;
scale_width } in
Sync.push (fun () ->
ll.min_rendering_height <- compute_min_rendering_height ll (w, h));
let width = if scale_width then Some w else None in
let room, i_final = compute_room ~height:min_rendering_height ~width
ll first Down in
let ll_height = total_height ll in
printd (debug_memory + debug_board)
"Long list of height %d was initialized with %d entries (%d..%d) ouf of %d \
and height=%d, rendered_height=%d, approx. total height is %d"
h (i_final+1-first) first i_final ll.length (Layout.height room)
ll.min_rendering_height ll_height;
let container, layout, slider =
if length > 0
then make_clip ?name ~w ~h ~scrollbar_width ll room
else dummy_clip (w, h) in
let regenerate () = free_all container ll in
{ regenerate; layout; slider; ll }
let get_layout t = t.layout
let redraw t =
Update.push t.slider
let regenerate t = t.regenerate ()
let create_layout ?name ~w ~h ~length ?(first=0) ~generate ?height_fn
?(cleanup=Layout.delete_textures) ?max_memory ?(linear=true)
?(scrollbar_width=10) ?(scale_width=false) () =
get_layout (create ?name ~w ~h ~length ~first ~generate ?height_fn ~cleanup
?max_memory ~linear ~scrollbar_width ~scale_width ())
(** Return the maximal value of the scrollbar attached to the Long_list (if any). *)
let get_scroll_steps t =
Slider.get_max (Widget.get_slider t.slider)
let get_scroll_value t =
let s = Widget.get_slider t.slider in
Slider.update_value s;
let v = Slider.value s in
printd debug_custom "Scroll value=%i, ll.offset=%i" v (Avar.get (Var.get t.ll.offset));
v
let set_scroll_value t v =
let oldo = (Avar.get (Var.get t.ll.offset)) in
Slider.set (Widget.get_slider t.slider) v;
printd debug_custom "Set_scroll %i, old offset=%i, new_offset=%i" v oldo
(Avar.get (Var.get t.ll.offset));
redraw t
let get_scroll t =
1. -. float (get_scroll_value t) /. (float (get_scroll_steps t))
let set_scroll t x =
set_scroll_value t (round ((1. -. x) *. (float (get_scroll_steps t))))
let iter_computed_layouts f t =
for i = t.first_mem to t.last_mem do
match t.array.(i) with
| Computed room -> f i room
| _ -> ()
done