terminal user interface to jujutsu. Focused on speed and clarity
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

reduce selection list duplication

reduce selection list duplication


reduce selection list duplication

+49 -127
+47 -125
jj_tui/lib/widgets/selection_list.ml
··· 12 12 ; ui : bool -> Ui.t 13 13 } 14 14 15 - (**Makes a ui element selectable. 16 - 17 - Takes [ui] and returns a function that appends '>' to the start when given [true] and ' ' when false 18 - 19 - Used in conjuction with [selection_list_custom]*) 20 - let selectable_item ui is_focused = 21 - let height = Ui.layout_height ui in 22 - let prefix = 23 - if is_focused 24 - then I.char A.(st bold++ bg (blue)) '>' 1 height 25 - else I.char A.empty ' ' 1 height 26 - in 27 - Ui.hcat [ prefix |> Ui.atom; ui ] 28 - ;; 29 - 30 - 31 - (** Selection list that allows for custom handling of keyboard events. 32 - Scrolls when the selection reaches the lower third 33 - Only handles up and down keyboard events. Use [~custom_handler] to do handle confirming your selection and such *) 34 - let selection_list_custom 35 - ?(focus = Focus.make ()) 36 - ?(on_selection_change = fun _ -> ()) 37 - ~custom_handler 38 - (items : 'a selectable_item list Lwd.t) 39 - = 40 - let selected_var = Lwd.var 0 in 41 - let selected_position = Lwd.var (0, 0) in 42 - (*handle selections*) 43 - let render_items = 44 - let$ focus = focus |> Focus.status 45 - and$ items, selected = 46 - (* This doesn't depend on changes in focus so we run it with just the items and selected*) 47 - let$ items = items 48 - and$ selected = Lwd.get selected_var in 49 - (* First ensure if our list has gotten shorter we haven't selected off the list*) 50 - (* We do this here to ensure that the selected var is updated before we render to avoid double rendering*) 51 - let max_selected = Int.max 0 (List.length items - 1) in 52 - if Int.min selected max_selected <> selected then selected_var $= max_selected; 53 - let selected = Lwd.peek selected_var in 54 - List.nth_opt items selected |> Option.iter (fun x -> on_selection_change x.data); 55 - items, selected 56 - in 57 - (* Ui.vcat can be a little weird when the *) 58 - items 59 - |> List.mapi (fun i x -> 60 - if selected == i 61 - then 62 - x.ui true 63 - |> Ui.transient_sensor (fun ~x ~y ~w:_ ~h:_ () -> 64 - if (x, y) <> Lwd.peek selected_position then selected_position $= (x, y)) 65 - else x.ui false) 66 - |> Ui.vcat 67 - |> Ui.keyboard_area ~focus (function 68 - | `Arrow `Up, [] -> 69 - let selected = max (Lwd.peek selected_var - 1) 0 in 70 - selected_var $= selected; 71 - `Handled 72 - | `Arrow `Down, [] -> 73 - let selected = 74 - Int.max (min (Lwd.peek selected_var + 1) ((items |> List.length) - 1)) 0 75 - in 76 - selected_var $= selected; 77 - `Handled 78 - | a -> 79 - custom_handler items selected_var a) 80 - in 81 - let rendered_size_var = Lwd.var (0, 0) in 82 - (*Handle scrolling*) 83 - let scrollitems = 84 - let size_var = Lwd.var (0, 0) in 85 - let shift_amount = 86 - let$ selected = Lwd.get selected_var 87 - and$ size = Lwd.get size_var 88 - and$ length = items |>$ List.length 89 - and$ ren_size = Lwd.get rendered_size_var in 90 - (*portion of the total size of the element that is rendered*) 91 - let size_ratio = 92 - (ren_size |> snd |> float_of_int) /. (size |> snd |> float_of_int) 93 - in 94 - (*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 )*) 95 - let offset = size_ratio *. ((length |> float_of_int) /. 2.9) in 96 - (*portion of the list that is behind the selection*) 97 - let list_ratio = 98 - ((selected |> float_of_int) +. offset) /. (length |> float_of_int) 99 - in 100 - (*if our position is further down the list than the portion that is shown we will shift by that amoumt *) 101 - Float.max (list_ratio -. size_ratio) 0.0 *. (size |> snd |> float_of_int) 102 - |> int_of_float 103 - in 104 - let$ items = render_items 105 - and$ shift_amount = shift_amount in 106 - items 107 - |> Ui.shift_area 0 shift_amount 108 - |> Ui.resize ~sh:1 109 - |> simpleSizeSensor ~size_var 110 - |> Ui.resize ~w:3 ~sw:1 ~h:0 111 - |> simpleSizeSensor ~size_var:rendered_size_var 112 - in 113 - scrollitems 114 - ;; 115 - 116 15 type 'a maybeSelectable = 117 16 | Selectable of 'a selectable_item 118 17 | Filler of Ui.t ··· 126 25 = 127 26 (* 128 27 The rough overview is: 129 - 1. Make a lookup list that has the indexes of all the selectable items within the overall list, we will be selecting from those 28 + 1. Make a new list that only contains our selectable items 130 29 2. Render the items, making sure to tell the selected one to render as selected. 131 30 3. Calculate how much we should scroll by. 132 31 4. offset by the scroll amount, apply size sensors and output final ui 133 32 *) 134 33 let selected_var = Lwd.var 0 in 135 34 let selected_position = Lwd.var (0, 0) in 136 - let selectable_item_indexes = 35 + let selectable_items = 137 36 let$ items = items in 138 - let lut = Array.make (Array.length items) 0 in 37 + let selectable_items = Array.make (Array.length items) (Obj.magic ()) in 139 38 let final_len = 140 39 items 141 40 |> Base.Array.foldi ~init:0 ~f:(fun i selectable_count item -> 142 41 match item with 143 - | Selectable _ -> 144 - Array.set lut selectable_count i; 42 + | Selectable item -> 43 + Array.set selectable_items selectable_count (i, item); 145 44 selectable_count + 1 146 45 | Filler _ -> 147 46 selectable_count) 148 47 in 149 - Array.sub lut 0 final_len 48 + Array.sub selectable_items 0 final_len 150 49 in 151 50 (*handle selections*) 152 51 let render_items = ··· 154 53 and$ items, selected, selectable_item_indexes = 155 54 (* This doesn't depend on changes in focus but it should update whenever there are new items or a selection change*) 156 55 let$ items = items 157 - and$ selectable_item_indexes = selectable_item_indexes 56 + and$ selectable_items = selectable_items 158 57 and$ selected = Lwd.get selected_var in 159 58 (* First ensure if our list has gotten shorter we haven't selected off the list*) 160 59 (* We do this here to ensure that the selected var is updated before we render to avoid double rendering*) 161 - let max_selected = Int.max 0 (Array.length selectable_item_indexes - 1) in 60 + let max_selected = Int.max 0 (Array.length selectable_items - 1) in 162 61 if Int.min selected max_selected <> selected then selected_var $= max_selected; 163 62 let selected = Lwd.peek selected_var in 164 - (* We lookup the index of the selected item in the list of items, remeber our list isn't entirely selectable items*) 165 - if Array.length selectable_item_indexes > 0 63 + if Array.length selectable_items > 0 166 64 then ( 167 - let item_idx = selectable_item_indexes.(selected) in 168 - items.(item_idx) |> fun x -> 169 - (match x with 170 - | Selectable s -> 171 - on_selection_change s.data 172 - | Filler _ -> 173 - failwith "selected an item that wasn't selectable. This shouldn't be possible"); 174 - items, item_idx, selectable_item_indexes) 175 - else items, 0, selectable_item_indexes 65 + let item_idx, item = selectable_items.(selected) in 66 + on_selection_change item.data; 67 + items, item_idx, selectable_items) 68 + else items, 0, selectable_items 176 69 in 177 70 (* Ui.vcat can be a little weird when the *) 178 71 items ··· 214 107 let shift_amount = 215 108 (*get the actual idx not just the selection number*) 216 109 let$ selected_idx = 217 - Lwd.map2 218 - (Lwd.get selected_var) 219 - selectable_item_indexes 220 - ~f:(fun selected indexes -> 221 - if Array.length indexes > 0 then indexes.(selected) else 0) 110 + Lwd.map2 (Lwd.get selected_var) selectable_items ~f:(fun selected selectable -> 111 + if Array.length selectable > selected then selectable.(selected) |> fst else 0) 222 112 and$ size = Lwd.get size_var 223 113 and$ length = items |>$ Array.length 224 114 and$ ren_size = Lwd.get rendered_size_var in ··· 246 136 |> simpleSizeSensor ~size_var:rendered_size_var 247 137 in 248 138 scrollitems 139 + ;; 140 + 141 + (**Makes a ui element selectable. 142 + 143 + Takes [ui] and returns a function that appends '>' to the start when given [true] and ' ' when false 144 + 145 + Used in conjuction with [selection_list_custom]*) 146 + let selectable_item ui is_focused = 147 + let height = Ui.layout_height ui in 148 + let prefix = 149 + if is_focused then I.char A.(bg blue) '>' 1 height else I.char A.empty ' ' 1 height 150 + in 151 + Ui.hcat [ prefix |> Ui.atom; ui ] 152 + ;; 153 + 154 + (** Selection list that allows for custom handling of keyboard events. 155 + Scrolls when the selection reaches the lower third 156 + Only handles up and down keyboard events. Use [~custom_handler] to do handle confirming your selection and such *) 157 + let selection_list_custom 158 + ?(focus = Focus.make ()) 159 + ?(on_selection_change = fun _ -> ()) 160 + ~custom_handler 161 + (items : 'a selectable_item list Lwd.t) 162 + = 163 + selection_list_exclusions 164 + ~focus 165 + ~on_selection_change 166 + ~custom_handler 167 + ( items |>$ fun items -> 168 + let selectable_items = Array.make (List.length items) (Obj.magic ()) in 169 + items |> List.iteri (fun i x -> Array.set selectable_items i (Selectable x)); 170 + selectable_items ) 249 171 ;; 250 172 251 173 (** A filterable selectable list.
+2 -2
jj_tui/lib/widgets/widgets.ml
··· 331 331 if c >= '0' && c <= '9' then Some (int_of_char c - int_of_char '0') else None 332 332 ;; 333 333 334 - (** Tab view, where exactly one element of [l] is shown at a time. *) 335 - let mouse_tabs (tabs : (string * (unit -> Ui.t Lwd.t)) list) : Ui.t Lwd.t = 334 + (** Tab view, where exactly one element of [l] is shown at a time. Naviagted using number keys on the keyboard *) 335 + let keyboard_tabs (tabs : (string * (unit -> Ui.t Lwd.t)) list) : Ui.t Lwd.t = 336 336 match tabs with 337 337 | [] -> 338 338 Lwd.return Ui.empty