WIP layout engine
This commit is contained in:
		
						commit
						13c8a36ba7
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
_build/**
 | 
			
		||||
.direnv/**
 | 
			
		||||
.envrc
 | 
			
		||||
							
								
								
									
										2
									
								
								.ocamlformat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.ocamlformat
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
version=0.26.2
 | 
			
		||||
profile=default
 | 
			
		||||
							
								
								
									
										4
									
								
								bin/dune
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								bin/dune
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
(executable
 | 
			
		||||
 (public_name tuiano)
 | 
			
		||||
 (name main)
 | 
			
		||||
 (libraries tuiano))
 | 
			
		||||
							
								
								
									
										5
									
								
								bin/main.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								bin/main.ml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
let () = 
 | 
			
		||||
  Tuiano.Tui.setup_terminal();
 | 
			
		||||
  (* let term = Tuiano.Tui.termsize in *)
 | 
			
		||||
  (* match term with *)
 | 
			
		||||
  (* | (h, w) -> Printf.printf "Got tty size: %d x %d\n" h w *)
 | 
			
		||||
							
								
								
									
										26
									
								
								dune-project
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								dune-project
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
(lang dune 3.16)
 | 
			
		||||
 | 
			
		||||
(name tuiano)
 | 
			
		||||
 | 
			
		||||
(generate_opam_files true)
 | 
			
		||||
 | 
			
		||||
(source
 | 
			
		||||
 (github username/reponame))
 | 
			
		||||
 | 
			
		||||
(authors "Nate Anderson")
 | 
			
		||||
 | 
			
		||||
(maintainers "Nate Anderson")
 | 
			
		||||
 | 
			
		||||
(license LICENSE)
 | 
			
		||||
 | 
			
		||||
(documentation https://fosscat.com)
 | 
			
		||||
 | 
			
		||||
(package
 | 
			
		||||
 (name tuiano)
 | 
			
		||||
 (synopsis "A Piano keyboard on the TUI")
 | 
			
		||||
 (description "A Piano keyboard on the TUI")
 | 
			
		||||
 (depends ocaml dune)
 | 
			
		||||
 (tags
 | 
			
		||||
  (topics "to describe" your project)))
 | 
			
		||||
 | 
			
		||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
 | 
			
		||||
							
								
								
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
{
 | 
			
		||||
  "nodes": {
 | 
			
		||||
    "flake-utils": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "systems": "systems"
 | 
			
		||||
      },
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1726560853,
 | 
			
		||||
        "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
 | 
			
		||||
        "owner": "numtide",
 | 
			
		||||
        "repo": "flake-utils",
 | 
			
		||||
        "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "numtide",
 | 
			
		||||
        "repo": "flake-utils",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "nixpkgs": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1727634051,
 | 
			
		||||
        "narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=",
 | 
			
		||||
        "owner": "nixos",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "nixos",
 | 
			
		||||
        "ref": "nixos-unstable",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "root": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "flake-utils": "flake-utils",
 | 
			
		||||
        "nixpkgs": "nixpkgs"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "systems": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1681028828,
 | 
			
		||||
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
 | 
			
		||||
        "owner": "nix-systems",
 | 
			
		||||
        "repo": "default",
 | 
			
		||||
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "nix-systems",
 | 
			
		||||
        "repo": "default",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "root": "root",
 | 
			
		||||
  "version": 7
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								flake.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								flake.nix
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
{
 | 
			
		||||
  description = "A very basic flake";
 | 
			
		||||
 | 
			
		||||
  inputs = {
 | 
			
		||||
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
 | 
			
		||||
    flake-utils.url = "github:numtide/flake-utils";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  outputs = { self, flake-utils, nixpkgs }:
 | 
			
		||||
    flake-utils.lib.eachDefaultSystem (system: let
 | 
			
		||||
      pkgs = import nixpkgs {
 | 
			
		||||
        inherit system;
 | 
			
		||||
      };
 | 
			
		||||
    in {
 | 
			
		||||
      devShell = 
 | 
			
		||||
      #let
 | 
			
		||||
        #
 | 
			
		||||
      #in
 | 
			
		||||
        pkgs.mkShell {
 | 
			
		||||
          buildInputs = with pkgs; [
 | 
			
		||||
            ocaml
 | 
			
		||||
            ocamlPackages.ocaml-lsp
 | 
			
		||||
            ocamlPackages.findlib
 | 
			
		||||
            ocamlformat
 | 
			
		||||
            ocamlPackages.ocamlformat-rpc-lib
 | 
			
		||||
            ocamlPackages.utop
 | 
			
		||||
            dune_3
 | 
			
		||||
            opam
 | 
			
		||||
            # Project deps
 | 
			
		||||
            ocamlPackages.ppxlib
 | 
			
		||||
            ocamlPackages.ppx_deriving
 | 
			
		||||
 | 
			
		||||
          ];
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										183
									
								
								lib/ansi.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								lib/ansi.ml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
			
		||||
open Unix
 | 
			
		||||
 | 
			
		||||
(* type coords = int * int *)
 | 
			
		||||
type offset = { x : int; y : int }
 | 
			
		||||
type ansirgb = int * int * int
 | 
			
		||||
 | 
			
		||||
type cursor_ops = Hide | Show | Move of offset
 | 
			
		||||
type screen_ops = Clear
 | 
			
		||||
 | 
			
		||||
type color =
 | 
			
		||||
  | Black
 | 
			
		||||
  | Red
 | 
			
		||||
  | Green
 | 
			
		||||
  | Yellow
 | 
			
		||||
  | Blue
 | 
			
		||||
  | Magenta
 | 
			
		||||
  | Cyan
 | 
			
		||||
  | White
 | 
			
		||||
  | Gray
 | 
			
		||||
  | BrightRed
 | 
			
		||||
  | BrightGreen
 | 
			
		||||
  | BrightYellow
 | 
			
		||||
  | BrightBlue
 | 
			
		||||
  | BrightMagenta
 | 
			
		||||
  | BrightCyan
 | 
			
		||||
  | BrightWhite
 | 
			
		||||
  | Default
 | 
			
		||||
  | Custom of ansirgb
 | 
			
		||||
 | 
			
		||||
type color_style =
 | 
			
		||||
  | Foreground of color
 | 
			
		||||
  | Background of color
 | 
			
		||||
 | 
			
		||||
type style =
 | 
			
		||||
  | Reset
 | 
			
		||||
  | Bold
 | 
			
		||||
  | Underlined
 | 
			
		||||
  | Blink
 | 
			
		||||
  | Inverse
 | 
			
		||||
  | Hidden
 | 
			
		||||
 | 
			
		||||
type ansi = 
 | 
			
		||||
  | Cursor of cursor_ops
 | 
			
		||||
  | Screen of screen_ops
 | 
			
		||||
  | ColorStyle of color_style
 | 
			
		||||
  | Style of style
 | 
			
		||||
 | 
			
		||||
let top_block = "▀"
 | 
			
		||||
let bot_block = "▄"
 | 
			
		||||
let block = "█"
 | 
			
		||||
 | 
			
		||||
let black = Foreground Black
 | 
			
		||||
let red = Foreground Red
 | 
			
		||||
let green = Foreground Green
 | 
			
		||||
let yellow = Foreground Yellow
 | 
			
		||||
let blue = Foreground Blue
 | 
			
		||||
let magenta = Foreground Magenta
 | 
			
		||||
let cyan = Foreground Cyan
 | 
			
		||||
let white = Foreground White
 | 
			
		||||
let default = Foreground Default
 | 
			
		||||
let bg_black = Background Black
 | 
			
		||||
let bg_red = Background Red
 | 
			
		||||
let bg_green = Background Green
 | 
			
		||||
let bg_yellow = Background Yellow
 | 
			
		||||
let bg_blue = Background Blue
 | 
			
		||||
let bg_magenta = Background Magenta
 | 
			
		||||
let bg_cyan = Background Cyan
 | 
			
		||||
let bg_white = Background White
 | 
			
		||||
let bg_default = Background Default
 | 
			
		||||
 | 
			
		||||
let is_valid_rgb = function x when x > 5 || x < 0 -> false | _ -> true
 | 
			
		||||
 | 
			
		||||
let rgb_to_ansi (rgb : ansirgb) =
 | 
			
		||||
  let rr, gg, bb = rgb in
 | 
			
		||||
  if is_valid_rgb rr && is_valid_rgb gg && is_valid_rgb bb then
 | 
			
		||||
    Ok (16 + (36 * rr) + (6 * gg) + bb)
 | 
			
		||||
  else Error "Invalid r, g, or b value provided (must be between 0-5)"
 | 
			
		||||
 | 
			
		||||
let screenop_to_ansi = function Clear -> "\027[2J"
 | 
			
		||||
 | 
			
		||||
let cursorop_to_ansi = function
 | 
			
		||||
  | Show -> "\027[?25h"
 | 
			
		||||
  | Hide -> "\027[?25l"
 | 
			
		||||
  | Move coord -> Printf.sprintf "\027[%d;%dH" coord.y coord.x
 | 
			
		||||
 | 
			
		||||
(* Function to get the ANSI escape code for a color *)
 | 
			
		||||
let color_to_ansi = function
 | 
			
		||||
  | Black -> "\027[30m"
 | 
			
		||||
  | Red -> "\027[31m"
 | 
			
		||||
  | Green -> "\027[32m"
 | 
			
		||||
  | Yellow -> "\027[33m"
 | 
			
		||||
  | Blue -> "\027[34m"
 | 
			
		||||
  | Magenta -> "\027[35m"
 | 
			
		||||
  | Cyan -> "\027[36m"
 | 
			
		||||
  | White -> "\027[37m"
 | 
			
		||||
  | Gray -> "\027[90m"
 | 
			
		||||
  | BrightRed -> "\027[91m"
 | 
			
		||||
  | BrightGreen -> "\027[92m"
 | 
			
		||||
  | BrightYellow -> "\027[9m"
 | 
			
		||||
  | BrightBlue -> "\027[94m"
 | 
			
		||||
  | BrightMagenta -> "\027[95m"
 | 
			
		||||
  | BrightCyan -> "\027[96m"
 | 
			
		||||
  | BrightWhite -> "\027[97m"
 | 
			
		||||
  | Default -> "\027[39m" (* Reset to default foreground color *)
 | 
			
		||||
  | Custom rgb -> (
 | 
			
		||||
      let ansi = rgb_to_ansi rgb in
 | 
			
		||||
      match ansi with Ok s -> Printf.sprintf "\027[38m;5;%d" s | Error _ -> "")
 | 
			
		||||
 | 
			
		||||
let bgcolor_to_ansi = function
 | 
			
		||||
  | Black -> "\027[40m"
 | 
			
		||||
  | Red -> "\027[41m"
 | 
			
		||||
  | Green -> "\027[42m"
 | 
			
		||||
  | Yellow -> "\027[4m"
 | 
			
		||||
  | Blue -> "\027[44m"
 | 
			
		||||
  | Magenta -> "\027[45m"
 | 
			
		||||
  | Cyan -> "\027[46m"
 | 
			
		||||
  | White -> "\027[47m"
 | 
			
		||||
  | Gray -> "\027[100m"
 | 
			
		||||
  | BrightRed -> "\027[101m"
 | 
			
		||||
  | BrightGreen -> "\027[102m"
 | 
			
		||||
  | BrightYellow -> "\027[10m"
 | 
			
		||||
  | BrightBlue -> "\027[10m"
 | 
			
		||||
  | BrightMagenta -> "\027[105m"
 | 
			
		||||
  | BrightCyan -> "\027[106m"
 | 
			
		||||
  | BrightWhite -> "\027[107m"
 | 
			
		||||
  | Default -> "\027[49m" (* Reset to default background color *)
 | 
			
		||||
  | Custom rgb -> (
 | 
			
		||||
      let ansi = rgb_to_ansi rgb in
 | 
			
		||||
      match ansi with Ok s -> Printf.sprintf "\027[48m;5;%d" s | Error _ -> "")
 | 
			
		||||
 | 
			
		||||
(* Function to get the ANSI escape code for a style *)
 | 
			
		||||
let style_to_ansi = function
 | 
			
		||||
  | Reset -> "\027[0m"
 | 
			
		||||
  | Bold -> "\027[1m"
 | 
			
		||||
  | Underlined -> "\027[4m"
 | 
			
		||||
  | Blink -> "\027[5m"
 | 
			
		||||
  | Inverse -> "\027[7m"
 | 
			
		||||
  | Hidden -> "\027[8m"
 | 
			
		||||
 | 
			
		||||
let color_style_to_ansi = function
 | 
			
		||||
  | Foreground color -> color_to_ansi color
 | 
			
		||||
  | Background color -> bgcolor_to_ansi color
 | 
			
		||||
(* You would define background colors as well *)
 | 
			
		||||
 | 
			
		||||
let string_of_ansi = function
 | 
			
		||||
  | ColorStyle op -> color_style_to_ansi op
 | 
			
		||||
  | Cursor op -> cursorop_to_ansi op
 | 
			
		||||
  | Style op -> style_to_ansi op
 | 
			
		||||
  | Screen op -> screenop_to_ansi op 
 | 
			
		||||
 | 
			
		||||
(* let cus_for = color_to_ansi Foreground Custom (3; 4; 1)) *)
 | 
			
		||||
let pixel_ansi offset color_style =
 | 
			
		||||
  let color_ansi = color_style_to_ansi color_style in
 | 
			
		||||
  let move_ansi = cursorop_to_ansi (Move offset) in
 | 
			
		||||
  let ansi = Printf.sprintf "%s%s%s" move_ansi color_ansi block in
 | 
			
		||||
  ansi
 | 
			
		||||
 | 
			
		||||
(* Gets the terminal height and width from running `stty size` *)
 | 
			
		||||
let termsize: offset =
 | 
			
		||||
  let command = "stty size" in
 | 
			
		||||
  let in_channel = open_process_in command in
 | 
			
		||||
  try
 | 
			
		||||
   let line = input_line in_channel in
 | 
			
		||||
   close_in in_channel;
 | 
			
		||||
   let parts = String.split_on_char ' ' line in
 | 
			
		||||
   match parts with
 | 
			
		||||
   | [height; width] ->
 | 
			
		||||
     {x = (int_of_string width); y = (int_of_string height)}
 | 
			
		||||
   | _ ->
 | 
			
		||||
     let msg = Printf.sprintf "Unexpected output from stty size: %s" line in
 | 
			
		||||
     failwith msg
 | 
			
		||||
  with
 | 
			
		||||
  | End_of_file ->
 | 
			
		||||
    close_in in_channel;
 | 
			
		||||
    failwith "No output from `stty size`"
 | 
			
		||||
  | Sys_error msg ->
 | 
			
		||||
    close_in in_channel;
 | 
			
		||||
    Printf.eprintf "Error reading input: %s\n" msg;
 | 
			
		||||
    failwith "Failed to get terminal size"
 | 
			
		||||
  | Failure msg ->
 | 
			
		||||
    Printf.eprintf "Error converting size to int: %s\n" msg;
 | 
			
		||||
    failwith "Failed to parse terminal size"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								lib/ansi.mli
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								lib/ansi.mli
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
type offset = { x : int; y : int }
 | 
			
		||||
type ansirgb = int * int * int
 | 
			
		||||
 | 
			
		||||
type cursor_ops = Hide | Show | Move of offset
 | 
			
		||||
type screen_ops = Clear
 | 
			
		||||
 | 
			
		||||
type color =
 | 
			
		||||
  | Black
 | 
			
		||||
  | Red
 | 
			
		||||
  | Green
 | 
			
		||||
  | Yellow
 | 
			
		||||
  | Blue
 | 
			
		||||
  | Magenta
 | 
			
		||||
  | Cyan
 | 
			
		||||
  | White
 | 
			
		||||
  | Gray
 | 
			
		||||
  | BrightRed
 | 
			
		||||
  | BrightGreen
 | 
			
		||||
  | BrightYellow
 | 
			
		||||
  | BrightBlue
 | 
			
		||||
  | BrightMagenta
 | 
			
		||||
  | BrightCyan
 | 
			
		||||
  | BrightWhite
 | 
			
		||||
  | Default
 | 
			
		||||
  | Custom of ansirgb
 | 
			
		||||
 | 
			
		||||
type color_style =
 | 
			
		||||
  | Foreground of color
 | 
			
		||||
  | Background of color
 | 
			
		||||
 | 
			
		||||
type style =
 | 
			
		||||
  | Reset
 | 
			
		||||
  | Bold
 | 
			
		||||
  | Underlined
 | 
			
		||||
  | Blink
 | 
			
		||||
  | Inverse
 | 
			
		||||
  | Hidden
 | 
			
		||||
 | 
			
		||||
type ansi = 
 | 
			
		||||
  | Cursor of cursor_ops
 | 
			
		||||
  | Screen of screen_ops
 | 
			
		||||
  | ColorStyle of color_style
 | 
			
		||||
  | Style of style
 | 
			
		||||
 | 
			
		||||
val string_of_ansi: ansi -> string
 | 
			
		||||
val termsize: offset
 | 
			
		||||
val pixel_ansi: offset -> color_style -> string
 | 
			
		||||
val top_block: string
 | 
			
		||||
val bot_block: string
 | 
			
		||||
val block: string
 | 
			
		||||
 | 
			
		||||
val black: color_style
 | 
			
		||||
val red: color_style
 | 
			
		||||
val green: color_style
 | 
			
		||||
val yellow: color_style
 | 
			
		||||
val blue: color_style
 | 
			
		||||
val magenta: color_style
 | 
			
		||||
val cyan: color_style
 | 
			
		||||
val white: color_style
 | 
			
		||||
val default: color_style
 | 
			
		||||
val bg_black: color_style
 | 
			
		||||
val bg_red: color_style
 | 
			
		||||
val bg_green: color_style
 | 
			
		||||
val bg_yellow: color_style
 | 
			
		||||
val bg_blue: color_style
 | 
			
		||||
val bg_magenta: color_style
 | 
			
		||||
val bg_cyan: color_style
 | 
			
		||||
val bg_white: color_style
 | 
			
		||||
val bg_default: color_style
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								lib/dune
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								lib/dune
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
(library
 | 
			
		||||
 (name tuiano)
 | 
			
		||||
 (libraries unix)
 | 
			
		||||
 (preprocess (pps ppx_deriving.show)))
 | 
			
		||||
							
								
								
									
										7
									
								
								lib/ex.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/ex.ml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
let greet name = 
 | 
			
		||||
  let message = "Greetings, " ^ name in
 | 
			
		||||
  print_endline message
 | 
			
		||||
 | 
			
		||||
let v = String.split_on_char ' ' "Hello using ppx heyo!!"
 | 
			
		||||
 | 
			
		||||
let string_of_string_list = [% show: string list]
 | 
			
		||||
							
								
								
									
										3
									
								
								lib/ex.mli
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lib/ex.mli
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
val greet: string -> unit
 | 
			
		||||
val string_of_string_list : string list -> string
 | 
			
		||||
val v: string list
 | 
			
		||||
							
								
								
									
										64
									
								
								lib/tui.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/tui.ml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
open Unix
 | 
			
		||||
open Ansi
 | 
			
		||||
open Widgets
 | 
			
		||||
 | 
			
		||||
let write_out line =
 | 
			
		||||
  ignore (write stdout (String.to_bytes line) 0 (String.length line))
 | 
			
		||||
 | 
			
		||||
let set_raw_mode fd =
 | 
			
		||||
  let original_attrs = tcgetattr fd in
 | 
			
		||||
  let raw_attrs = original_attrs in
 | 
			
		||||
  raw_attrs.c_ignbrk <- false; (* Do not ignore break conditions *)
 | 
			
		||||
  raw_attrs.c_icanon <- false; (* Disable canonical mode *)
 | 
			
		||||
  raw_attrs.c_echo <- false;   (* Disable echo mode *)
 | 
			
		||||
  tcsetattr fd TCSAFLUSH raw_attrs;
 | 
			
		||||
  (* Hide cursor *)
 | 
			
		||||
  write_out "\027[?25l";
 | 
			
		||||
  original_attrs (* Return original attributes *)
 | 
			
		||||
 | 
			
		||||
let restore_terminal fd original_attrs =
 | 
			
		||||
  (* Show cursor *)
 | 
			
		||||
  write_out "\027[?25h";
 | 
			
		||||
  tcsetattr fd TCSADRAIN original_attrs
 | 
			
		||||
 | 
			
		||||
let setup_terminal () =
 | 
			
		||||
  let fd = stdin in
 | 
			
		||||
  let original_attrs = set_raw_mode fd in
 | 
			
		||||
  let off: offset = {x = 1000; y = 1000;} in
 | 
			
		||||
  let color = red in
 | 
			
		||||
  let {x = term_x; y = term_y} = termsize in
 | 
			
		||||
  (* let out_channel = out_channel_of_descr stdout in *)
 | 
			
		||||
  write_out "Raw mode enabled.\n";
 | 
			
		||||
  (* flush out_channel; *)
 | 
			
		||||
  sleepf 0.5;
 | 
			
		||||
  let cursor = Cursor (Move {x = term_x; y = term_y}) |> string_of_ansi in
 | 
			
		||||
    (* Printf.sprintf "\027[%d;%dH" (term_y - 10) (termsize.x - 10) in *)
 | 
			
		||||
  (*       clear screen  cursor to pos   text   *)
 | 
			
		||||
  let line = "\027[2J" ^ cursor ^ "Test 123\n" in
 | 
			
		||||
  let screen = Column(
 | 
			
		||||
    default_column_opts,
 | 
			
		||||
    [
 | 
			
		||||
      Text "Testing 123";
 | 
			
		||||
      Row(
 | 
			
		||||
        default_row_opts,
 | 
			
		||||
        [
 | 
			
		||||
          Padding(
 | 
			
		||||
            default_padding_opts,
 | 
			
		||||
            Text "Padded child"
 | 
			
		||||
          )
 | 
			
		||||
        ]
 | 
			
		||||
      )
 | 
			
		||||
    ]) 
 | 
			
		||||
    |> string_of_widget in
 | 
			
		||||
  write_out line;
 | 
			
		||||
  (* flush out_channel; *)
 | 
			
		||||
  sleep 1;
 | 
			
		||||
  write_out screen;
 | 
			
		||||
  sleep 1;
 | 
			
		||||
  write_out "test this one\n";
 | 
			
		||||
  sleep 1;
 | 
			
		||||
  write_out (pixel_ansi off color);
 | 
			
		||||
  write_out "and another";
 | 
			
		||||
  sleep 1;
 | 
			
		||||
  restore_terminal fd original_attrs
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										184
									
								
								lib/widgets.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								lib/widgets.ml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,184 @@
 | 
			
		||||
open Ansi
 | 
			
		||||
 | 
			
		||||
type bounded_constraints = {
 | 
			
		||||
  max_width: int option;
 | 
			
		||||
  min_width: int option;
 | 
			
		||||
  max_height: int option;
 | 
			
		||||
  min_height: int option;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type constraints = 
 | 
			
		||||
  | Unbounded
 | 
			
		||||
  | Bounded of bounded_constraints
 | 
			
		||||
 | 
			
		||||
type size = {
 | 
			
		||||
  height: int;
 | 
			
		||||
  width: int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type alignment =
 | 
			
		||||
  | Start
 | 
			
		||||
  | Center
 | 
			
		||||
  | End
 | 
			
		||||
  | Stretch
 | 
			
		||||
  | Even
 | 
			
		||||
 | 
			
		||||
type cross_alignment =
 | 
			
		||||
  | Start
 | 
			
		||||
  | Center
 | 
			
		||||
  | End
 | 
			
		||||
  | Stretch
 | 
			
		||||
  | Even
 | 
			
		||||
 | 
			
		||||
type box_options = {
 | 
			
		||||
  alignment: alignment;
 | 
			
		||||
  cross_alignment: cross_alignment;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type padding_options = {
 | 
			
		||||
  left: int;
 | 
			
		||||
  right: int;
 | 
			
		||||
  bottom: int;
 | 
			
		||||
  top: int;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type widget_options = 
 | 
			
		||||
  | Box of box_options
 | 
			
		||||
  | Padding of padding_options
 | 
			
		||||
 | 
			
		||||
let default_padding_opts = {
 | 
			
		||||
    left = 0;
 | 
			
		||||
    right = 0;
 | 
			
		||||
    bottom = 0;
 | 
			
		||||
    top = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
let default_column_opts = {
 | 
			
		||||
    alignment = Start;
 | 
			
		||||
    cross_alignment = Center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
let default_row_opts = {
 | 
			
		||||
    alignment = Start;
 | 
			
		||||
    cross_alignment = Center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
type ansi_widget = 
 | 
			
		||||
  | Column of box_options * ansi_widget list
 | 
			
		||||
  | Row of box_options * ansi_widget list
 | 
			
		||||
  | Text of string
 | 
			
		||||
  | Padding of padding_options * ansi_widget
 | 
			
		||||
 | 
			
		||||
type widget_element =
 | 
			
		||||
  | ElementColumn of constraints * size ref * box_options * widget_element list
 | 
			
		||||
  | ElementRow of constraints * size ref * box_options * widget_element list
 | 
			
		||||
  | ElementText of constraints * size ref * string
 | 
			
		||||
  | ElementPadding of constraints * size ref * padding_options * widget_element
 | 
			
		||||
 | 
			
		||||
let size_of_widget_element = function
 | 
			
		||||
  | ElementColumn (_, s, _, _) -> !s
 | 
			
		||||
  | ElementRow (_, s, _, _) -> !s
 | 
			
		||||
  | ElementText (_, s, _) -> !s
 | 
			
		||||
  | ElementPadding (_, s, _, _) -> !s
 | 
			
		||||
 | 
			
		||||
(* let descend_widget_tree_list widget_list = *) 
 | 
			
		||||
  (* let widgets = List.iter descend_widget_tree widge_list in *)
 | 
			
		||||
  (* List.fold_left (^) widgets *)
 | 
			
		||||
 | 
			
		||||
let rec make_spacer = function
 | 
			
		||||
  | 0 -> ""
 | 
			
		||||
  | n -> " " ^ make_spacer (n - 1)
 | 
			
		||||
 | 
			
		||||
let rec descend_widget_tree depth widget_tree  =
 | 
			
		||||
  match widget_tree with
 | 
			
		||||
  | Column (c_opts, widgets) -> 
 | 
			
		||||
    let _ = c_opts in
 | 
			
		||||
    let deeper = depth + 1 in
 | 
			
		||||
    let widget_strings = List.map (descend_widget_tree deeper) widgets in
 | 
			
		||||
    let line = String.concat "" widget_strings in
 | 
			
		||||
    let spacer = make_spacer depth in
 | 
			
		||||
    spacer ^ "Column ->\n" ^ line
 | 
			
		||||
 | 
			
		||||
  | Row (r_opts, widgets) -> 
 | 
			
		||||
    let _ = r_opts in
 | 
			
		||||
    let deeper = depth + 1 in
 | 
			
		||||
    let widget_strings = List.map (descend_widget_tree deeper) widgets in
 | 
			
		||||
    let line = String.concat "" widget_strings in
 | 
			
		||||
    let spacer = make_spacer depth in
 | 
			
		||||
    spacer ^ "Row ->\n" ^ line
 | 
			
		||||
  | Text s -> 
 | 
			
		||||
    let spacer = make_spacer depth in
 | 
			
		||||
    spacer ^ Printf.sprintf "Text: \"%s\"\n" s
 | 
			
		||||
  | Padding (_, widget) -> 
 | 
			
		||||
    let spacer = make_spacer depth in
 | 
			
		||||
    spacer ^ "Padding ->\n" ^ descend_widget_tree (depth + 1) widget
 | 
			
		||||
 | 
			
		||||
let debug_string_of_widget ansi_widget =
 | 
			
		||||
  (* let {x = term_x; y = term_y} = termsize in *)
 | 
			
		||||
  descend_widget_tree 0 ansi_widget
 | 
			
		||||
 | 
			
		||||
let max_constraints =
 | 
			
		||||
  let {x = term_x; y = term_y} = termsize in 
 | 
			
		||||
  Bounded {max_width = Some term_x; max_height = Some term_y; min_height = Some 0; min_width = Some 0}
 | 
			
		||||
 | 
			
		||||
(**
 | 
			
		||||
1. Layout - Descend tree, parent passing down constraints, child passing up size
 | 
			
		||||
2. Convert ansi_widget tree to widget_element tree
 | 
			
		||||
**)
 | 
			
		||||
 | 
			
		||||
let layout_tree ansi_widget =
 | 
			
		||||
  let filter_sizes elements =  List.map size_of_widget_element elements in
 | 
			
		||||
  let sum_sizes_col = (fun {height = h; width = w} element ->
 | 
			
		||||
      {height = h + element.height; width = max w element.width}
 | 
			
		||||
    ) in
 | 
			
		||||
  let sum_sizes_row = (fun {height = h; width = w} element ->
 | 
			
		||||
      {height = max h element.height; width = w + element.width}
 | 
			
		||||
    ) in
 | 
			
		||||
  let subtract_bound some_x x =
 | 
			
		||||
    match some_x with
 | 
			
		||||
    | Some i -> Some (max (x - i) 0)
 | 
			
		||||
    | None -> Some x
 | 
			
		||||
      in
 | 
			
		||||
  let rec layout_tree_list constraints widgets =
 | 
			
		||||
    let constrained_layout = layout_tree_rec constraints in
 | 
			
		||||
    let elements = List.map constrained_layout widgets in
 | 
			
		||||
    elements
 | 
			
		||||
  and layout_tree_rec constraints widget_tree =
 | 
			
		||||
    let size = ref {height = 0; width = 0} in
 | 
			
		||||
    match widget_tree with
 | 
			
		||||
    | Column (c_opts, widgets) ->
 | 
			
		||||
      let widget_elements = (layout_tree_list constraints widgets) in
 | 
			
		||||
      let sizes = filter_sizes widget_elements in
 | 
			
		||||
      size := List.fold_left sum_sizes_col {height = 0; width = 0} sizes;
 | 
			
		||||
      ElementColumn( constraints, size, c_opts, widget_elements)
 | 
			
		||||
    | Row (r_opts, widgets) -> 
 | 
			
		||||
      let widget_elements = (layout_tree_list constraints widgets) in
 | 
			
		||||
      let sizes = filter_sizes widget_elements in
 | 
			
		||||
      size := List.fold_left sum_sizes_row {height = 0; width = 0} sizes;
 | 
			
		||||
      ElementRow( constraints, size, r_opts, widget_elements)
 | 
			
		||||
    | Text (s) -> 
 | 
			
		||||
      size := {height = 1; width = String.length s};
 | 
			
		||||
      ElementText (constraints, size, s)
 | 
			
		||||
    | Padding (p_opts, widget) -> 
 | 
			
		||||
      let vertical_padding = p_opts.top + p_opts.bottom in
 | 
			
		||||
      let horizontal_padding = p_opts.left + p_opts.right in
 | 
			
		||||
      let new_constraints = match constraints with
 | 
			
		||||
        | Bounded cons -> Bounded {
 | 
			
		||||
            max_height = (subtract_bound cons.max_height vertical_padding);
 | 
			
		||||
            max_width = (subtract_bound cons.max_width horizontal_padding);
 | 
			
		||||
            min_height = Some vertical_padding;
 | 
			
		||||
            min_width = Some horizontal_padding; }
 | 
			
		||||
        | Unbounded -> Bounded {
 | 
			
		||||
            max_height = None;
 | 
			
		||||
            max_width = None;
 | 
			
		||||
            min_height = Some vertical_padding;
 | 
			
		||||
            min_width = Some horizontal_padding;
 | 
			
		||||
             }
 | 
			
		||||
      in
 | 
			
		||||
      let element = layout_tree_rec new_constraints widget in 
 | 
			
		||||
      let element_size = size_of_widget_element element in
 | 
			
		||||
      size := {width = horizontal_padding + element_size.width; height = vertical_padding + element_size.width};
 | 
			
		||||
      ElementPadding (constraints, size, p_opts, element)
 | 
			
		||||
  in
 | 
			
		||||
  let size = ref {height = 0; width = 0} in
 | 
			
		||||
  layout_tree_rec max_constraints ansi_widget
 | 
			
		||||
							
								
								
									
										0
									
								
								test/test_tuiano.ml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								test/test_tuiano.ml
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										31
									
								
								tuiano.opam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tuiano.opam
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
# This file is generated by dune, edit dune-project instead
 | 
			
		||||
opam-version: "2.0"
 | 
			
		||||
synopsis: "A Piano keyboard on the TUI"
 | 
			
		||||
description: "A Piano keyboard on the TUI"
 | 
			
		||||
maintainer: ["Nate Anderson"]
 | 
			
		||||
authors: ["Nate Anderson"]
 | 
			
		||||
license: "LICENSE"
 | 
			
		||||
tags: ["topics" "to describe" "your" "project"]
 | 
			
		||||
homepage: "https://github.com/username/reponame"
 | 
			
		||||
doc: "https://fosscat.com"
 | 
			
		||||
bug-reports: "https://github.com/username/reponame/issues"
 | 
			
		||||
depends: [
 | 
			
		||||
  "ocaml"
 | 
			
		||||
  "dune" {>= "3.16"}
 | 
			
		||||
  "odoc" {with-doc}
 | 
			
		||||
]
 | 
			
		||||
build: [
 | 
			
		||||
  ["dune" "subst"] {dev}
 | 
			
		||||
  [
 | 
			
		||||
    "dune"
 | 
			
		||||
    "build"
 | 
			
		||||
    "-p"
 | 
			
		||||
    name
 | 
			
		||||
    "-j"
 | 
			
		||||
    jobs
 | 
			
		||||
    "@install"
 | 
			
		||||
    "@runtest" {with-test}
 | 
			
		||||
    "@doc" {with-doc}
 | 
			
		||||
  ]
 | 
			
		||||
]
 | 
			
		||||
dev-repo: "git+https://github.com/username/reponame.git"
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user