package miaou-core

  1. Overview
  2. Docs
Miaou core/widgets (no drivers, no SDL)

Install

dune-project
 Dependency

Authors

Maintainers

Sources

v0.5.2.tar.gz
md5=60a3b9f181f24572a06a9492532bfdda
sha512=fcc35a275066be2900e6201782faf47503076fa4640f08cf78067835a6f447b74613009e55b2ac799adb7ca46f1bffa261fc5971753f2cc3c6bef327511c7ef6

doc/src/miaou_widgets_navigation/breadcrumbs_widget.ml.html

Source file breadcrumbs_widget.ml

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
(*****************************************************************************)
(*                                                                           *)
(* SPDX-License-Identifier: MIT                                              *)
(* Copyright (c) 2025 Nomadic Labs <contact@nomadic-labs.com>                *)
(*                                                                           *)
(*****************************************************************************)

[@@@warning "-32-34-37-69"]

module W = Miaou_widgets_display.Widgets
module Helpers = Miaou_helpers.Helpers

type crumb = {id : string; label : string; on_enter : (unit -> unit) option}

type t = {crumbs : crumb list; selected : int}

let crumb ~id ~label ?on_enter () = {id; label; on_enter}

let id c = c.id

let label c = c.label

let clamp idx len = if len = 0 then 0 else max 0 (min (len - 1) idx)

let make crumbs =
  let crumbs =
    List.filter (fun c -> String.length c.label > 0) crumbs
    |> List.map (fun c -> {id = c.id; label = c.label; on_enter = c.on_enter})
  in
  {crumbs; selected = clamp 0 (List.length crumbs)}

let current t = List.nth_opt t.crumbs t.selected

let move t dir =
  let len = List.length t.crumbs in
  if len = 0 then t
  else
    let next =
      match dir with
      | `Left -> max 0 (t.selected - 1)
      | `Right -> min (len - 1) (t.selected + 1)
      | `First -> 0
      | `Last -> len - 1
    in
    {t with selected = next}

let select t ~id =
  let rec find i = function
    | [] -> t.selected
    | c :: cs -> if String.equal c.id id then i else find (i + 1) cs
  in
  let len = List.length t.crumbs in
  let idx = clamp (find 0 t.crumbs) len in
  {t with selected = idx}

let handle_event ?(bubble_unhandled = false) t ~key =
  match Miaou_core.Keys.of_string key with
  | Some Miaou_core.Keys.Left -> (move t `Left, `Handled)
  | Some Miaou_core.Keys.Right -> (move t `Right, `Handled)
  | Some (Miaou_core.Keys.Char "Home") -> (move t `First, `Handled)
  | Some (Miaou_core.Keys.Char "End") -> (move t `Last, `Handled)
  | Some Miaou_core.Keys.Enter -> (
      match current t with
      | Some {on_enter = Some f; _} ->
          f () ;
          (t, `Handled)
      | _ -> (t, if bubble_unhandled then `Bubble else `Handled))
  | _ -> (
      (* Check for mouse click to select crumb *)
      match Miaou_helpers.Mouse.parse_click key with
      | Some {col; _} -> (
          (* Calculate which crumb was clicked based on column position.
             Each crumb is: "label" + " > " separator (3 chars). *)
          let rec find_crumb idx pos crumbs =
            match crumbs with
            | [] -> None
            | crumb :: rest ->
                let crumb_width = String.length crumb.label in
                let sep_width = if rest = [] then 0 else 3 in
                (* " > " *)
                let total = crumb_width + sep_width in
                if col >= pos && col < pos + crumb_width then Some idx
                else find_crumb (idx + 1) (pos + total) rest
          in
          match find_crumb 0 1 t.crumbs with
          | Some idx -> (
              let t' = {t with selected = idx} in
              (* Also trigger on_enter if clicking *)
              match List.nth_opt t'.crumbs idx with
              | Some {on_enter = Some f; _} ->
                  f () ;
                  (t', `Handled)
              | _ -> (t', `Handled))
          | None -> (t, if bubble_unhandled then `Bubble else `Handled))
      | None -> (t, if bubble_unhandled then `Bubble else `Handled))

let handle_key t ~key =
  let t, status = handle_event t ~key in
  (t, match status with `Handled -> `Handled | `Bubble -> `Ignored)

let render t ~focus =
  let parts =
    List.mapi
      (fun i c ->
        let base = if i = t.selected then W.bold c.label else W.dim c.label in
        if focus && i = t.selected then W.title_highlight base else base)
      t.crumbs
  in
  match parts with [] -> "" | _ -> Helpers.concat_with_sep (W.dim " > ") parts