Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/key_map/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(executable
(name main)
(libraries minttea spices leaves))
48 changes: 48 additions & 0 deletions examples/key_map/main.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
open Leaves
open Minttea

type msg = CursorUp | CursorDown | CursorLeft

let defaults =
let open Key_map in
make [
on
~help:{ key = "up"; desc = "↑/k" }
[ Minttea.Event.Up; Minttea.Event.Key "k" ]
CursorUp;
on
~help:{ key = "down"; desc = "↓/j" }
Copy link
Owner

@leostera leostera Jan 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about deriving these from the events and using the ~help for writing more about what the keymap is supposed to do?

For example:

let keymap = Key_map.make [
  on Event.[Up; Key "k"] CursorUp ~help:"moves the cursor up";
  on Event.[Key "q"] Quit ~help:"quit the app";
]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how with this information we can put together a bindings help view:

↑/k: moves the cursor up
q: quit the app

[ Minttea.Event.Down; Minttea.Event.Key "j" ]
CursorDown;
on ~disabled:true
~help:{ key = "left"; desc = "←/h" }
[ Minttea.Event.Left; Minttea.Event.Key "h" ]
CursorLeft;
]

let custom_key_map =
let open Key_map in
[
on
~help:{ key = "up"; desc = "↑/k" }
[ Minttea.Event.Up; Minttea.Event.Key "k"; Minttea.Event.Key "u" ]
CursorUp;
]

let () =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost forgot, all the examples should be working Minttea app to showcase how this component fits into an application.

This also helps you see if the API really figs the way you'd use it in an app.

In this case we can also refactor an existing example that uses several keys (like the views example) so use one or more key maps.

This should also expose the component to the use cases of:

  • how do you deal with multiple key maps in a single app?

  • how do we render key maps for users to see that they are available?

We don't have to have those answers in this PR but they would definitely inform the design you go for.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so in this case should I remove the key_map example and refactor say the views example to use the key map feature?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you :) either making this one a complete tiny app, or refactoring one of the others.

List.iter
(fun k ->
match Key_map.find_match ~custom_key_map k defaults with
| Some CursorUp -> print_endline "up"
| Some CursorDown -> print_endline "down"
| Some CursorLeft -> print_endline "left"
| None -> print_endline "Not Found")
[
Event.Up;
Event.Key "k";
Event.Key "u";
Event.Down;
Event.Key "j";
Event.Left;
Event.Enter;
]
32 changes: 32 additions & 0 deletions leaves/key_map.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type help = { key : string; desc : string }

type binding = {
keys : Minttea.Event.key list;
help : help option;
disabled : bool;
}

type 'a t = ('a * binding) list

let on ?help ?(disabled = false) keys msg = (msg, { keys; help; disabled })

let find_match ?(custom_key_map : 'a t option) key (default_key_map : 'a t) =
let key_map =
match custom_key_map with
| Some k_map ->
List.fold_left
(fun acc (k, b) ->
if List.mem_assoc k acc then acc else List.cons (k, b) acc)
k_map default_key_map
| None -> default_key_map
in

let f (_, (binding : binding)) =
if binding.disabled then false
else List.exists (fun k -> k == key) binding.keys
in
List.find_opt f key_map |> Option.map (fun (msg, _) -> msg)

(* INFO: This is just for future proofing, in case the underlying type changes *)
let make (key_map : ('a * binding) list) = key_map
let to_list (key_map : 'a t) = key_map
26 changes: 26 additions & 0 deletions leaves/key_map.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type help = { key : string; desc : string }

type binding = {
keys : Minttea.Event.key list;
help : help option;
disabled : bool;
}

type 'a t = ('a * binding) list

val on :
?help:help ->
?disabled:bool ->
Minttea.Event.key list ->
'a ->
'a * binding

val find_match :
?custom_key_map:'a t ->
Minttea.Event.key ->
'a t ->
'a option

val make : ('a * binding) list -> 'a t
val to_list : 'a t -> ('a * binding) list