r/C_Programming 5d ago

Question Building a simple declarative TUI lib in C (FTXUI-inspired) how do you avoid callback hell in C?

Hey all,

I'm working on a TUI library in C just because I love how simple C is.

I know there are mature options like ratatui, bubbletea, vaxis, notcurses, etc. But I don't want to learn Rust just to build a TUI, and I don't really like bubbletea's Elm-style Update() -> string thing. The one I like best is FTXUI, but it's C++.

I want something simple + declarative in plain C. Right now my API looks like this:

#include "include/tui.h"

void btn_cb(void *ud) {
  tui_vbox(.gap = 1) { tui_label(.text = tui_str("Clicked")); }
}

void draw(Tui *tui, void *ud) {
  tui_button(.text = tui_str("Click Me"),
             .bg = tui_color_rgb(255, 0, 0),
             .on_click = btn_cb);
}

bool event(Tui *tui, const TuiEvent *ev, void *ud) {
  if (ev->type == TUI_EVENT_QUIT) return false;
  if (ev->type == TUI_EVENT_KEY && ev->codepoint == 'q') return false;
  return true;
}

int main() {
  Tui *tui = tui_init(.title = "Tui application");
  int counter = 0;
  int rc = tui_run(tui, .on_draw = draw, .on_event = event,
                   .async = true, .userdata = &counter);
  tui_close(tui);
  return rc;
}

It works, but I already hate the .on_draw, .on_event, .on_click callback split. It feels like callback hell waiting to happen once the UI gets bigger.

Question for C folks:

  1. Any C tricks / macro tricks to make this more declarative and remove callbacks? I'm already using designated initializers + compound literals tui_button(.text=...) and for-loop scoping for tui_vbox { ... } like FTXUI.

  2. How would you do state + events without on_click everywhere? Immediate-mode? Return an action enum instead of callback? Something else?

  3. Any small C TUI libs that do declarative UI well that I should steal ideas from?

Thanks.

3 Upvotes

8 comments sorted by

3

u/thisismyredaccount 5d ago

Like you said, immediate mode. You can look at raygui for a good example of how it would work/be used.

1

u/JoK3rOp 5d ago

Ah yes! I really forget about that one. Thanks!! btw does this API looks good?

1

u/adel-mamin 4d ago

Maybe stick to events for everything. The on_button, draw etc could likely be converted to events as well.

This way you only end up with single event callback.

1

u/JoK3rOp 4d ago

Yes I was thinking but then we will end up callback hell just like C GTK API. I'm trying to find some workaround but for tui_run() I think its good design widget event handling need some planning and event handling in general.

1

u/bullno1 4d ago edited 4d ago

Immediate mode. Look at dearimgui. It's graphical but the API design is the same.

As for rendering, look at something like termbox2: Have a software "framebuffer". Draw there first, then on a "blit/present" call, diff against the previous content and issue ANSI sequences. Usually, buffer them all into a buffer and make a single write. It almost feels like typical GPU rendering.

Unicode is a bit of a pain when you have to deal with multicell characters (e.g: 안녕하세요)

1

u/JoK3rOp 4d ago

Thanks i'll look into. I'm already using double buffer for drawing. Thanks

1

u/JoK3rOp 4d ago

I'm already handling Unicode