open StdLabels open Note open Brr module Timer = Events.Timer module Path_Builder = Path.Builder.Make(Path.Point) module Path_Printer = Path_Builder.Draw(Path.WireFramePrinter) module Fixed_Printer = Path_Builder.DrawFixed(Path.WireFramePrinter) type mode = | Edit | Out type current = Path_Builder.t type state = { mode : mode ; paths : Path_Builder.fixedPath list ; current : current ; timer : Timer.t } (** Events *) type canva_events = [ `Click of float * float | `Out of float * float ] type events = [ canva_events | `Point of float * (float * float) ] type canva_signal = Path.Point.t module Mouse = Brr_note_kit.Mouse (** Create the element in the page, and the event handler *) let canva : Brr.El.t -> [> canva_events] Note.E.t * (float * float) option Note.S.t * Brr_canvas.Canvas.t = fun element -> let module C = Brr_canvas.Canvas in let c = C.of_el element in (* Mouse events *) let mouse = Brr_note_kit.Mouse.on_el ~normalize:false (fun x y -> (x, y)) element in let click = Brr_note_kit.Mouse.left_down mouse |> E.map (fun c -> `Click c) in let up = Brr_note_kit.Mouse.left_up mouse |> E.map (fun c -> `Out c) in let position = Mouse.pos mouse in let pos = S.l2 (fun b pos -> if b then Some pos else None ) (Mouse.left mouse) position in E.select [click; up], pos, c let insert_or_replace ((x, y) as p) path = let point = Path.Point.create x y in match Path_Builder.peek path with | None -> Path_Builder.add_point point path | Some p1 -> let open Gg.V2 in let p1' = Path.Point.get_coord p1 in let dist = (norm (p1' - (of_tuple p))) in if dist < 0.05 then ( path ) else ( Path_Builder.add_point point path ) let do_action : events -> state -> state = fun event state -> match event, state.mode with | `Point (_delay, (x, y)), Edit -> (* Add the point in the list *) let current= Path_Builder.add_point (Path.Point.create x y) state.current in { state with current } | `Click _, Out -> Timer.start state.timer 0.3; { state with mode = Edit } | `Out point, Edit -> Timer.stop state.timer; let current = insert_or_replace point state.current in (* let beziers = Path.Draw.to_path @@ Path_Builder.get current in let paths = beziers::state.paths *) let paths = Path_Builder.to_fixed current::state.paths and current = Path_Builder.empty in { state with mode = Out; paths; current } | _ -> state let backgroundColor = Jstr.v "#2e3440" let white = Jstr.v "#eceff4" let green = Jstr.v "#a3be8c" let nord8 = Jstr.v "#81a1c1" let on_change canva mouse_position state = let module Path' = Path in let open Brr_canvas.C2d in let w, h = Brr_canvas.Canvas.(float_of_int @@ w canva, float_of_int @@ h canva) in let _area = Gg.V2.v w h in let context = create canva in set_fill_style context (color backgroundColor); fill_rect context ~x:0.0 ~y:0.0 ~w ~h; set_stroke_style context (color white); set_fill_style context (color white); (* If we are in edit mode, we add a point under the cursor. Otherwise, we would only display the previous registered point, which can be far away in the past, and would give to the user a sensation of lag. *) let pos = S.rough_value mouse_position in let current = match state.mode, pos with | Edit, Some (x, y) -> Path_Builder.add_point (Path'.Point.create x y) state.current | _ -> state.current in let path = Path'.WireFramePrinter.get @@ Path_Printer.draw current in stroke context path; List.iter state.paths ~f:(fun path -> let path = Path'.WireFramePrinter.get @@ Fixed_Printer.draw path in stroke context path ); () let page_main id = let timer, tick = Timer.create () in let init = { paths = [] ; current = Path_Builder.empty ; mode = Out ; timer } in (*begin match Document.find_el_by_id G.document id with*) begin match (Jv.is_none id) with | true -> Console.(error [str "No element with id '%s' found"; id]) | false -> let canva_events, mouse_position, canva = canva (Jv.Id.of_jv id) in let tick_event = S.sample_filter mouse_position ~on:tick (fun pos f -> Option.map (fun p -> `Point (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 = E.select [canva_events; tick_event] |> E.map do_action |> Note.S.accum init in (* The seconde evaluation is the canva refresh, which only occurs when the mouse is updated *) let v = E.map (fun _ -> state) (S.changes mouse_position) |> E.map (fun x -> on_change canva mouse_position (S.value x) ) |> fun ev -> E.log ev (fun _ -> ()) in (* Draw the canva for first time *) on_change canva mouse_position init; let _ = Logr.hold (S.log state (fun _ -> ())) in let _ = match v with | None -> () | Some log -> Logr.hold log in () end let () = if Brr_webworkers.Worker.ami () then () else let open Jv in let drawer = obj [| "run", (repr page_main) |] in set global "drawer" drawer