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