···1212 ; ui : bool -> Ui.t
1313}
14141515-(**Makes a ui element selectable.
1616-1717- Takes [ui] and returns a function that appends '>' to the start when given [true] and ' ' when false
1818-1919- Used in conjuction with [selection_list_custom]*)
2020-let selectable_item ui is_focused =
2121- let height = Ui.layout_height ui in
2222- let prefix =
2323- if is_focused
2424- then I.char A.(st bold++ bg (blue)) '>' 1 height
2525- else I.char A.empty ' ' 1 height
2626- in
2727- Ui.hcat [ prefix |> Ui.atom; ui ]
2828-;;
2929-3030-3131-(** Selection list that allows for custom handling of keyboard events.
3232- Scrolls when the selection reaches the lower third
3333- Only handles up and down keyboard events. Use [~custom_handler] to do handle confirming your selection and such *)
3434-let selection_list_custom
3535- ?(focus = Focus.make ())
3636- ?(on_selection_change = fun _ -> ())
3737- ~custom_handler
3838- (items : 'a selectable_item list Lwd.t)
3939- =
4040- let selected_var = Lwd.var 0 in
4141- let selected_position = Lwd.var (0, 0) in
4242- (*handle selections*)
4343- let render_items =
4444- let$ focus = focus |> Focus.status
4545- and$ items, selected =
4646- (* This doesn't depend on changes in focus so we run it with just the items and selected*)
4747- let$ items = items
4848- and$ selected = Lwd.get selected_var in
4949- (* First ensure if our list has gotten shorter we haven't selected off the list*)
5050- (* We do this here to ensure that the selected var is updated before we render to avoid double rendering*)
5151- let max_selected = Int.max 0 (List.length items - 1) in
5252- if Int.min selected max_selected <> selected then selected_var $= max_selected;
5353- let selected = Lwd.peek selected_var in
5454- List.nth_opt items selected |> Option.iter (fun x -> on_selection_change x.data);
5555- items, selected
5656- in
5757- (* Ui.vcat can be a little weird when the *)
5858- items
5959- |> List.mapi (fun i x ->
6060- if selected == i
6161- then
6262- x.ui true
6363- |> Ui.transient_sensor (fun ~x ~y ~w:_ ~h:_ () ->
6464- if (x, y) <> Lwd.peek selected_position then selected_position $= (x, y))
6565- else x.ui false)
6666- |> Ui.vcat
6767- |> Ui.keyboard_area ~focus (function
6868- | `Arrow `Up, [] ->
6969- let selected = max (Lwd.peek selected_var - 1) 0 in
7070- selected_var $= selected;
7171- `Handled
7272- | `Arrow `Down, [] ->
7373- let selected =
7474- Int.max (min (Lwd.peek selected_var + 1) ((items |> List.length) - 1)) 0
7575- in
7676- selected_var $= selected;
7777- `Handled
7878- | a ->
7979- custom_handler items selected_var a)
8080- in
8181- let rendered_size_var = Lwd.var (0, 0) in
8282- (*Handle scrolling*)
8383- let scrollitems =
8484- let size_var = Lwd.var (0, 0) in
8585- let shift_amount =
8686- let$ selected = Lwd.get selected_var
8787- and$ size = Lwd.get size_var
8888- and$ length = items |>$ List.length
8989- and$ ren_size = Lwd.get rendered_size_var in
9090- (*portion of the total size of the element that is rendered*)
9191- let size_ratio =
9292- (ren_size |> snd |> float_of_int) /. (size |> snd |> float_of_int)
9393- in
9494- (*Tries to ensure that we start scrolling the list when we've selected about a third of the way down (using 3.0 causes weird jumping, so i use just less than )*)
9595- let offset = size_ratio *. ((length |> float_of_int) /. 2.9) in
9696- (*portion of the list that is behind the selection*)
9797- let list_ratio =
9898- ((selected |> float_of_int) +. offset) /. (length |> float_of_int)
9999- in
100100- (*if our position is further down the list than the portion that is shown we will shift by that amoumt *)
101101- Float.max (list_ratio -. size_ratio) 0.0 *. (size |> snd |> float_of_int)
102102- |> int_of_float
103103- in
104104- let$ items = render_items
105105- and$ shift_amount = shift_amount in
106106- items
107107- |> Ui.shift_area 0 shift_amount
108108- |> Ui.resize ~sh:1
109109- |> simpleSizeSensor ~size_var
110110- |> Ui.resize ~w:3 ~sw:1 ~h:0
111111- |> simpleSizeSensor ~size_var:rendered_size_var
112112- in
113113- scrollitems
114114-;;
115115-11615type 'a maybeSelectable =
11716 | Selectable of 'a selectable_item
11817 | Filler of Ui.t
···12625 =
12726 (*
12827 The rough overview is:
129129- 1. Make a lookup list that has the indexes of all the selectable items within the overall list, we will be selecting from those
2828+ 1. Make a new list that only contains our selectable items
13029 2. Render the items, making sure to tell the selected one to render as selected.
13130 3. Calculate how much we should scroll by.
13231 4. offset by the scroll amount, apply size sensors and output final ui
13332 *)
13433 let selected_var = Lwd.var 0 in
13534 let selected_position = Lwd.var (0, 0) in
136136- let selectable_item_indexes =
3535+ let selectable_items =
13736 let$ items = items in
138138- let lut = Array.make (Array.length items) 0 in
3737+ let selectable_items = Array.make (Array.length items) (Obj.magic ()) in
13938 let final_len =
14039 items
14140 |> Base.Array.foldi ~init:0 ~f:(fun i selectable_count item ->
14241 match item with
143143- | Selectable _ ->
144144- Array.set lut selectable_count i;
4242+ | Selectable item ->
4343+ Array.set selectable_items selectable_count (i, item);
14544 selectable_count + 1
14645 | Filler _ ->
14746 selectable_count)
14847 in
149149- Array.sub lut 0 final_len
4848+ Array.sub selectable_items 0 final_len
15049 in
15150 (*handle selections*)
15251 let render_items =
···15453 and$ items, selected, selectable_item_indexes =
15554 (* This doesn't depend on changes in focus but it should update whenever there are new items or a selection change*)
15655 let$ items = items
157157- and$ selectable_item_indexes = selectable_item_indexes
5656+ and$ selectable_items = selectable_items
15857 and$ selected = Lwd.get selected_var in
15958 (* First ensure if our list has gotten shorter we haven't selected off the list*)
16059 (* We do this here to ensure that the selected var is updated before we render to avoid double rendering*)
161161- let max_selected = Int.max 0 (Array.length selectable_item_indexes - 1) in
6060+ let max_selected = Int.max 0 (Array.length selectable_items - 1) in
16261 if Int.min selected max_selected <> selected then selected_var $= max_selected;
16362 let selected = Lwd.peek selected_var in
164164- (* We lookup the index of the selected item in the list of items, remeber our list isn't entirely selectable items*)
165165- if Array.length selectable_item_indexes > 0
6363+ if Array.length selectable_items > 0
16664 then (
167167- let item_idx = selectable_item_indexes.(selected) in
168168- items.(item_idx) |> fun x ->
169169- (match x with
170170- | Selectable s ->
171171- on_selection_change s.data
172172- | Filler _ ->
173173- failwith "selected an item that wasn't selectable. This shouldn't be possible");
174174- items, item_idx, selectable_item_indexes)
175175- else items, 0, selectable_item_indexes
6565+ let item_idx, item = selectable_items.(selected) in
6666+ on_selection_change item.data;
6767+ items, item_idx, selectable_items)
6868+ else items, 0, selectable_items
17669 in
17770 (* Ui.vcat can be a little weird when the *)
17871 items
···214107 let shift_amount =
215108 (*get the actual idx not just the selection number*)
216109 let$ selected_idx =
217217- Lwd.map2
218218- (Lwd.get selected_var)
219219- selectable_item_indexes
220220- ~f:(fun selected indexes ->
221221- if Array.length indexes > 0 then indexes.(selected) else 0)
110110+ Lwd.map2 (Lwd.get selected_var) selectable_items ~f:(fun selected selectable ->
111111+ if Array.length selectable > selected then selectable.(selected) |> fst else 0)
222112 and$ size = Lwd.get size_var
223113 and$ length = items |>$ Array.length
224114 and$ ren_size = Lwd.get rendered_size_var in
···246136 |> simpleSizeSensor ~size_var:rendered_size_var
247137 in
248138 scrollitems
139139+;;
140140+141141+(**Makes a ui element selectable.
142142+143143+ Takes [ui] and returns a function that appends '>' to the start when given [true] and ' ' when false
144144+145145+ Used in conjuction with [selection_list_custom]*)
146146+let selectable_item ui is_focused =
147147+ let height = Ui.layout_height ui in
148148+ let prefix =
149149+ if is_focused then I.char A.(bg blue) '>' 1 height else I.char A.empty ' ' 1 height
150150+ in
151151+ Ui.hcat [ prefix |> Ui.atom; ui ]
152152+;;
153153+154154+(** Selection list that allows for custom handling of keyboard events.
155155+ Scrolls when the selection reaches the lower third
156156+ Only handles up and down keyboard events. Use [~custom_handler] to do handle confirming your selection and such *)
157157+let selection_list_custom
158158+ ?(focus = Focus.make ())
159159+ ?(on_selection_change = fun _ -> ())
160160+ ~custom_handler
161161+ (items : 'a selectable_item list Lwd.t)
162162+ =
163163+ selection_list_exclusions
164164+ ~focus
165165+ ~on_selection_change
166166+ ~custom_handler
167167+ ( items |>$ fun items ->
168168+ let selectable_items = Array.make (List.length items) (Obj.magic ()) in
169169+ items |> List.iteri (fun i x -> Array.set selectable_items i (Selectable x));
170170+ selectable_items )
249171;;
250172251173(** A filterable selectable list.
+2-2
jj_tui/lib/widgets/widgets.ml
···331331 if c >= '0' && c <= '9' then Some (int_of_char c - int_of_char '0') else None
332332;;
333333334334-(** Tab view, where exactly one element of [l] is shown at a time. *)
335335-let mouse_tabs (tabs : (string * (unit -> Ui.t Lwd.t)) list) : Ui.t Lwd.t =
334334+(** Tab view, where exactly one element of [l] is shown at a time. Naviagted using number keys on the keyboard *)
335335+let keyboard_tabs (tabs : (string * (unit -> Ui.t Lwd.t)) list) : Ui.t Lwd.t =
336336 match tabs with
337337 | [] ->
338338 Lwd.return Ui.empty