aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xlib/elements/input.ml25
-rw-r--r--motus/js/fieldList.ml89
-rwxr-xr-xscript.it/script.ml392
3 files changed, 245 insertions, 261 deletions
diff --git a/lib/elements/input.ml b/lib/elements/input.ml
index 5def7d4..8c4bcea 100755
--- a/lib/elements/input.ml
+++ b/lib/elements/input.ml
@@ -12,31 +12,27 @@ let slider : at:Brr.At.t list -> Brr.El.t * float S.t =
let init_value = Jstr.to_float (El.prop El.Prop.value slider) in
let event =
- Evr.on_el
- Ev.input
+ Evr.on_el Ev.input
(fun _ ->
let raw_value = El.prop El.Prop.value slider in
- Jstr.to_float raw_value )
+ Jstr.to_float raw_value)
slider
|> S.hold init_value
in
(slider, event)
-
-type file =
- { file : File.t
+type file = {
+ file : File.t
; content : Jstr.t
- }
+}
(** Read the content from the file *)
let file_loader : file Note.E.send -> File.t -> unit =
fun event file ->
let blob = File.as_blob file in
- Fut.await
- (Blob.text blob)
+ Fut.await (Blob.text blob)
(Result.iter (fun content -> event { file; content }))
-
(** Create an imput which load a file.
[file_loader (Jstr.v ".json"] will create an input which only accept json
@@ -48,8 +44,7 @@ let file_loader : Jstr.t -> Brr.El.t * file Note.event =
let add_file_event, add_file_sender = Note.E.create () in
let i =
- El.input
- ()
+ El.input ()
~at:[ At.type' (Jstr.v "file"); (At.v (Jstr.v "accept")) selector ]
in
@@ -59,6 +54,10 @@ let file_loader : Jstr.t -> Brr.El.t * file Note.event =
list. *)
let on_change files = file_loader add_file_sender (List.hd files) in
- Ev.listen Ev.change (fun _e -> on_change (El.Input.files i)) (El.as_target i);
+ let _ =
+ Ev.listen Ev.change
+ (fun _e -> on_change (El.Input.files i))
+ (El.as_target i)
+ in
(i, add_file_event)
diff --git a/motus/js/fieldList.ml b/motus/js/fieldList.ml
index 26b89bb..5af5e92 100644
--- a/motus/js/fieldList.ml
+++ b/motus/js/fieldList.ml
@@ -12,32 +12,26 @@ type elements = Brr.El.t list
*)
let get_validity_from_element : El.t -> Motus_lib.Validity.t =
fun el ->
- if El.class' (Jstr.v "missing") el
- then Missing
- else if El.class' (Jstr.v "misplaced") el
- then Misplaced
+ if El.class' (Jstr.v "missing") el then Missing
+ else if El.class' (Jstr.v "misplaced") el then Misplaced
else Wellplaced
-
let get_rules : elements -> State.proposition =
fun t ->
List.map
~f:(fun input ->
let value = El.prop El.Prop.value input in
- if Jstr.equal Jstr.empty value
- then None
+ if Jstr.equal Jstr.empty value then None
else
let validity = get_validity_from_element input in
- Some (value, validity) )
+ Some (value, validity))
t
-
let get_class : Motus_lib.Validity.t -> Jstr.t = function
| Wellplaced -> Jstr.v "wellplaced"
| Misplaced -> Jstr.v "misplaced"
| _ -> Jstr.v "missing"
-
(** Create the field list modifiied by the user *)
let make : int -> (int * Jstr.t * Motus_lib.Validity.t) E.send -> elements =
fun len change_sender ->
@@ -46,7 +40,8 @@ let make : int -> (int * Jstr.t * Motus_lib.Validity.t) E.send -> elements =
El.input
~at:
At.
- [ type' (Jstr.v "text")
+ [
+ type' (Jstr.v "text")
; v (Jstr.v "maxLength") (Jstr.v "1")
; value Jstr.empty
; class' (Jstr.v "missing")
@@ -55,27 +50,26 @@ let make : int -> (int * Jstr.t * Motus_lib.Validity.t) E.send -> elements =
()
in
- Ev.listen
- Ev.change
- (fun _ ->
- let validity = get_validity_from_element input in
- change_sender (i, El.prop El.Prop.value input, validity) )
- (El.as_target input);
-
- Ev.listen
- Ev.click
- (fun _ ->
- let validity =
- match get_validity_from_element input with
- | Missing -> Motus_lib.Validity.Misplaced
- | Misplaced -> Motus_lib.Validity.Wellplaced
- | Wellplaced -> Motus_lib.Validity.Missing
- in
- change_sender (i, El.prop El.Prop.value input, validity) )
- (El.as_target input);
-
- El.td [ input ] )
+ let _ =
+ Ev.listen Ev.change
+ (fun _ ->
+ let validity = get_validity_from_element input in
+ change_sender (i, El.prop El.Prop.value input, validity))
+ (El.as_target input)
+ and _ =
+ Ev.listen Ev.click
+ (fun _ ->
+ let validity =
+ match get_validity_from_element input with
+ | Missing -> Motus_lib.Validity.Misplaced
+ | Misplaced -> Motus_lib.Validity.Wellplaced
+ | Wellplaced -> Motus_lib.Validity.Missing
+ in
+ change_sender (i, El.prop El.Prop.value input, validity))
+ (El.as_target input)
+ in
+ El.td [ input ])
(** Set the element class depending of the proposition validity for each letter
*)
@@ -115,8 +109,7 @@ let set_with_props :
El.set_class (Jstr.v "missing") false hd;
El.set_at (Jstr.v "readonly") (Some (Jstr.v "false")) hd;
El.set_class (get_class validity) true hd
- | _, [], _ -> () )
-
+ | _, [], _ -> ())
let build : El.t -> int S.t -> State.proposition S.t =
fun container length ->
@@ -128,27 +121,26 @@ let build : El.t -> int S.t -> State.proposition S.t =
El.input
~at:
At.
- [ type' (Jstr.v "text")
+ [
+ type' (Jstr.v "text")
; v (Jstr.v "maxLength") (Jstr.v "1")
; value Jstr.empty
]
()
in
- input )
+ input)
in
let events =
List.mapi
~f:(fun i input ->
- Evr.on_el
- Ev.input
+ Evr.on_el Ev.input
(fun _ ->
let value = El.prop El.Prop.value input in
- if Jstr.equal Jstr.empty value
- then (i, None)
+ if Jstr.equal Jstr.empty value then (i, None)
else
let validity = Motus_lib.Validity.Wellplaced in
- (i, Some (Jstr.uppercased value, validity)) )
- input )
+ (i, Some (Jstr.uppercased value, validity)))
+ input)
elements
(* As the state is in a list, we have no way to be sure that the list
length is the same as the number of elements… except to rely on the
@@ -159,13 +151,14 @@ let build : El.t -> int S.t -> State.proposition S.t =
and init_prop = List.init ~len ~f:(fun _ -> None) in
(* Replace the children in the element *)
- El.set_children
- container
- [ El.table
- [ (* The table has only one row *)
+ El.set_children container
+ [
+ El.table
+ [
+ (* The table has only one row *)
El.tr
(List.map elements ~f:(fun el ->
- El.td [ (* Each cell is the input element *) el ] ) )
+ El.td [ (* Each cell is the input element *) el ]))
]
];
@@ -173,8 +166,8 @@ let build : El.t -> int S.t -> State.proposition S.t =
E.select events
|> E.map (fun (position, value) acc ->
List.mapi acc ~f:(fun i prop ->
- if i <> position then prop else value ) )
+ if i <> position then prop else value))
in
let initial_proposition = S.accum init_prop change in
- initial_proposition )
+ initial_proposition)
diff --git a/script.it/script.ml b/script.it/script.ml
index e4cec67..fffc589 100755
--- a/script.it/script.ml
+++ b/script.it/script.ml
@@ -9,7 +9,6 @@ module Path = Script_path
let post : Brr_webworkers.Worker.t -> Worker_messages.to_worker -> unit =
Brr_webworkers.Worker.post
-
type canva_events =
[ `MouseDown of float * float
| `Out of float * float
@@ -30,8 +29,7 @@ let canva :
El.set_prop El.Prop.width (El.prop Elements.Prop.offsetWidth element) element;
- El.set_prop
- El.Prop.height
+ El.set_prop El.Prop.height
(El.prop Elements.Prop.offsetHeight element)
element;
@@ -62,27 +60,27 @@ let canva :
(E.select [ click; up ], pos, c)
-
let click_event el = Evr.on_el Ev.click Evr.unit el
-type 'a param_events =
- { width : float S.t
+type 'a param_events = {
+ width : float S.t
; angle : float S.t
; export : unit E.t
; delete : unit E.t
; rendering : State.event E.t
- }
+}
-type slider =
- { input : El.t
+type slider = {
+ input : El.t
; legend : El.t
- }
+}
let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
fun element state ->
let delete =
El.button
- [ El.i
+ [
+ El.i
~at:At.[ class' (Jstr.v "fas"); class' (Jstr.v "fa-times-circle") ]
[]
; El.txt' "Delete "
@@ -93,7 +91,8 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
let export =
El.button
- [ El.i ~at:At.[ class' (Jstr.v "fas"); class' (Jstr.v "fa-download") ] []
+ [
+ El.i ~at:At.[ class' (Jstr.v "fas"); class' (Jstr.v "fa-download") ] []
; El.txt' "Download"
]
in
@@ -103,7 +102,8 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
Elements.Input.slider
~at:
At.
- [ type' (Jstr.v "range")
+ [
+ type' (Jstr.v "range")
; v (Jstr.v "min") (Jstr.v "1")
; v (Jstr.v "max") (Jstr.v "50")
; At.value (Jstr.of_float state.width)
@@ -117,7 +117,8 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
Elements.Input.slider
~at:
At.
- [ type' (Jstr.v "range")
+ [
+ type' (Jstr.v "range")
; v (Jstr.v "min") (Jstr.v "0")
; v (Jstr.v "max") (Jstr.v "90")
; At.value (Jstr.of_float state.angle)
@@ -129,7 +130,8 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
let render =
El.select
- [ El.option ~at:At.[ value (Jstr.v "1") ] [ El.txt' "Fill" ]
+ [
+ El.option ~at:At.[ value (Jstr.v "1") ] [ El.txt' "Fill" ]
; El.option ~at:At.[ value (Jstr.v "3") ] [ El.txt' "Ductus" ]
]
in
@@ -137,8 +139,7 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
let rendering' = El.div [ El.txt' "Rendering : "; render ] in
let render_event =
- Evr.on_el
- Ev.change
+ Evr.on_el Ev.change
(fun _ ->
let raw_value = El.prop El.Prop.value render |> Jstr.to_int in
let render_type =
@@ -154,14 +155,14 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
let process t state = { state with State.rendering = t }
end in
- State.dispatch (module M) render_type )
+ State.dispatch (module M) render_type)
rendering'
in
let () =
- El.append_children
- element
- [ El.hr ()
+ El.append_children element
+ [
+ El.hr ()
; delete
; export
; rendering'
@@ -172,7 +173,8 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
; input_angle
]
in
- ( { delete = delete_event
+ ( {
+ delete = delete_event
; angle = angle_event
; width = nib_size_event
; export = export_event
@@ -181,11 +183,8 @@ let set_sidebar : El.t -> State.state -> _ param_events * slider * slider =
, angle_slider
, width_slider )
-
let backgroundColor = Blog.Nord.nord0
-
let white = Jstr.v "#eceff4"
-
let green = Jstr.v "#a3be8c"
let draw_point point context =
@@ -193,7 +192,6 @@ let draw_point point context =
let x, y = Gg.V2.to_tuple @@ Path.Point.get_coord point in
Cd2d.stroke_rect ~x:(x -. 5.) ~y:(y -. 5.) ~w:10. ~h:10. context
-
(** Redraw the canva on update *)
let on_change canva mouse_position timer state =
let pos = S.rough_value mouse_position in
@@ -204,7 +202,7 @@ let on_change canva mouse_position timer state =
Brr_canvas.Canvas.(float_of_int @@ w canva, float_of_int @@ h canva)
in
- let context = Cd2d.create canva in
+ let context = Brr_canvas.C2d.get_context canva in
Cd2d.set_fill_style context (Cd2d.color backgroundColor);
Cd2d.fill_rect context ~x:0.0 ~y:0.0 ~w ~h;
@@ -226,42 +224,38 @@ let on_change canva mouse_position timer state =
let back =
Path.Path_Builder.map current (fun pt ->
- Path.Point.copy pt @@ Path.Point.get_coord' pt )
+ Path.Point.copy pt @@ Path.Point.get_coord' pt)
in
Layer.Paths.to_canva
(module Path.Path_Builder)
- (current, back)
- context
- state.rendering;
+ (current, back) context state.rendering;
List.iter state.paths ~f:(fun path ->
let () =
match state.mode with
- | Selection (Path id) | Selection (Point (id, _)) ->
- ( match id = path.Outline.id with
- | true ->
- (* If the element is the selected one, change the color *)
- Cd2d.set_fill_style context (Cd2d.color Blog.Nord.nord8);
- Cd2d.set_stroke_style context (Cd2d.color Blog.Nord.nord8)
- | false ->
- Cd2d.set_stroke_style context (Cd2d.color white);
- Cd2d.set_fill_style context (Cd2d.color white) )
+ | Selection (Path id) | Selection (Point (id, _)) -> (
+ match id = path.Outline.id with
+ | true ->
+ (* If the element is the selected one, change the color *)
+ Cd2d.set_fill_style context (Cd2d.color Blog.Nord.nord8);
+ Cd2d.set_stroke_style context (Cd2d.color Blog.Nord.nord8)
+ | false ->
+ Cd2d.set_stroke_style context (Cd2d.color white);
+ Cd2d.set_fill_style context (Cd2d.color white))
| _ -> ()
in
let p = path.Outline.path in
Layer.Paths.to_canva
(module Path.Fixed)
- (p, path.Outline.back)
- context
- state.rendering );
+ (p, path.Outline.back) context state.rendering);
(* Draw the selected path, and operate the modifications directly as a preview *)
let () =
match state.mode with
- | Selection t ->
+ | Selection t -> (
Cd2d.set_stroke_style context (Cd2d.color white);
- ( match (pos_v2, Selection.find_selection t state.paths) with
+ match (pos_v2, Selection.find_selection t state.paths) with
(* The selected element does not exist, just do nothing *)
| _, None -> ()
(* There is no click on the canva, print the line *)
@@ -269,17 +263,15 @@ let on_change canva mouse_position timer state =
Layer.Paths.to_canva
(module Path.Fixed)
(outline.path, outline.back)
- context
- `Line
+ context `Line
(* The user is modifiying the path *)
| Some pos_v2, Some (Path outline) ->
(* Translate the path *)
let delta = Gg.V2.(pos_v2 - state.State.mouse_down_position) in
let path =
Path.Fixed.map outline.Outline.path (fun pt ->
- Path.Point.get_coord pt
- |> Gg.V2.add delta
- |> Path.Point.copy pt )
+ Path.Point.get_coord pt |> Gg.V2.add delta
+ |> Path.Point.copy pt)
in
Layer.Paths.to_canva (module Path.Fixed) (path, path) context `Line
(* The user is modifiying the point *)
@@ -298,19 +290,16 @@ let on_change canva mouse_position timer state =
Layer.Paths.to_canva
(module Path.Fixed)
(outline.path, outline.back)
- context
- `Line;
- draw_point point context )
+ context `Line;
+ draw_point point context)
| _ -> ()
in
()
-
let spawn_worker () =
try Ok (Brr_webworkers.Worker.create (Jstr.v "worker.js")) with
| Jv.Error e -> Error e
-
let page_main id =
let timer, tick = Elements.Timer.create () in
@@ -324,151 +313,154 @@ let page_main id =
match Jv.is_none id with
| true -> Console.(error [ str "No element with id '%s' found"; id ])
- | false ->
- ( match spawn_worker () with
- | Error e ->
- El.set_children
- (Jv.Id.of_jv id)
- [ El.p El.[ txt (Jv.Error.message e) ] ]
- | Ok worker ->
- let worker_event, worker_send = E.create () in
-
- let delete_event =
- E.map
- (fun () ->
- let module Delete = Script_event.Delete in
- State.dispatch (module Delete) Delete.{ worker } )
- parameters.delete
- and export_event =
- E.map
- (fun () ->
- let module Export = Script_event.Export in
- State.dispatch (module Export) () )
- parameters.export
- and angle_event =
- S.changes parameters.angle
- |> E.map (fun value ->
- let module Property = Script_event.Property in
- State.dispatch
- (module Property)
- Property.{ value; worker; prop = `Angle } )
- and width_event =
- S.changes parameters.width
- |> E.map (fun value ->
- let module Property = Script_event.Property in
- State.dispatch
- (module Property)
- Property.{ value; worker; prop = `Width } )
- and worker_event =
- Note.E.filter_map
- (function
- | `Other t ->
- Console.(log [ t ]);
- None
- | `Complete outline ->
- let module Complete_path = Script_event.Complete_path in
- Some (State.dispatch (module Complete_path) outline) )
- worker_event
- in
-
- let my_host = Uri.host @@ Window.location @@ G.window in
- ( if Hashtbl.hash my_host = Blog.Hash_host.expected_host
- then
- let target = Brr_webworkers.Worker.as_target worker in
- Ev.listen
- Brr_io.Message.Ev.message
- (fun t -> Ev.as_type t |> Brr_io.Message.Ev.data |> worker_send)
- target );
-
- (* Add the events to the canva :
-
- - The mouse position is a signal used for both the update and the
- canva refresh
-
- - Get also the click event for starting to draw
- *)
- let canva_events, mouse_position, canva = canva (Jv.Id.of_jv id) in
- let canva_events =
- Note.E.map
- (function
- | `MouseDown c ->
- let module MouseDown = Script_event.Mouse_down in
- State.dispatch
- (module MouseDown)
- MouseDown.{ position = c; timer }
- | `Out c ->
- let module Click = Script_event.Click in
- State.dispatch
- (module Click)
- Click.{ point = c; worker; timer } )
- canva_events
- in
-
- let tick_event =
- S.sample_filter mouse_position ~on:tick (fun pos f ->
- let module Tick = Script_event.Tick in
- Option.map (fun p -> State.dispatch (module Tick) (f, p)) pos )
- in
-
- (* The first evaluation is the state. Which is the result of all the
- successives events to the initial state *)
- let state =
- State.run
- State.init
- (E.select
- [ worker_event
- ; canva_events
- ; tick_event
- ; angle_event
- ; width_event
- ; delete_event
- ; export_event
- ; parameters.rendering
- ] )
- in
-
- (* The seconde evaluation is the canva refresh, which only occurs when
- the mouse is updated, or on delete events *)
- let _ =
- E.select
- [ E.map (fun _ -> ()) (S.changes mouse_position)
- ; E.map (fun _ -> ()) parameters.rendering
- ; E.map (fun _ -> ()) worker_event
- ; parameters.delete
- ]
- |> fun ev ->
- E.log ev (fun _ ->
- on_change canva mouse_position timer (S.value state) )
- |> Option.iter Logr.hold
- in
-
- (* Ajust the angle slide according to the state *)
- let angle_signal = S.map (fun s -> Jstr.of_float s.State.angle) state in
- let _ =
- Elr.def_prop Elements.Prop.value angle_signal angle_element.input
- and _ =
- Elr.def_children
- angle_element.legend
- (S.map
- (fun v -> [ El.txt' "Angle : "; El.txt v; El.txt' "°" ])
- angle_signal )
- in
-
- let width_signal = S.map (fun s -> Jstr.of_float s.State.width) state in
- let _ = Elr.def_prop Elements.Prop.value width_signal width_slider.input
- and _ =
- Elr.def_children
- width_slider.legend
- (S.map (fun v -> [ El.txt' "Width : "; El.txt v ]) width_signal)
- in
-
- (* Draw the canva for first time *)
- on_change canva mouse_position timer State.init;
-
- (* Hold the state *)
- let _ = Logr.hold (S.log state (fun _ -> ())) in
- () )
-
+ | false -> (
+ match spawn_worker () with
+ | Error e ->
+ El.set_children (Jv.Id.of_jv id)
+ [ El.p El.[ txt (Jv.Error.message e) ] ]
+ | Ok worker ->
+ let worker_event, worker_send = E.create () in
+
+ let delete_event =
+ E.map
+ (fun () ->
+ let module Delete = Script_event.Delete in
+ State.dispatch (module Delete) Delete.{ worker })
+ parameters.delete
+ and export_event =
+ E.map
+ (fun () ->
+ let module Export = Script_event.Export in
+ State.dispatch (module Export) ())
+ parameters.export
+ and angle_event =
+ S.changes parameters.angle
+ |> E.map (fun value ->
+ let module Property = Script_event.Property in
+ State.dispatch
+ (module Property)
+ Property.{ value; worker; prop = `Angle })
+ and width_event =
+ S.changes parameters.width
+ |> E.map (fun value ->
+ let module Property = Script_event.Property in
+ State.dispatch
+ (module Property)
+ Property.{ value; worker; prop = `Width })
+ and worker_event =
+ Note.E.filter_map
+ (function
+ | `Other t ->
+ Console.(log [ t ]);
+ None
+ | `Complete outline ->
+ let module Complete_path = Script_event.Complete_path in
+ Some (State.dispatch (module Complete_path) outline))
+ worker_event
+ in
+
+ let my_host = Uri.host @@ Window.location @@ G.window in
+ (if Hashtbl.hash my_host = Blog.Hash_host.expected_host then
+ let target = Brr_webworkers.Worker.as_target worker in
+ let _ =
+ Ev.listen Brr_io.Message.Ev.message
+ (fun t -> Ev.as_type t |> Brr_io.Message.Ev.data |> worker_send)
+ target
+ in
+ ());
+
+ (* Add the events to the canva :
+
+ - The mouse position is a signal used for both the update and the
+ canva refresh
+
+ - Get also the click event for starting to draw
+ *)
+ let canva_events, mouse_position, canva = canva (Jv.Id.of_jv id) in
+ let canva_events =
+ Note.E.map
+ (function
+ | `MouseDown c ->
+ let module MouseDown = Script_event.Mouse_down in
+ State.dispatch
+ (module MouseDown)
+ MouseDown.{ position = c; timer }
+ | `Out c ->
+ let module Click = Script_event.Click in
+ State.dispatch
+ (module Click)
+ Click.{ point = c; worker; timer })
+ canva_events
+ in
+
+ let tick_event =
+ S.sample_filter mouse_position ~on:tick (fun pos f ->
+ let module Tick = Script_event.Tick in
+ Option.map (fun p -> State.dispatch (module Tick) (f, p)) pos)
+ in
+
+ (* The first evaluation is the state. Which is the result of all the
+ successives events to the initial state *)
+ let state =
+ State.run State.init
+ (E.select
+ [
+ worker_event
+ ; canva_events
+ ; tick_event
+ ; angle_event
+ ; width_event
+ ; delete_event
+ ; export_event
+ ; parameters.rendering
+ ])
+ in
+
+ (* The seconde evaluation is the canva refresh, which only occurs when
+ the mouse is updated, or on delete events *)
+ let _ =
+ E.select
+ [
+ E.map (fun _ -> ()) (S.changes mouse_position)
+ ; E.map (fun _ -> ()) parameters.rendering
+ ; E.map (fun _ -> ()) worker_event
+ ; parameters.delete
+ ]
+ |> fun ev ->
+ E.log ev (fun _ ->
+ on_change canva mouse_position timer (S.value state))
+ |> Option.iter Logr.hold
+ in
+
+ (* Ajust the angle slide according to the state *)
+ let angle_signal =
+ S.map (fun s -> Jstr.of_float s.State.angle) state
+ in
+ let _ =
+ Elr.def_prop Elements.Prop.value angle_signal angle_element.input
+ and _ =
+ Elr.def_children angle_element.legend
+ (S.map
+ (fun v -> [ El.txt' "Angle : "; El.txt v; El.txt' "°" ])
+ angle_signal)
+ in
+
+ let width_signal =
+ S.map (fun s -> Jstr.of_float s.State.width) state
+ in
+ let _ =
+ Elr.def_prop Elements.Prop.value width_signal width_slider.input
+ and _ =
+ Elr.def_children width_slider.legend
+ (S.map (fun v -> [ El.txt' "Width : "; El.txt v ]) width_signal)
+ in
+
+ (* Draw the canva for first time *)
+ on_change canva mouse_position timer State.init;
+
+ (* Hold the state *)
+ let _ = Logr.hold (S.log state (fun _ -> ())) in
+ ())
let () =
let open Jv in