Unicorn_jsooUnicorn provides a small algebra of seven combinators to define GUI applications:
empty for when you don't want anything drawna & b when you want to display two or more widgetsiso i w to adapt an existing widget w to a different type (similar to "map")on lens w when you want to edit a product/tuple/recordinto prism w for pattern matching over sum typesstateful s w to hide the internal state of the widget wdynamic for complete control over the widget's position, movement, creation, history, etc.These combinators are the glue to assemble complex applications out of smaller widgets. The jsoo (js_of_ocaml) backend additionnally provides the primitive widgets and HTML support.
val run : ?id:string -> 'a t -> 'a -> unitrun w x renders the widget w with the initial state x, then proceed to update it in reaction to user events. It's the main loop that drives the GUI application, so you probably want to call it only once.
?id is specified, the widget renders in the HTML node with the corresponding id attribute. Otherwise, the widget is appended to the HTML body of the webpage.val empty : 'a tThe empty widget, invisible and ineffective.
a & b displays both widgets a and b side by side.
∀ w. empty & w == w == w & empty∀ a b c. a & (b & c) == a & b & c == (a & b) & cb & a displays the widgets in a different order.The polymorphic 'a in an 'a t widget is invariant: It is consummed when rendering, but also produced in reaction to user events. As such, we always need pair of functions to "map" over it:
iso i w maps the isomorphism i over the widget w.
∀ i. iso i empty == empty and ∀ w. map Iso.id w == w∀ i j w. iso i (iso j w) == map (Iso.compose i j) w∀ i a b. iso i a & iso i b == iso i (a & b)val on : ( 'a, 'b ) Optic.lens -> 'b t -> 'a ton lens w zooms on a sub-field of the state 'a with the lens and renders/updates it with the widget w.
∀ lens. on lens empty == empty and ∀ w. on Lens.id w == w∀ l1 l2 w. on l1 (on l2 w) == on (Lens.compose l1 l2) w∀ lens a b. on lens a & on lens b == on lens (a & b)val into : ( 'a, 'b ) Optic.prism -> 'b t -> 'a tinto prism w pattern matches the state 'a with the prism, to render the widget w. If the prism fails, then it renders empty.
∀ prism. into prism empty == empty and ∀ w. into Prism.id w == w∀ p1 p2 w. into p1 (on p2 w) == into (Prism.compose p1 p2) w∀ prism a b. into prism a & into prism b == into prism (a & b)val case : ( 'a, 'b ) Optic.prism -> 'b t -> 'a tcase prism w is the same as into prism w, but the internal state of the widget w is reset when the prism is not satisfied.
∀ p w. into p w == stateful w (case Prism.(product id p) dynamic)stateful s w encapsulates the internal state of the widget w with an initial value s.
∀ s. stateful s empty == empty and ∀ s w. stateful s (on Lens.snd w) == w∀ s1 s2 w. stateful s1 (stateful s2 w) == stateful (s1, s2) (iso Iso.assoc w)∀ s a b. stateful s a & b == stateful s (a & on Lens.snd b) and ∀ s a b. a & stateful s b == stateful s (on Lens.snd a & b)∀ i s w. iso i (stateful s w) = stateful s (iso (Iso.product id i) w) and same for lenses and prisms.However, internal state is not shared: stateful s a & stateful s b =/= stateful s (a & b).
dynamic renders and updates a dynamic widget coming from the state.
∀ w. w == stateful w dynamicThe following are defined from the previous combinators.
cond p w renders the widget w when the predicate p is satisfied.
∀ p. cond p empty == empty and ∀ w. cond (fun _ -> true) w == w and ∀ w. cond (fun _ -> false) w == empty∀ p q w. cond p (cond q w) == cond (fun x -> p x && q x) w∀ p a b. cond p (a & b) == cond p a & cond p bifte p if_true if_false renders the widget if_true when p is satisfied, otherwise if_false.
Same as cond, but the internal state is lost when the predicate is unsatisfied (see case).
stateful_by f w is the same as stateful, but the initial value is computed from the current state.
As OCaml is a strict language, recursive definitions need special support as this would fail to terminate:
let rec list w = into Prism.cons (w <*> list w)and one should rather delay slightly the recursion:
let rec list w = into Prism.cons (w <*> apply list w)of_lazy w delays the lazy widget w, such that of_lazy (lazy w) == w
fix f uses of_lazy to simplify the definition of recursive traversals.
val str : string -> 'a tstr s displays the constant string s as HTML.
val text : ( 'a -> string ) -> 'a ttext f renders a dynamic string using the function f on the current state.
val checkbox : bool tcheckbox is a primitive widget to render and edit a boolean.
val input_int : int tinput_int is a primitive widget to render and edit an integer as a text input.
val input_string : string tinput_string is a primitive widget to render and edit a single-line string.
button w is an HTML button using the widget w for its contents. The reaction when the button is clicked can be specified with E.click:
button (str "Click" & E.click (fun old_state -> new_state))module H : sig ... endHTML elements and nodes, like div, span, etc.
module A : sig ... endHTML attributes and properties.
module E : sig ... endHTML events.