-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathlambda_term.mlx
180 lines (149 loc) · 4.99 KB
/
lambda_term.mlx
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
(** This is an implementation of a reconciler for the Lambda_term widget
library: https://github.com/ocaml-community/lambda-term
This is just an example but you could use this to create interesting CLI
apps, with a react-like functional API! *)
open Lwt
open LTerm_widget
open Brisk_reconciler
(* Step 1: Define the reconciler template *)
type hostElement =
| Label of LTerm_widget.label
| Button of LTerm_widget.button
| Container of LTerm_widget.box
type node = hostElement
let insertNode ~(parent : node) ~(child : node) ~position:_ =
(match parent, child with
| Container box, Label child -> box#add child
| Container box, Button child -> box#add child
| Container box, Container child -> box#add child
| _ -> ());
parent
let deleteNode ~(parent : node) ~(child : node) ~position:_ =
(match parent, child with
| Container box, Label child -> box#remove child
| Container box, Button child -> box#remove child
| Container box, Container child -> box#remove child
| _ -> ());
parent
let moveNode ~parent ~child:_ ~from:_ ~to_:_ = parent
let onStale = Remote_action.create ();;
addStaleTreeHandler (fun () -> Remote_action.send ~action:() onStale)
(* Step 2: Define some native components (aka primitives) *)
let%nativeComponent hbox ~children () hooks =
( { make =
(fun () ->
let node = new LTerm_widget.hbox in
Container node)
; configureInstance = (fun ~isFirstRender:_ node -> node)
; insertNode
; deleteNode
; moveNode
; children }
, hooks )
let%nativeComponent vbox ~children () hooks =
( { make =
(fun () ->
let node = new LTerm_widget.vbox in
Container node)
; configureInstance = (fun ~isFirstRender:_ node -> node)
; insertNode
; deleteNode
; moveNode
; children }
, hooks )
let%nativeComponent label ~text () hooks =
( { make =
(fun () ->
let node = new LTerm_widget.label text in
Label node)
; configureInstance =
(fun ~isFirstRender:_ node ->
(match node with Label n -> n#set_text text | _ -> ());
node)
; insertNode
; deleteNode
; moveNode
; children = empty }
, hooks )
let%nativeComponent button ~text ~onClick () hooks =
( { make =
(fun () ->
let button = new button text in
button#on_click onClick; Button button)
; configureInstance =
(fun ~isFirstRender:_ node ->
(match node with Button n -> n#set_label text | _ -> ());
node)
; insertNode
; deleteNode
; moveNode
; children = empty }
, hooks )
(* Step 3: Use the reconciler + native elements to create something great! *)
type action = Increment | Decrement
let reducer action state =
match action with Increment -> state + 1 | Decrement -> state - 1
(* CounterButtons
This shows how you can use reducers with a callback from a primitive. *)
let%component counterButtons () =
let%hook count, dispatch = Hooks.reducer ~initialState:0 reducer in
<hbox>
<button text="Decrement" onClick=(fun () -> dispatch Decrement) />
<label text=("Counter: " ^ string_of_int count) />
<button text="Increment" onClick=(fun () -> dispatch Increment) />
<label text="???" />
</hbox>
(* Clock
Custom clock component to show the time. Demonstrates use of
`useEffect` and `setState` together. *)
let%component clock () =
let%hook time, setTime = Hooks.state (Unix.time ()) in
let%hook () =
Hooks.effect Hooks.Effect.Always (fun () ->
let evt =
Lwt_engine.on_timer 1.0 true (fun _ -> setTime (fun _ -> Unix.time ()))
in
Some (fun () -> Lwt_engine.stop_event evt))
in
let formated_time =
let tm = Unix.localtime time in
Printf.sprintf "%02d:%02d:%02d" tm.tm_hour tm.tm_min tm.tm_sec
in
<label text=("Time: " ^ formated_time) />
(* Step 4: Make the first render *)
let main () =
let waiter, wakener = wait () in
let quit () = wakeup wakener () in
(* Let's finally put our UI to use! *)
let render () =
<vbox>
<label text="Hello World!" />
<clock />
<counterButtons />
<button onClick=quit text="Quit" />
</vbox>
in
(* Create a container for our UI *)
let body = new vbox in
let root =
RenderedElement.{node = Container body; insertNode; deleteNode; moveNode}
in
let rendered =
let rendered = RenderedElement.render root (render ()) in
RenderedElement.executeHostViewUpdates rendered |> ignore;
ref (RenderedElement.executePendingEffects rendered)
in
let _unsubscribe =
Remote_action.subscribe
~handler:(fun () ->
let nextElement = RenderedElement.flushPendingUpdates !rendered in
RenderedElement.executeHostViewUpdates nextElement |> ignore;
rendered := RenderedElement.executePendingEffects nextElement)
onStale
in
Lazy.force LTerm.stdout >>= fun term ->
LTerm.enable_mouse term >>= fun () ->
Lwt.finalize
(fun () -> run term body waiter)
(fun () -> LTerm.disable_mouse term)
let () = Lwt_main.run (main ())