Source file note_brr_kit.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
open Brr
open Note
open Note_brr
module Key = struct
type code = int
type t =
[ `Alt of [ `Left | `Right ]
| `Arrow of [ `Up | `Down | `Left | `Right ]
| `Ascii of Char.t
| `Backspace
| `Ctrl of [ `Left | `Right ]
| `End
| `Enter
| `Escape
| `Func of int
| `Home
| `Insert
| `Key of code
| `Meta of [ `Left | `Right ]
| `Page of [ `Up | `Down ]
| `Return
| `Shift of [ `Left | `Right ]
| `Spacebar
| `Tab ]
let of_keycode kc = match kc with
| c when 48 <= c && c <= 57 -> `Ascii (Char.chr c)
| c when 65 <= c && c <= 90 -> `Ascii (Char.chr (c + 32) )
| c when 96 <= c && c <= 105 -> `Ascii (Char.chr (c - 96 + 48))
| c when 112 <= c && c <= 135 -> `Func (c - 111)
| 8 -> `Backspace
| 9 -> `Tab
| 13 -> `Return
| 16 -> `Shift `Left
| 17 -> `Ctrl `Left
| 18 -> `Alt `Left
| 27 -> `Escape
| 32 -> `Spacebar
| 33 -> `Page `Up
| 34 -> `Page `Down
| 35 -> `End
| 36 -> `Home
| 37 -> `Arrow `Left
| 38 -> `Arrow `Up
| 39 -> `Arrow `Right
| 40 -> `Arrow `Down
| 45 -> `Enter
| 91 | 224 -> `Meta `Left
| 93 -> `Meta `Right
| c -> `Key c
let of_ev e = of_keycode (Jv.Int.get (Ev.to_jv e) "keyCode")
let equal k0 k1 = k0 = k1
let compare k0 k1 = compare k0 k1
let dir_to_jstr = function
| `Left -> Jstr.v "left" | `Right -> Jstr.v "right"
| `Up -> Jstr.v "up" | `Down -> Jstr.v "down"
let to_jstr = function
| `Alt dir -> Jstr.(v "alt_" + dir_to_jstr dir)
| `Arrow dir -> Jstr.(v "arrow_" + dir_to_jstr dir)
| `Ascii c -> Jstr.(v "key_" + of_char c)
| `Backspace -> Jstr.v "backspace"
| `Ctrl dir -> Jstr.(v "ctrl_" + dir_to_jstr dir)
| `End -> Jstr.v "end"
| `Enter -> Jstr.v "enter"
| `Escape -> Jstr.v "escape"
| `Func n -> Jstr.(v "F" + of_int n)
| `Home -> Jstr.v "home"
| `Insert -> Jstr.v "insert"
| `Key c -> Jstr.(v "key_" + of_int c)
| `Meta dir -> Jstr.(v "meta_" + dir_to_jstr dir)
| `Page dir -> Jstr.(v "page_" + dir_to_jstr dir)
| `Return -> Jstr.v "return"
| `Shift dir -> Jstr.(v "shift_" + dir_to_jstr dir)
| `Spacebar -> Jstr.v "spacebar"
| `Tab -> Jstr.v "tab"
type events =
{ any_down : t event; send_any_down : t E.send;
any_up : t event; send_any_up : t E.send;
mutable down_count : int;
any_holds : bool signal; set_any_holds : bool S.set;
down_event : (t, unit event * unit E.send) Hashtbl.t ;
up_event : (t, unit event * unit E.send) Hashtbl.t;
holds : (t, bool signal * bool S.set) Hashtbl.t;
alt : bool signal; ctrl : bool signal; meta : bool signal;
shift : bool signal; }
let def_event event k = try fst (Hashtbl.find event k) with
| Not_found -> let d = E.create () in Hashtbl.add event k d; fst d
let send_event ?step event k = try snd (Hashtbl.find event k) ?step () with
| Not_found -> ()
let def_holds holds k = try fst (Hashtbl.find holds k) with
| Not_found -> let d = S.create false in Hashtbl.add holds k d; fst d
let set_holds ?step holds k v = try snd (Hashtbl.find holds k) ?step v with
| Not_found -> ()
let add_modifiers holds =
let lalt = S.create false in
let ralt = S.create false in
let alt = S.Bool.(fst lalt || fst ralt) in
let lctrl = S.create false in
let rctrl = S.create false in
let ctrl = S.Bool.(fst lctrl || fst rctrl) in
let lmeta = S.create false in
let rmeta = S.create false in
let meta = S.Bool.(fst lmeta || fst rmeta) in
let lshift = S.create false in
let rshift = S.create false in
let shift = S.Bool.(fst lshift || fst rshift) in
Hashtbl.add holds (`Alt `Left) lalt;
Hashtbl.add holds (`Alt `Right) ralt;
Hashtbl.add holds (`Ctrl `Left) lctrl;
Hashtbl.add holds (`Ctrl `Right) rctrl;
Hashtbl.add holds (`Meta `Left) lmeta;
Hashtbl.add holds (`Meta `Right) rmeta;
Hashtbl.add holds (`Shift `Left) lshift;
Hashtbl.add holds (`Shift `Right) rshift;
alt, ctrl, meta, shift
let handle_down evs ~step k =
evs.down_count <- evs.down_count + 1 ;
evs.send_any_down ~step k;
evs.set_any_holds ~step true;
send_event ~step evs.down_event k;
set_holds ~step evs.holds k true;
()
let handle_up evs ~step k =
evs.down_count <- evs.down_count - 1;
evs.send_any_up ~step k;
if evs.down_count <= 0 then
(evs.down_count <- 0; evs.set_any_holds ~step false);
send_event ~step evs.up_event k;
set_holds ~step evs.holds k false;
()
let down_cb evs e =
if Ev.(Keyboard.repeat (as_type e)) then () else
let step = Step.create () in
handle_down evs ~step (of_ev e);
Step.execute step
let up_cb evs e =
let step = Step.create () in
handle_up evs ~step (of_ev e);
Step.execute step
let on_target ?capture ?propagate ?default t =
let hsize = 47 in
let any_down, send_any_down = E.create () in
let any_up, send_any_up = E.create () in
let any_holds, set_any_holds = S.create false in
let down_event = Hashtbl.create hsize in
let up_event = Hashtbl.create hsize in
let holds = Hashtbl.create hsize in
let alt, ctrl, meta, shift = add_modifiers holds in
let evs = { any_down; send_any_down; any_up;
send_any_up; down_count = 0; any_holds; set_any_holds;
down_event; up_event; holds; alt; ctrl; meta; shift }
in
Evr.endless_listen ?capture ?propagate ?default t Ev.keydown (down_cb evs);
Evr.endless_listen ?capture ?propagate ?default t Ev.keyup (up_cb evs);
evs
let on_el ?capture ?propagate ?default t =
on_target ?capture ?propagate ?default (El.as_target t)
let any_down evs = evs.any_down
let any_up evs = evs.any_up
let any_holds evs = evs.any_holds
let down evs k = def_event evs.down_event k
let up evs k = def_event evs.up_event k
let holds evs k = def_holds evs.holds k
let alt evs = evs.alt
let ctrl evs = evs.ctrl
let meta evs = evs.meta
let shift evs = evs.shift
end
module Mouse = struct
let warn_but () = Console.(warn [Jstr.v "unexpected e.which"])
let pt x y = (x, y)
type 'a events =
{ t : Ev.target;
normalize : bool;
pt : float -> float -> 'a;
mutable last_pos : float * float;
mutable unlisten : (unit -> unit) list;
pos : 'a signal; set_pos : 'a S.set;
dpos : 'a event; send_dpos : 'a E.send;
mem : bool signal; set_mem : bool S.set;
left : bool signal; set_left : bool S.set;
left_down : 'a event; send_left_down : 'a E.send;
left_up : 'a event; send_left_up : 'a E.send;
mid : bool signal; set_mid : bool S.set;
mid_down : 'a event; send_mid_down : 'a E.send;
mid_up : 'a event; send_mid_up : 'a E.send;
right : bool signal; set_right : bool S.set;
right_down : 'a event; send_right_down : 'a E.send;
right_up : 'a event; send_right_up : 'a E.send; }
let destroy evs = List.iter (fun f -> f ()) evs.unlisten
let event_mouse_pos pt evs e =
let t = (Obj.magic evs.t : El.t) in
let x = (Ev.Mouse.client_x e) -. El.bound_x t in
let y = (Ev.Mouse.client_y e) -. El.bound_y t in
if not evs.normalize then pt x y else
let nx = x /. (El.bound_w t) in
let ny = 1. -. (y /. (El.bound_h t)) in
pt nx ny
let set_mouse_pos ~step evs e =
let x, y as l = event_mouse_pos pt evs e in
let epos = evs.pt x y in
let dx = x -. fst evs.last_pos in
let dy = y -. snd evs.last_pos in
evs.send_dpos ~step (evs.pt dx dy);
evs.set_pos ~step epos;
evs.last_pos <- l;
epos
let move_cb evs e =
let step = Step.create () in
let _ = set_mouse_pos ~step evs (Ev.as_type e) in
Step.execute step
let mem_cb mem evs e =
let step = Step.create () in
let _ = set_mouse_pos ~step evs (Ev.as_type e) in
evs.set_mem ~step mem;
Step.execute step
let down_cb evs e =
let step = Step.create () in
let epos = set_mouse_pos ~step evs (Ev.as_type e) in
let set, send_down = match Ev.Mouse.button (Ev.as_type e) with
| 0 -> evs.set_left, evs.send_left_down
| 1 -> evs.set_mid, evs.send_mid_down
| 2 -> evs.set_right, evs.send_right_down
| _ -> warn_but(); evs.set_left, evs.send_left_down
in
set ~step true; send_down ~step epos;
Step.execute step
let up_cb evs e =
let step = Step.create () in
let epos = set_mouse_pos ~step evs (Ev.as_type e) in
let set, send_up = match Ev.Mouse.button (Ev.as_type e) with
| 0 -> evs.set_left, evs.send_left_up
| 1 -> evs.set_mid, evs.send_mid_up
| 2 -> evs.set_right, evs.send_right_up
| _ -> warn_but (); evs.set_left, evs.send_left_up
in
set ~step false; send_up ~step epos;
Step.execute step
let doc_up_cb evs e =
if not (S.rough_value evs.mem) &&
(S.rough_value evs.left || S.rough_value evs.mid ||
S.rough_value evs.right)
then up_cb evs e else ()
let on_target ?capture ?propagate ?default ?(normalize = true) pt t =
let pos, set_pos = S.create (pt 0. 0.) in
let dpos, send_dpos = E.create () in
let mem, set_mem = S.create false in
let left, set_left = S.create false in
let left_down, send_left_down = E.create () in
let left_up, send_left_up = E.create () in
let mid, set_mid = S.create false in
let mid_down, send_mid_down = E.create () in
let mid_up, send_mid_up = E.create () in
let right, set_right = S.create false in
let right_down, send_right_down = E.create () in
let right_up, send_right_up = E.create () in
let evs =
{ t; normalize; pt; last_pos = (0., 0.);
unlisten = [];
pos; set_pos;
dpos; send_dpos;
mem; set_mem;
left; set_left; left_down; send_left_down; left_up; send_left_up;
mid; set_mid; mid_down; send_mid_down; mid_up; send_mid_up;
right; set_right; right_down; send_right_down; right_up; send_right_up}
in
let l = Evr.listen in
let unlisten =
[ l ?capture ?propagate ?default evs.t Ev.mousedown (down_cb evs);
l ?capture ?propagate ?default evs.t Ev.mouseup (up_cb evs);
l ?capture ?propagate ?default evs.t Ev.mousemove (move_cb evs);
l ?capture ?propagate ?default evs.t Ev.mouseenter (mem_cb true evs);
l ?capture ?propagate ?default evs.t Ev.mouseleave (mem_cb false evs);
l ?capture ?propagate ?default
(Document.as_target G.document) Ev.mouseup (doc_up_cb evs) ]
in
evs.unlisten <- unlisten; evs
let on_el ?capture ?propagate ?default ?normalize pt e =
let t = El.as_target e in
let evs = on_target ?capture ?propagate ?default ?normalize pt t in
Elr.on_rem (fun () -> destroy evs) e;
evs
let pos evs = evs.pos
let dpos evs = evs.dpos
let mem evs = evs.mem
let left evs = evs.left
let left_down evs = evs.left_down
let left_up evs = evs.left_up
let mid evs = evs.mid
let mid_down evs = evs.mid_down
let mid_up evs = evs.mid_up
let right evs = evs.right
let right_down evs = evs.right_down
let right_up evs = evs.right_up
module Cursor = struct
type t = Jstr.t
let url ?(x = 0) ?(y = 0) url = match x = 0 && y = 0 with
| true -> Jstr.(v "url(" + url + v ")")
| false -> Jstr.(v "url(" + url + v ") " + of_int x + sp + of_int y)
let auto = Jstr.v "auto"
let default = Jstr.v "default"
let none = Jstr.v "none"
let = Jstr.v "context-menu"
let help = Jstr.v "help"
let pointer = Jstr.v "pointer"
let progress = Jstr.v "progress"
let wait = Jstr.v "wait"
let cell = Jstr.v "cell"
let crosshair = Jstr.v "crosshair"
let text = Jstr.v "text"
let vertical_text = Jstr.v "vertical-text"
let alias = Jstr.v "alias"
let copy = Jstr.v "copy"
let move = Jstr.v "move"
let no_drop = Jstr.v "no-drop"
let not_allowed = Jstr.v "not-allowed"
let grab = Jstr.v "grab"
let grabbing = Jstr.v "grabbing"
let e_resize = Jstr.v "e-resize"
let n_resize = Jstr.v "n-resize"
let ne_resize = Jstr.v "ne-resize"
let nw_resize = Jstr.v "nw-resize"
let s_resize = Jstr.v "s-resize"
let se_resize = Jstr.v "se-resize"
let sw_resize = Jstr.v "sw-resize"
let w_resize = Jstr.v "w-resize"
let ew_resize = Jstr.v "ew-resize"
let ns_resize = Jstr.v "ns-resize"
let nesw_resize = Jstr.v "nesw-resize"
let nwse_resize = Jstr.v "nwse-resize"
let col_resize = Jstr.v "col-resize"
let row_resize = Jstr.v "row-resize"
let all_scroll = Jstr.v "all-scroll"
let zoom_in = Jstr.v "zoom-in"
let zoom_out = Jstr.v "zoom-out"
end
end
module Windowr = struct
let in_fullscreen () =
Option.is_some (Document.fullscreen_element G.document)
let is_fullscreen =
if Jv.is_none (Document.to_jv G.document) then S.const false else
let is_fullscreen, set_fullscreen = S.create (in_fullscreen ()) in
let change _e = set_fullscreen (in_fullscreen ()) in
ignore
(Ev.listen Ev.fullscreenchange change (Document.as_target G.document));
is_fullscreen
let quit =
if Jv.is_none (Document.to_jv G.document) then E.never else
let quit, send_quit = E.create () in
let send_quit _e = send_quit () in
ignore (Ev.listen Ev.unload send_quit (Document.as_target G.document));
quit
end
module Time = struct
type span = float
let tick_now () = Performance.now_ms G.performance /. 1000.
let start = tick_now ()
let elapsed () = tick_now () -. start
type counter = span
let counter () = tick_now ()
let counter_value c = tick_now () -. c
let tick span =
let e, send_e = E.create () in
let c = counter () in
let action () = send_e (counter_value c) in
let ms = truncate @@ span *. 1000. in
ignore (G.set_timeout action ~ms);
e
let delay span f = ignore (G.set_timeout f ~ms:(truncate @@ span *. 1000.))
let to_jstr u s = match u with
| `S -> Jstr.(of_float s + v "s")
| `Ms -> Jstr.(of_float (s *. 1e3) + v "ms")
| `Mus -> Jstr.(of_float (s *. 1e6) + v "μs")
end
module Human = struct
let noticed = 0.1
let interrupted = 1.
let left = 10.
let rec feel_action feel set_feel () =
let new_feel, delay = match S.value feel with
| `Interacting -> `Interrupted, left -. interrupted
| `Interrupted -> `Left, 0.
| `Left -> assert false
in
set_feel new_feel;
if delay = 0. then () else
let action = feel_action feel set_feel in
let ms = truncate @@ delay *. 1000. in
ignore (G.set_timeout ~ms action);
()
let feel () =
let feel, set_feel = S.create `Interacting in
let action = feel_action feel set_feel in
let ms = truncate @@ interrupted *. 1000. in
ignore (G.set_timeout ~ms action);
feel
let touch_target_size = 9.
let touch_target_size_min = 7.
let touch_target_pad = 2.
let average_finger_width = 11.
end
module Ui = struct
let ui_active = Jstr.v "ui-active"
let ui_button = Jstr.v "ui-button"
let ui_button_selector = Jstr.v "ui-button-selector"
let ui_dir_align_center = Jstr.v "ui-dir-align-center"
let ui_dir_align_distribute = Jstr.v "ui-dir-align-distribute"
let ui_dir_align_end = Jstr.v "ui-dir-align-end"
let ui_dir_align_justify = Jstr.v "ui-dir-align-justify"
let ui_dir_align_start = Jstr.v "ui-dir-align-start"
let ui_dir_align_stretch = Jstr.v "ui-dir-align-stretch"
let ui_dir_h = Jstr.v "ui-dir-h"
let ui_dir_v = Jstr.v "ui-dir-v"
let ui_disabled = Jstr.v "ui-disabled"
let ui_editing = Jstr.v "ui-editing"
let ui_file_selector = Jstr.v "ui-file-selector"
let ui_group = Jstr.v "ui-group"
let ui_label = Jstr.v "ui-label"
let = Jstr.v "ui-menu-selector"
let ui_selected = Jstr.v "ui-selected"
let ui_slider_selector = Jstr.v "ui-slider-selector"
let ui_str_editor = Jstr.v "ui-str-editor"
let ui_xdir_align_center = Jstr.v "ui-xdir-align-center"
let ui_xdir_align_distribute = Jstr.v "ui-xdir-align-distribute"
let ui_xdir_align_end = Jstr.v "ui-xdir-align-end"
let ui_xdir_align_justify = Jstr.v "ui-xdir-align-justify"
let ui_xdir_align_start = Jstr.v "ui-xdir-align-start"
let ui_xdir_align_stretch = Jstr.v "ui-xdir-align-stretch"
let disabled ~enabled =
let is_disabled enabled = if enabled then None else Some Jstr.empty in
S.map is_disabled enabled
let el_def_tip ~tip el = match tip with
| None -> ()
| Some tip -> Elr.def_at At.Name.title (S.Option.some tip) el
module Group = struct
type dir = [ `H | `V ]
type align = [ `Start | `End | `Center | `Justify | `Distribute | `Stretch ]
let dir_cls = [ `H, ui_dir_h; `V, ui_dir_v; ]
let align_cls =
[ `Start, ui_dir_align_start; `End, ui_dir_align_end;
`Center, ui_dir_align_center; `Justify, ui_dir_align_justify;
`Distribute, ui_dir_align_distribute; `Stretch, ui_dir_align_stretch; ]
let xalign_cls =
[ `Start, ui_xdir_align_start; `End, ui_xdir_align_end;
`Center, ui_xdir_align_center; `Justify, ui_xdir_align_justify;
`Distribute, ui_xdir_align_distribute; `Stretch, ui_xdir_align_stretch;]
let set_class classes el v = El.set_class (List.assoc v classes) true el
type 'a t =
{ el : El.t;
enabled : bool signal;
action : 'a event;
dir : dir;
dir_align : align;
xdir_align : align; }
let v
?class':cl ?(enabled = S.Bool.true') ?(action = E.never)
?(xdir_align = `Start) ?(dir_align = `Start) ~dir cs
=
let at = At.[if_some (Option.map class' cl); class' ui_group] in
let el = El.div ~at [] in
let () = Elr.def_children el cs
and () = Elr.def_class ui_disabled (S.Bool.not enabled) el
and () = set_class dir_cls el dir
and () = set_class align_cls el dir_align
and () = set_class xalign_cls el xdir_align in
{ el; enabled; action; dir; dir_align; xdir_align }
let dir g = g.dir
let dir_align g = g.dir_align
let xdir_align g = g.xdir_align
let action g = g.action
let enabled g = g.enabled
let el g = g.el
let with_action action g = { g with action }
let hide_action g = with_action E.never g
end
module Label = struct
type t = { el : El.t; enabled : bool signal }
let v ?class':cl ?(enabled = S.Bool.true') ?tip cs =
let at = At.[if_some (Option.map class' cl); class' ui_label] in
let el = El.div ~at [] in
let () = Elr.def_children el cs
and () = el_def_tip ~tip el
and () = Elr.def_class ui_disabled (S.Bool.not enabled) el in
{el; enabled}
let el l = l.el
let enabled l = l.enabled
end
module Button = struct
type 'a t =
{ el : El.t;
action : 'a event;
active : bool signal;
enabled : bool signal; }
let button_str = Jstr.v "button"
let at_base cl =
At.[if_some (Option.map class' cl); type' button_str; class' ui_button]
let v
?class':cl ?(active = S.Bool.false') ?(enabled = S.Bool.true') ?tip cs v
=
let el = El.button ~at:(at_base cl) [] in
let action = Evr.on_el Ev.click (Evr.stamp v) el in
let () = Elr.def_children el cs
and () = el_def_tip ~tip el
and () = Elr.def_at At.Name.disabled (disabled ~enabled) el
and () = Elr.def_class ui_disabled (S.Bool.not enabled) el
and () = Elr.def_class ui_active active el in
{ el; action; active; enabled }
let action b = b.action
let enabled b = b.enabled
let active b = b.active
let el b = b.el
let file_str = Jstr.v "file"
let accept_str = Jstr.v "accept"
let multiple_str = Jstr.v "multiple"
let _file_selector
~multiple get ?class':cl ?(active = S.Bool.false')
?(enabled = S.Bool.true') ?tip ?(exts = []) cs
=
let input =
let at = match exts with
| [] -> []
| exts -> [ At.v accept_str (Jstr.v (String.concat "," exts)) ]
in
let at = At.if' multiple (At.v multiple_str Jstr.empty) :: at in
let at = At.type' file_str :: at in
El.input ~at ()
in
let el = El.button ~at:At.(class' ui_file_selector :: at_base cl) [] in
let () = El.set_inline_style El.Style.display (Jstr.v "none") input
and () =
let forward e =
El.set_prop El.Prop.value Jstr.empty input;
El.click input
in
ignore (Ev.listen Ev.click forward (El.as_target el))
and () =
Elr.def_children el (S.map ~eq:(==) (fun cs -> cs @ [input]) cs)
and () = el_def_tip ~tip el
and () = Elr.def_at At.Name.disabled (disabled ~enabled) el
and () = Elr.def_class ui_disabled (S.Bool.not enabled) el
and () = Elr.def_class ui_active active el
and action = Evr.on_el Ev.change (get input) input in
{ el; enabled; action; active }
let file_selector =
_file_selector ~multiple:false (fun i _ -> List.hd (El.Input.files i))
let files_selector =
_file_selector ~multiple:true (fun i _ -> El.Input.files i)
end
module Jstr_editor = struct
let text_str = Jstr.v "text"
type t =
{ el : El.t;
enabled : bool signal;
editing : bool signal;
action : Jstr.t event }
let att_size = Jstr.v "size"
let v ?class':cl ?(enabled = S.Bool.true') ?on:(edit = E.never) ?length str =
let span = El.span [] in
let editor = El.input ~at:At.[type' text_str] () in
let div =
let at = At.[if_some (Option.map class' cl); class' ui_str_editor] in
El.div ~at [span; editor]
in
let edit = E.select [E.stamp edit (); Evr.on_el Ev.click Evr.unit div] in
let edit = S.sample_filter enabled ~on:edit @@ fun enabled _ ->
if enabled then Some () else None
in
let keys = Evr.on_el Ev.keydown Key.of_ev editor in
let escape_key = E.stamp (E.filter (Key.equal `Escape) keys) false in
let return_key = E.stamp (E.filter (Key.equal `Return) keys) true in
let start_focus = Evr.on_el Ev.focus (Evr.stamp true) editor in
let stop_focus = Evr.on_el Ev.blur (Evr.stamp false) editor in
let focus =
S.hold (El.has_focus editor) @@ E.select [start_focus;stop_focus] in
let valid = S.hold true @@ E.select [start_focus; escape_key] in
let start = E.stamp edit true in
let key_stop = E.stamp (E.select [escape_key; return_key]) false in
let stop = E.stamp (E.select [key_stop; stop_focus]) false in
let editing = S.hold false (E.select [start; stop]) in
let action = S.sample_filter valid ~on:stop_focus @@ fun valid _ ->
if valid then Some (El.prop El.Prop.value editor) else None
in
let () = Elr.def_children span (S.map (fun s -> [El.txt s]) str)
and () =
Elr.call (fun _ e -> El.select_text e) ~on:start editor
and () = Elr.set_prop El.Prop.value ~on:(S.snapshot str ~on:edit) editor
and () = Elr.def_has_focus focus editor
and () = Elr.def_at At.Name.disabled (disabled ~enabled) editor
and () = Elr.def_class ui_disabled (S.Bool.not enabled) div
and () = Elr.def_class ui_editing editing div
and () = match length with
| None -> ()
| Some l ->
let size = S.map (fun l -> Some (Jstr.of_int l)) l in
Elr.def_at att_size size editor
in
{ el = div; enabled; editing; action }
let action e = e.action
let enabled e = e.enabled
let editing e = e.editing
let el e = e.el
end
module Value_selector = struct
module Button = struct
type 'a t = 'a Group.t
let v
?class' ?(enabled = S.Bool.true') ?button_class ?button_tip
?xdir_align ?dir_align ~dir label choices sel
=
let but v =
let class' = match button_class with
| Some f -> Some (f v)| None -> None
in
let tip = match button_tip with Some f -> Some (f v) | None -> None in
let label = label v in
Button.v ?class' ?tip ~enabled label v
in
let buts = S.map ~eq:( == ) (List.map but) choices in
let els = S.map ~eq:( == ) (List.map Button.el) buts in
let action =
let select buts = E.select (List.map Button.action buts) in
E.swap @@ S.map ~eq:( == ) select buts
in
let sel_obs =
let find_sel_but eq sel choices buts = match sel with
| None ->
let deselect b = El.set_class ui_selected false (Button.el b) in
List.iter deselect buts
| Some sel ->
let rec loop sel choices buts = match choices, buts with
| c :: cs, b :: bs when eq (Some c) (Some sel) ->
El.set_class ui_selected true (Button.el b);
loop sel cs bs
| _ :: cs, b :: bs ->
El.set_class ui_selected false (Button.el b);
loop sel cs bs
| [], [] -> ()
| _, _ -> assert false
in
loop sel choices buts
in
Logr.(const (find_sel_but (S.eq sel)) $ S.obs sel $ S.obs choices $
S.obs buts)
in
let g = Group.v ?class' ~action ?xdir_align ?dir_align ~dir els in
let () = El.set_class ui_button_selector true (Group.el g) in
let () = Elr.hold_logr (Group.el g) (Logr.create sel_obs) in
g
end
end
module Float_selector = struct
type t =
{ el : El.t;
enabled : bool signal;
action : float event; }
let range_str = Jstr.v "range"
let min_str = Jstr.v "min"
let max_str = Jstr.v "max"
let step_str = Jstr.v "step"
let v
?class' ?(enabled = S.Bool.true') ?(min = S.const 0.)
?(max = S.const 1.) ?(step = S.const None) v
=
let v = S.map (fun v -> Jstr.of_float v) v in
let at = At.[type' range_str; class' ui_slider_selector; tabindex (-1)] in
let el = El.input ~at () in
let e _ = match El.prop El.Prop.value e with
| s when Jstr.is_empty s -> None
| s -> Some (float_of_string (Jstr.to_string s))
in
let action =
E.Option.on_some @@ Evr.on_el Ev.input (extract_value el) el
in
let min_att = S.map (fun v -> Some (Jstr.of_float v)) min in
let max_att = S.map (fun v -> Some (Jstr.of_float v)) max in
let step_att = step |> S.map @@ function
| None -> Some (Jstr.v "any")
| Some f -> Some (Jstr.v @@ string_of_float f)
in
let () = Elr.def_at min_str min_att el
and () = Elr.def_at max_str max_att el
and () = Elr.def_at step_str step_att el
and () = Elr.def_at At.Name.disabled (disabled ~enabled) el
and () = Elr.def_class ui_disabled (S.Bool.not enabled) el
and () = Elr.def_prop El.Prop.value v el
and () =
let unset_focus _ = El.set_has_focus false el in
ignore (Ev.listen Ev.focus unset_focus (El.as_target el))
in
{ el; action; enabled }
let action r = r.action
let enabled r = r.enabled
let el r = r.el
end
end