genderphasing

fatuilog 1: introducing fatui

if you speak italian the name is silly

mood: relieveddevlogfatui

last time i mentioned that

[…]the curse has once again stirred in its sleep, and in the wreckage i have acquired…

a new project

nyehehehe

b/rb/log 5: refactoréd, me!

that new project is fatui, a textmode ui crate for huge nerds. usage looks a bit like this:

let mut backend = fatui::open();
let mut system = sysinfo::System::new();
system.refresh_processes(ProcessesToUpdate::All, true);
while let Some(frame) = backend.step() {
  let (header, body) = frame.split(rows!(1, *));
  let (status, reload) = header.split(cols(*, 8));
  status.draw(text!(
    "Status: " black on_white,
    if system.processes().is_empty() {
      "DEAD" white on_red
    } else {
      "OK" green on_white
    },
  ));
  if reload.draw(button!(white on_dark_grey " reload ")).clicked {
    // TODO: do this in a background thread
    system.refresh_processes(ProcessesToUpdate::All, true);
  }
  body.draw(ProcessViewer(system.processes()));
}

now, you may notice that there's no fatui project link on the front page. that's on purpose: i've just started this project! i don't wanna write a bunch of docs just to end up changing everything.

for example, there are a lot of important features left out of that tiny example: scrollable areas, overlapping regions, modals, borders, externally triggered redraws… i more or less understand how these will work under the hood, but designing the apis is hard!

so, until things are a little more set in stone, i'm not going to be publishing tons of documentation, or even pushing this to crates.io. i expect that to be up probably soonish – you know, on my timescale, so you can probably expect fatuilog 2 sometime in august. of 2027.

that said, i do have the basic abstraction and its api set in stone. briefly:

  1. get a dyn Backend however you like, with a reasonable default being fatui::open()
  2. use backend.step() to get the next frame, which has one user input event and a character grid to draw the updated screen into
  3. use frame.split() to break the frame up into sub-frames, then those sub-frames into sub-sub-frames, etc.
  4. use frame.draw() to attach a component to a frame (or sub-frame, or sub-sub– you get it, they're all just struct Frame)
  5. use the return value of frame.draw() to get output from the component, like whether a button was just clicked.

this should feel somewhat familiar to anyone who's used ratatui, but there are two major differences:

these design decisions have some consequences, like making it somewhat more complex to externally trigger a redraw than ratatui does. but i think they're a worthwhile enough tradeoff to spend my time building this, and, well, regardless i think i'm going to have fun!

oh! and about the name. it's literally silly in italian, but it's also two more jokes: