layoutzZero-dependency library for terminal animations, compositional ANSI strings, plots, and Elm-style TUIs. In Scala, Haskell, OCaml, and Clojure.
Scala
Haskell
OCaml
Clojure
import layoutz._
val demo = layout(
row(
"Layoutz".style(Style.Bold),
underline("^", Color.Cyan)("DEMO")
).center(),
br,
row(
statusCard("Users", "1.2K"),
statusCard("API", "UP").border(Border.Double),
statusCard("CPU", "23%").border(Border.Thick).color(Color.Red),
table(
Seq("Name", "Role", "Skills"),
Seq(
Seq("Gegard", "Pugilist",
ul("Armenian", ul("bad", ul("man")))),
Seq("Eve", "QA", "Testing")
)
).border(Border.Round).style(Style.Reverse)
)
)
demo.putStrLn
import Layoutz
demo = layout
[ center $ row
[ withStyle StyleBold $ text "Layoutz"
, underlineColored "^" ColorCyan $ text "DEMO"
]
, br
, row
[ statusCard "Users" "1.2K"
, withBorder BorderDouble $ statusCard "API" "UP"
, withColor ColorRed $ withBorder BorderThick $
statusCard "CPU" "23%"
, withStyle StyleReverse $ withBorder BorderRound $
table ["Name", "Role", "Skills"]
[ [text "Gegard", text "Pugilist",
ul [text "Armenian",
ul [text "bad", ul [text "man"]]]]
, [text "Eve", text "QA", text "Testing"]
]
]
]
main = putStrLn $ render demo
open Layoutz
let demo =
layout
[ center
(row
[ s "Layoutz" |> styleBold
; underlineColored ~char:"^" ~color:Color.cyan (s "DEMO")
])
; br
; row
[ statusCard ~label:(s "Users") ~content:(s "1.2K")
; statusCard ~label:(s "API") ~content:(s "UP") |> borderDouble
; statusCard ~label:(s "CPU") ~content:(s "23%")
|> borderThick |> colorRed
; table
~headers:[ s "Name"; s "Role"; s "Skills" ]
[ [ s "Gegard"; s "Pugilist"
; ul [ li ~c:[ li ~c:[ li (s "man") ]
(s "bad") ] (s "Armenian") ] ]
; [ s "Eve"; s "QA"; s "Testing" ] ]
|> borderRound |> styleReverse
]
]
let () = print demo
(require '[layoutz.core :refer :all])
(def demo
(layout
[(center
(row
[(-> "Layoutz" style-bold)
(underline-colored "DEMO" "^" cyan)]))
br
(row
[(status-card "Users" "1.2K")
(-> (status-card "API" "UP") border-double)
(-> (status-card "CPU" "23%") border-thick color-red)
(-> (table ["Name" "Role" "Skills"]
[["Gegard" "Pugilist"
(ul [(li "Armenian"
:c [(li "bad"
:c [(li "man")])])])]
["Eve" "QA" "Testing"]])
border-round style-reverse)])]))
(print-elem demo)
Showcase
src: ShowcaseApp.scala | ShowcaseApp.hs | showcase_app.ml | showcase.clj Inline animations
Scala
Haskell
OCaml
Clojure
import layoutz._
case class St(progress: Double, done: Int)
case object Tick
object Loader extends LayoutzApp[St, Tick.type] {
def init = (St(0, 0), Cmd.none)
def update(msg: Tick.type, s: St) = {
if (s.done > 30) (s, Cmd.exit)
else if (s.progress >= 1.0) (s.copy(done = s.done + 1), Cmd.none)
else (s.copy(progress = math.min(1.0, s.progress + 0.008)), Cmd.none)
}
def subscriptions(s: St) = Sub.time.everyMs(16, Tick)
def view(s: St) = {
val w = 40; val filled = (s.progress * w).toInt
val bar = (0 until w).map { i =>
if (i < filled) { val r = i.toDouble / w
"█".color(Color.True((r*180).toInt+50, ((1-r)*200).toInt+55, 255))
} else "░".color(Color.BrightBlack)
}
layout(rowTight(bar: _*),
s"Linking... ${(s.progress*100).toInt}%".color(Color.BrightCyan))
}
}
println("hello from a normal process")
println("doing some work...")
println("now watch this:")
Loader.run(clearOnStart = false, clearOnExit = false)
println("back to normal output")
import Layoutz
data St = St { progress :: Double, done :: Int }
data Msg = Tick
app :: LayoutzApp St Msg
app = LayoutzApp
{ appInit = (St 0.0 0, CmdNone)
, appUpdate = \Tick st ->
if done st > 30 then (st, CmdExit)
else if progress st >= 1.0
then (st { done = done st + 1 }, CmdNone)
else (st { progress = min 1.0 (progress st + 0.008) }, CmdNone)
, appSubscriptions = \_ -> subEveryMs 16 Tick
, appView = \st ->
let w = 40
f = floor (progress st * fromIntegral w)
bar = map (\i ->
let r = fromIntegral i / fromIntegral w
in withColor (ColorTrue (floor(r*180)+50)
(floor((1-r)*200)+55) 255) $ text "█") [0..f-1]
emp = replicate (w-f) (withColor ColorBrightBlack $ text "░")
pct = show (floor (progress st * 100)) ++ "%"
in layout [ tightRow (bar ++ emp)
, withColor ColorBrightCyan $ text $ "Linking... " <> pct ]
}
main = do
putStrLn "hello from a normal process"
putStrLn "doing some work..."
putStrLn "now watch this:"
runApp app
putStrLn "back to normal output"
open Layoutz
type st = { progress : float; done_ticks : int }
type msg = Tick
let loader =
{ init = ({ progress = 0.0; done_ticks = 0 }, CmdNone);
update = (fun Tick st ->
if st.done_ticks > 30 then (st, CmdExit)
else if st.progress >= 1.0
then ({ st with done_ticks = st.done_ticks + 1 }, CmdNone)
else ({ st with progress = min 1.0 (st.progress +. 0.008) }, CmdNone));
subscriptions = (fun _ -> sub_every_ms 16 Tick);
view = (fun st ->
let w = 40 in
let filled = int_of_float (st.progress *. float_of_int w) in
let bar = List.init w (fun i ->
if i < filled then
let r = float_of_int i /. float_of_int w in
s "█" |> colorRGB (int_of_float (r *. 180.) + 50)
(int_of_float ((1. -. r) *. 200.) + 55) 255
else s "░" |> colorBrightBlack) in
let pct = Printf.sprintf "%d%%" (int_of_float (st.progress *. 100.)) in
layout [ tightRow bar; s ("Linking... " ^ pct) |> colorBrightCyan ]);
}
let () =
let opts = { default_options with
clear_on_start = false; clear_on_exit = false } in
print_endline "hello from a normal process";
print_endline "doing some work...";
print_endline "now watch this:";
run_app ~options:opts loader;
print_endline "back to normal output"
(require '[layoutz.core :refer :all])
(def loader
{:init (fn [] [{:progress 0.0 :done 0} (cmd-none)])
:update
(fn [msg st]
(case msg
:tick (cond
(> (:done st) 30) [st (cmd-exit)]
(>= (:progress st) 1.0)
[(update st :done inc) (cmd-none)]
:else
[(update st :progress #(min 1.0 (+ % 0.008)))
(cmd-none)])
[st (cmd-none)]))
:subscriptions (fn [_] (sub-every-ms 16 :tick))
:view
(fn [{:keys [progress]}]
(let [w 40 filled (int (* progress w))
bar (map (fn [i]
(if (< i filled)
(let [r (/ (double i) w)]
(-> "█" (color-rgb (+ (int (* r 180)) 50)
(+ (int (* (- 1.0 r) 200)) 55) 255)))
(-> "░" color-bright-black)))
(range w))]
(layout [(tight-row bar)
(-> (str "Linking... " (int (* progress 100)) "%")
color-bright-cyan)])))})
(println "hello from a normal process")
(println "doing some work...")
(println "now watch this:")
(run-inline loader)
(println "back to normal output")
Interactive TUIs
Scala
Haskell
OCaml
Clojure
import layoutz._
object CounterApp extends LayoutzApp[Int, String] {
def init = (0, Cmd.none)
def update(msg: String, count: Int) = msg match {
case "inc" => (count + 1, Cmd.none)
case "dec" => (count - 1, Cmd.none)
case _ => (count, Cmd.none)
}
def subscriptions(count: Int) =
Sub.onKeyPress {
case Key.Char('+') => Some("inc")
case Key.Char('-') => Some("dec")
case _ => None
}
def view(count: Int) = layout(
section("Counter")(s"Count: $count"),
br,
ul("Press `+` or `-`")
)
}
CounterApp.run
import Layoutz
data CounterMsg = Inc | Dec
counterApp :: LayoutzApp Int CounterMsg
counterApp = LayoutzApp
{ appInit = (0, CmdNone)
, appUpdate = \msg count -> case msg of
Inc -> (count + 1, CmdNone)
Dec -> (count - 1, CmdNone)
, appSubscriptions = \_state ->
subKeyPress $ \key -> case key of
KeyChar '+' -> Just Inc
KeyChar '-' -> Just Dec
_ -> Nothing
, appView = \count ->
layout
[ section "Counter" [text $ "Count: " <> show count]
, br
, ul ["Press '+' or '-'", "Press ESC to quit"]
]
}
main = runApp counterApp
open Layoutz
type msg = Inc | Dec
let () =
run_app
{ init = (0, CmdNone);
update =
(fun msg count ->
match msg with
| Inc -> (count + 1, CmdNone)
| Dec -> (count - 1, CmdNone));
subscriptions =
(fun _ ->
sub_key_press (fun key ->
match key with
| KeyChar '+' -> Some Inc
| KeyChar '-' -> Some Dec
| _ -> None));
view =
(fun count ->
layout
[ section ~title:"Counter"
[ s ("Count: " ^ string_of_int count) ];
br;
ul [ li (s "Press `+` or `-`") ]
]);
}
(require '[layoutz.core :refer :all])
(run-app
{:init (fn [] [{:n 0} (cmd-none)])
:update (fn [msg state]
(case msg
:inc [(update state :n inc) (cmd-none)]
:dec [(update state :n dec) (cmd-none)]
[state (cmd-none)]))
:subscriptions (fn [_]
(sub-key-press
(fn [{:keys [type]}]
(case type :up :inc :down :dec nil))))
:view (fn [{:keys [n]}]
(layout
[(section "Counter" (str "Count: " n))
br
(ul ["Press up/down" "Ctrl-Q to quit"])]))})
|