{"id":15724322,"url":"https://github.com/cedlemo/ocaml-notty-introduction","last_synced_at":"2025-07-03T23:34:08.878Z","repository":{"id":54366529,"uuid":"67309120","full_name":"cedlemo/OCaml-Notty-introduction","owner":"cedlemo","description":"My notes about the Notty library https://github.com/pqwy/notty","archived":false,"fork":false,"pushed_at":"2021-02-23T04:24:17.000Z","size":39,"stargazers_count":45,"open_issues_count":4,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-07T13:06:14.721Z","etag":null,"topics":["ocaml","ocaml-notty"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cedlemo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-09-03T19:58:30.000Z","updated_at":"2024-08-22T10:48:47.000Z","dependencies_parsed_at":"2022-08-13T13:31:10.339Z","dependency_job_id":null,"html_url":"https://github.com/cedlemo/OCaml-Notty-introduction","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cedlemo/OCaml-Notty-introduction","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2FOCaml-Notty-introduction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2FOCaml-Notty-introduction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2FOCaml-Notty-introduction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2FOCaml-Notty-introduction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cedlemo","download_url":"https://codeload.github.com/cedlemo/OCaml-Notty-introduction/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedlemo%2FOCaml-Notty-introduction/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263421504,"owners_count":23464013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ocaml","ocaml-notty"],"created_at":"2024-10-03T22:16:10.513Z","updated_at":"2025-07-03T23:34:08.825Z","avatar_url":"https://github.com/cedlemo.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ocaml Notty library\n\n* [Introduction](#introduction)\n* [Basics](#basics)\n  * [Image](#image)\n  * [The Image module](#the-image-module)\n    * [Image Creation](#image-creation)\n    * [Image Composition](#image-composition)\n    * [Image Modification](#image-modification)\n      * [Cropping Image](#cropping-image)\n        * [Horizontal cropping](#horizontal-cropping)\n\t* [Vertical cropping](#vertical-cropping)\n      * [Padding](#padding)\n        * [Negative cropping](#negative-cropping)\n\t* [Padding functions](#padding-functions)\n  * [The Notty_unix.Term module](#the-unix-term-module)\n  * [The Notty_lwt.Term module](#the-lwt-term-module)\n\n## Introduction\n\nBetter than curses/ncurses, here is Notty. Written in OCaml, this library\nis based on the notion of composable images.\n\n  * [Notty on Github](https://github.com/pqwy/notty)\n  * [documentation](http://pqwy.github.io/notty/doc/Notty.html)\n\n## Basics\n\n### Image\n\nAn image is a rectangle displayed in the terminal that contains styled characters.\nAn image can be:\n  * a single character with display attributes,\n  * or some text with display attributes,\n  * or a combinaison of both beside, above or over each other.\n\nAfter its construction, the image can be rendered. Basically, it is tranformed\nto a string we can display.\n\nPrint a red \"Wow!\" above its right-shifted copy:\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(*\n * ocamlbuild -pkg notty -pkg notty.unix basics_wow.native\n *)\nlet () =\nlet wow = I.string A.(fg lightred) \"Wow!\" in\nI.(wow \u003c-\u003e (void 2 0 \u003c|\u003e wow))\n|\u003e Notty_unix.eol\n|\u003e Notty_unix.output_image\n```\n\nIn this program we create an image that is based on a string \"Wow!\" with some\nattributes `A.(fg lightred)`. Then we compose a bigger image and display twice\nthe `wow` image at different position. The function `Notty_unix.output_image_endline`\nallow us to display the generated image.\n\n### The Image module\n\nhttps://pqwy.github.io/notty/Notty.I.html\n\n#### Image Creation\n\nBasics images can be created with :\n\n*  `I.string` : require an attribute (style) and a string\n*  `I.uchars` : require an attribute and an array of unicode value\n*  `I.char`   : require an attribute, a char and 2 int for the width and the height of the grid.\n*  `I.uchar`  : same as `I.char` but for unicode value.\n\nor 2 specials primitives:\n\n*  `I.empty`  : which is a zero sized image.\n*  `I.void`   : require a width and an height, it is a transparent image.\n\n##### I.string basic example\n\n```ocaml\nopen Notty\nopen Notty_unix\n(* ocamlbuild -pkg notty -pkg notty.unix basics_I_string.native *)\nlet () =\nI.string A.(fg lightred) \"Wow!\"\n|\u003e eol\n|\u003e Notty_unix.output_image\n```\n\n##### I.uchars basic example\n\n```ocaml\nopen Notty\nopen Notty_unix\n(* ocamlbuild -pkg notty -pkg notty.unix basics_I_uchars.native *)\nlet () =\n  let my_unicode_chars =\n    [|0x2500; 0x2502; 0x2022; 0x2713; 0x25cf;\n      0x256d; 0x256e; 0x256f; 0x2570; 0x253c|] in\n   I.uchars A.(fg lightred) (Array.map Uchar.of_int my_unicode_chars)\n   |\u003e Notty_unix.eol\n   |\u003e Notty_unix.output_image\n```\n\n##### I.char basic example\n\n```ocaml\nopen Notty\nopen Notty_unix\n(* ocamlbuild -pkg notty -pkg notty.unix basics_I_char.native *)\nlet () =\n   I.char A.(fg lightred) 'o' 4 4\n   |\u003e Notty_unix.eol\n   |\u003e Notty_unix.output_image\n```\n\n##### I.uchar basic example\n\n```ocaml\nopen Notty\nopen Notty_unix\n(* ocamlbuild -pkg notty -pkg notty.unix basics_I_uchar.native *)\nlet () =\n   I.uchar A.(fg lightred) (Uchar.of_int 0x2022) 4 4\n   |\u003e Notty_unix.eol\n   |\u003e Notty_unix.output_image\n```\n\n#### Image composition\n\nThere are 3 composition modes which allow you to blend simple images into\ncomplexe ones.\n\n*  `I.(\u003c|\u003e)` : puts one image after another\n*  `I.(\u003c-\u003e)` : puts one image below another\n*  `I.(\u003c/\u003e)` : puts one image on another.\n\n##### Side by side images\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlbuild -pkg notty -pkg notty.unix basic_I_side_by_side *)\nlet () =\n  let bar = I.uchar A.(fg lightred) 0x2502 3 1 in\n  let img1 = I.string A.(fg lightgreen) \"image1\" in\n  I.(img1 \u003c|\u003e bar) |\u003e Notty_unix.output_image_endline\n```\n\n##### Image above another\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlbuild -pkg notty -pkg notty.unix basics_I_above_another.native *)\nlet () =\n  let bar = I.uchar A.(fg lightred) (Uchar.of_int 0x2502) 3 1 in\n  let img1 = I.string A.(fg lightgreen) \"image1\" in\n  I.(img1 \u003c-\u003e bar) |\u003e Notty_unix.eol |\u003e Notty_unix.output_image\n```\n\n##### Image overlay\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlbuild -pkg notty -pkg notty.unix basic_I_overlay.native *)\nlet () =\n  let bar = I.uchar A.(fg lightred) 0x2502 3 1 in\n  let img1 = I.string A.(fg lightgreen) \"image1\" in\n  I.(img1 \u003c/\u003e bar) |\u003e Notty_unix.output_image_endline\n```\n\n#### Image modifications\n\nBasic notty images can be modified by cropping them or by adding them padding.\n\n#### Cropping image\n\n*  `I.hcrop`\n*  `I.vcrop`\n*  `I.crop`\n\n\n##### Horizontal cropping:\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlfind ocamlc -o basics_hcropping -package notty,notty.unix  -linkpkg -g basics_I_hcropping.ml *)\nlet long_line_str = \"This is a line that will be cropped 2 unit left and 5 unit right\"\n\nlet () =\n  let long_line = I.string A.(fg lightgreen ++ bg black) long_line_str in\n  let long_line_cropped = I.hcrop 2 5 long_line in\n  I.(long_line \u003c-\u003e long_line_cropped) |\u003e Notty_unix.output_image_endline\n```\n\n##### Vertical cropping:\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlfind ocamlc -o basics_vcropping -package notty,notty.unix  -linkpkg -g basics_I_vcropping.ml *)\nlet line_str num =\n  \"line number \" ^ (string_of_int num)\n\nlet build_5_lines () =\n  let rec _build img remain =\n    if remain = 0 then img\n    else let str = line_str (6 - remain) in\n    _build I.(img \u003c-\u003e string A.(fg lightgreen ++ bg black) str) (remain - 1)\n  in _build (I.string A.(fg lightgreen ++ bg black) (line_str 1)) 4\n\nlet description =\n  I.string A.(fg lightyellow ++ bg lightblack) \"crop 2 at top and 1 at bottom\"\n\nlet () =\n   I.(build_5_lines () \u003c-\u003e\n   description \u003c-\u003e\n   vcrop 2 1 (build_5_lines ())) |\u003e Notty_unix.output_image_endline\n```\n#### Padding\n\n##### Negative cropping\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlfind ocamlc -o basics_negative_vcropping -package notty,notty.unix  -linkpkg -g basics_I_negative_vcropping.ml *)\nlet line_str num =\n  \"line number \" ^ (string_of_int num)\n\nlet build_5_lines () =\n  let rec _build img remain =\n    if remain = 0 then img\n    else let str = line_str (6 - remain) in\n    _build I.(img \u003c-\u003e string A.(fg lightgreen ++ bg black) str) (remain - 1)\n  in _build (I.string A.(fg lightgreen ++ bg black) (line_str 1)) 4\n\nlet description =\n  I.string A.(fg lightyellow ++ bg lightblack) \"Negative crop -2 at top and -1 at bottom\"\n\nlet () =\n   I.(build_5_lines () \u003c-\u003e\n   description \u003c-\u003e\n   vcrop (-2) (-1) (build_5_lines ())) |\u003e Notty_unix.output_image_endline\n```\nThe same applies to `I.hcrop`.\n\n##### Padding functions\n\n*  `I.hpad`\n*  `I.vpad`\n*  `I.pad`\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlfind ocamlc -o basics_padding -package notty,notty.unix  -linkpkg -g basics_I_padding.ml *)\nlet line_str num =\n  \"line number \" ^ (string_of_int num)\n\nlet build_5_lines () =\n  let rec _build img remain =\n    if remain = 0 then img\n    else let str = line_str (6 - remain) in\n    _build I.(img \u003c-\u003e string A.(fg lightgreen ++ bg black) str) (remain - 1)\n  in _build (I.string A.(fg lightgreen ++ bg black) (line_str 1)) 4\n\nlet description =\n  I.string A.(fg lightyellow ++ bg lightblack) \"Padding left = 2, right = 3, top = 4 and 1 at bottom\"\n\nlet () =\n   I.(build_5_lines () \u003c-\u003e\n   description \u003c-\u003e\n   pad ~l:2 ~r:3 ~t:4 ~b:1 (build_5_lines ())) |\u003e Notty_unix.output_image_endline\n```\n\n### The Unix Term module\n\n*  http://pqwy.github.io/notty/Notty_unix.Term.html\n\nIt is an helper for fullscreen, interactive applications.\n\n#### Simple interactive fullscreen\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlbuild -pkg notty -pkg notty.unix basics_Term_simple_terminal.native\n * or\n * ocamlfind ocamlc -o basics_simple_terminal -package notty,notty.unix -linkpkg -g basics_Term_simple_terminal.ml*)\n\nlet rec main_loop t =\n  let img = I.(string A.(bg lightred ++ fg black) \"This is a simple example\") in\n    Term.image t img;\n    match Term.event t with\n    | `End | `Key (`Escape, []) | `Key (`Uchar 67, [`Ctrl]) -\u003e ()\n    | _ -\u003e main_loop t\n\nlet () =\n  let t = Term.create () in main_loop t\n```\n\nThis little programm just draw in all the terminal, add a string and wait for\nkey events. Press \"Esc\" and the program exits.\n\n#### Handling the terminal size and the resize events.\n\n```ocaml\nopen Notty\nopen Notty_unix\n\n(* ocamlbuild -pkg notty -pkg notty.unix basics_Term_simple_terminal_resize.native\n * or\n * ocamlfind ocamlc -o simple_terminal_resize -package notty.unix  -linkpkg -g common.ml basics_Term_simple_terminal_resize.ml*)\n\nlet grid xxs = xxs |\u003e List.map I.hcat |\u003e I.vcat\n\nlet outline attr t =\n  let (w, h) = Term.size t in\n  let chr x = I.uchar attr x 1 1\n  and hbar  = I.uchar attr 0x2500 (w - 2) 1\n  and vbar  = I.uchar attr 0x2502 1 (h - 2) in\n  let (a, b, c, d) = (chr 0x256d, chr 0x256e, chr 0x256f, chr 0x2570) in\n  grid [ [a; hbar; b]; [vbar; I.void (w - 2) 1; vbar]; [d; hbar; c] ]\n\nlet size_box cols rows =\n  let cols_str = string_of_int cols in let rows_str = string_of_int rows in\n  let label = (cols_str ^ \"x\" ^ rows_str) in\n  let box = I.string A.(fg lightgreen ++ bg lightblack) label in\n  let top_margin = (rows - I.height box) / 2 in\n  let left_margin = (cols - I.width box) / 2 in\n  I.pad ~t:top_margin ~l:left_margin box\n\nlet rec main t (x, y as pos) =\n  let img = I.((outline A.(fg lightred ) t) \u003c/\u003e (size_box x y)) in\n  Term.image t img;\n  Term.cursor t (Some pos);\n  match Term.event t with\n  | `End | `Key (`Escape, []) | `Key (`Uchar 67, [`Ctrl]) -\u003e ()\n  | `Resize (cols, rows) -\u003e main t (cols, rows)\n  | _ -\u003e main t pos\n\nlet () =\n  let t = Term.create () in\n  main t (Term.size t)\n```\n\n*  The grid function takes a list of list of images and compose a bigger image.\n*  The outline function draws a line at the border of the screen.\n*  The attr argument allows to configure the style of the line.\n\n### The Lwt Term module\n\n*  https://pqwy.github.io/notty/Notty_lwt.html\n*  https://ocsigen.org/lwt/3.0.0/manual/\n\n#### Handling the terminal size and the resize events.\n\nThe same example but inside a light-weight cooperative thread.\n\n```ocaml\nopen Notty\nopen Notty_lwt\nopen Lwt\n\n(* ocamlfind ocamlc -o simple_lwt_terminal_resize -package notty.lwt -linkpkg -g common.ml basics_Lwt_Term_simple_terminal_resize.ml*)\n\n\nmodule LwtTerm = Notty_lwt.Term\n\nlet grid xxs = xxs |\u003e List.map I.hcat |\u003e I.vcat\n\nlet outline attr t =\n  let (w, h) = LwtTerm.size t in\n  let chr x = I.uchar attr x 1 1\n  and hbar  = I.uchar attr 0x2500 (w - 2) 1\n  and vbar  = I.uchar attr 0x2502 1 (h - 2) in\n  let (a, b, c, d) = (chr 0x256d, chr 0x256e, chr 0x256f, chr 0x2570) in\n  grid [ [a; hbar; b]; [vbar; I.void (w - 2) 1; vbar]; [d; hbar; c] ]\n\nlet size_box cols rows =\n  let cols_str = string_of_int cols in let rows_str = string_of_int rows in\n  let label = (cols_str ^ \"x\" ^ rows_str) in\n  let box = I.string A.(fg lightgreen ++ bg lightblack) label in\n  let top_margin = (rows - I.height box) / 2 in\n  let left_margin = (cols - I.width box) / 2 in\n  I.pad ~t:top_margin ~l:left_margin box\n\nlet rec main t (x, y as pos) =\n  let img = I.((outline A.(fg lightred ) t) \u003c/\u003e (size_box x y)) in\n  LwtTerm.image t img\n  \u003e\u003e= fun () -\u003e\n    LwtTerm.cursor t (Some pos)\n    \u003e\u003e= fun () -\u003e\n      Lwt_stream.get ( LwtTerm.events t)\n      \u003e\u003e= fun event -\u003e\n      match event with\n      | None -\u003e LwtTerm.release t \u003e\u003e= fun () -\u003e Lwt.return_unit\n      | Some (`Resize _ | #Unescape.event as x) -\u003e match x with\n        | `Key (`Escape, []) | `Key (`Uchar 67, [`Ctrl]) -\u003e LwtTerm.release t \u003e\u003e= fun () -\u003e Lwt.return_unit\n        | `Resize (cols, rows) -\u003e main t (cols, rows)\n        | _ -\u003eLwt.return () \u003e\u003e= fun () -\u003e main t pos\n\nlet () =\n  let t = LwtTerm.create () in\n  let size = LwtTerm.size t in\n  Lwt_main.run @@ main t size\n```\n\n#### Update a terminal interface with a timeout.\n\nThe following example illustrate how to :\n* create of a Notty.term that is updated with a timeout\n* use an Notty.term with another thread, one for the interface and one for\nanother loop.\n\nIt is a very simple terminal application that displays a counter. The user can\nleave it with `Esc`.\n\n```ocaml\n(* ocamlfind ocamlc -o basics_Lwt_Term_simple_terminal_timeout -package lwt,notty.lwt -linkpkg -g basics_Lwt_Term_simple_terminal_timeout.ml *)\nopen Lwt\nopen Lwt.Infix\nopen Notty\nopen Notty_lwt\nopen Notty.Infix\n\nmodule Term = Notty_lwt.Term\n\nlet counter = ref 0\n\nlet rec increase_counter () =\n  Lwt_unix.sleep 0.1\n  \u003e\u003e= fun () -\u003e\n    (\n    if !counter \u003c max_int then counter := (!counter + 1)\n    else counter := 0\n  );\n  Lwt.return ()\n  \u003e\u003e= fun () -\u003e\n    increase_counter ()\n\nlet render (w, h) =\n  I.(strf ~attr:A.(fg lightblack) \"[counter %d]\" !counter)\n\nlet timer () = Lwt_unix.sleep 0.1 \u003e|= fun () -\u003e `Timer\n\nlet event term = Lwt_stream.get (Term.events term) \u003e|= function\n  | Some (`Resize _ | #Unescape.event as x) -\u003e x\n  | None -\u003e `End\n\nlet rec loop term (e, t) dim =\n  (e \u003c?\u003e t) \u003e\u003e= function\n  | `End | `Key (`Escape, []) -\u003e\n      Lwt.return_unit\n  | `Timer -\u003e\n      Term.image term (render dim)\n      \u003e\u003e= fun () -\u003e\n        loop term (e, timer ()) dim\n  | `Mouse ((`Press _|`Drag), (x, y), _) -\u003e\n      loop term (event term, t) dim\n  | `Resize dim -\u003e\n      Term.image term (render dim)\n      \u003e\u003e= fun () -\u003e\n        loop term (event term, t) dim\n  | _ -\u003e loop term (event term, t) dim\n\nlet interface () =\n  let tc = Unix.(tcgetattr stdin) in\n  Unix.(tcsetattr stdin TCSANOW { tc with c_isig = false });\n  let term    = Term.create () in\n  let size = Term.size term in\n  loop term (event term, timer ()) size\n\nlet main () =\n  Lwt.choose [\n    increase_counter ();\n    interface ();\n  ]\n\nlet () = Lwt_main.run (main ())\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedlemo%2Focaml-notty-introduction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcedlemo%2Focaml-notty-introduction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedlemo%2Focaml-notty-introduction/lists"}