axui-growth-chamber/src/ctxt/ctxt.c

346 lines
8.7 KiB
C

#include "../ax.h"
#include "../backend.h"
#include "../concurrent/msg.h"
#include "../concurrent/msgq.h"
#include "../ctxt.h"
#include "../util.h"
#include "theme.h"
#include "window.h"
#include <unistd.h>
/* -----------------------------------------------------------------------------
* API functions :: init / cleanup
* -------------------------------------------------------------------------- */
struct ax_ctxt* ax_new(void)
{
// "bootstrap" a new region
struct rgn rgn0;
rgn_init(&rgn0, SMALL);
struct rgn* rgn = rmemdup_typed(&rgn0, struct rgn, &rgn0, 1);
// use it to make a new context
struct ax_ctxt* ax = ralloc_typed(rgn, struct ax_ctxt, 1);
ax__ctxt_init(ax, rgn);
return ax;
}
void ax_free(struct ax_ctxt* ax)
{
if (ax != NULL) {
ax__ctxt_cleanup(ax);
rgn_cleanup(ax->init_rgn);
}
}
const char* ax_get_error(struct ax_ctxt* ax)
{
return ax->err;
}
/* -----------------------------------------------------------------------------
* Internal 'ax_ctxt' operations
* -------------------------------------------------------------------------- */
static void spawn_backend(
struct rgn* init_rgn,
pthread_t* out_thid,
struct ax_backend** out_backend);
void ax__ctxt_init(struct ax_ctxt* ax, struct rgn* init_rgn)
{
/* init simple stuff */
ax->init_rgn = init_rgn;
ax->log_out = NULL;
ax->log_auto_close = false;
ax->err_rgn = rgn_new(init_rgn, SMALL);
ax->err = "";
ax->thmb_rgn = rgn_new(init_rgn, THEME_BUILDER_DESIRED_REGION_SIZE);
ax->thmb = NULL;
ax->sel_thm = NULL;
ax->winb_rgn = rgn_new(init_rgn, WINDOW_BUILDER_DESIRED_REGION_SIZE);
ax->winb = NULL;
ax->sel_win = NULL;
ax->bac = NULL;
spawn_backend(init_rgn, &ax->bac_thid, &ax->bac);
ASSERT(ax->bac != NULL, "backend wasn't initialized");
}
static void shutdown(struct msgq* mq, pthread_t thid);
void ax__ctxt_cleanup(struct ax_ctxt* ax)
{
shutdown(ax__backend_msgq(ax->bac), ax->bac_thid);
if (ax->log_auto_close) {
fclose(ax->log_out);
}
}
/* -----------------------------------------------------------------------------
* API functions :: logging
* -------------------------------------------------------------------------- */
void ax_set_logger(struct ax_ctxt* ax, int fd, bool auto_close)
{
if (ax->log_auto_close) {
fclose(ax->log_out);
}
ax->log_out = fdopen(fd, "w");
ax->log_auto_close = auto_close;
}
void ax_log(struct ax_ctxt* ax, const char* string)
{
if (ax->log_out != NULL) {
fprintf(ax->log_out, "[LOG] %s\n", string);
}
}
/* -----------------------------------------------------------------------------
* API functions :: themes
* -------------------------------------------------------------------------- */
void ax_begin_theme(struct ax_ctxt* ax)
{
rgn_clear(ax->thmb_rgn);
ax->thmb = ax__theme_builder_new(ax->thmb_rgn);
}
struct ax_theme* ax_end_theme(struct ax_ctxt* ax)
{
if (ax->thmb == NULL) {
ax_log(ax, "`ax_end_theme' called while not building a theme");
return NULL;
}
struct ax_theme* thm = ax__theme_builder_finish(
ax->thmb,
ax->init_rgn,
ax__backend_msgq(ax->bac));
ax->thmb = NULL;
rgn_clear(ax->thmb_rgn);
return thm;
}
void ax_set_theme_color(
struct ax_ctxt* ax, const char* cat_name, int64_t rgb)
{
if (ax->thmb == NULL) {
ax_log(ax, "`ax_set_theme_color' called while not building a theme");
return;
}
enum ax_color_cat cat;
if (ax__string_to_color_cat(cat_name, &cat) != 0) {
ax->thmb->err =
rsprintf(ax->thmb_rgn, "invalid color category `%s'", cat_name);
} else {
ax__theme_set_color(ax->thmb, cat, rgb);
}
}
void ax_set_theme_font(
struct ax_ctxt* ax, const char* cat_name, const char* fnt_path, size_t fnt_size)
{
if (ax->thmb == NULL) {
ax_log(ax, "`ax_set_theme_font' called while not building a theme");
return;
}
enum ax_font_cat cat;
if (ax__string_to_font_cat(cat_name, &cat) != 0) {
ax->thmb->err =
rsprintf(ax->thmb_rgn, "invalid font category `%s'", cat_name);
} else {
ax__theme_set_font(ax->thmb, cat, fnt_path, fnt_size);
}
}
void ax_set_theme_iconset(
struct ax_ctxt* ax, const char* iconset_dir)
{
if (ax->thmb == NULL) {
ax_log(ax, "`ax_set_theme_iconset' called while not building a theme");
return;
}
UNIMPLEMENTED();
}
void ax_theme_wait_until_loaded(struct ax_ctxt* ax, struct ax_theme* thm)
{
struct rgn rgn[1];
rgn_init(rgn, SMALL);
struct msgq* mq = msgq_new(rgn);
ax__theme_on_load(thm, mq);
msgq_begin_recv_and_wait(mq);
MSGQ_RECV_ALL(mq) {
default:
break;
}
msgq_end_recv(mq);
rgn_cleanup(rgn);
}
int ax_select_theme(struct ax_ctxt* ax, struct ax_theme* thm)
{
ASSERT_NON_NULL(thm, "theme");
if (thm->err != NULL) {
ax->err = thm->err;
return AX_ERR_THEME_INVALID;
}
if (!ax__theme_is_loaded(thm)) {
ax->err = "theme hasn't finished loading";
return AX_ERR_THEME_LOADING;
}
ax->sel_thm = thm;
ax_log(ax, "theme selected");
return 0;
}
/* -----------------------------------------------------------------------------
* API functions :: window
* -------------------------------------------------------------------------- */
void ax_begin_window(struct ax_ctxt* ax)
{
rgn_clear(ax->winb_rgn);
ax->winb = ax__window_builder_new(ax->winb_rgn);
}
void ax_set_window_title(
struct ax_ctxt* ax, const char* text)
{
if (ax->winb == NULL) {
ax_log(ax, "`ax_set_window_title' called while not building a window");
return;
}
ax__window_set_title(ax->winb, text);
}
void ax_set_window_size(
struct ax_ctxt* ax, uint64_t w, uint64_t h, bool resizable)
{
if (ax->winb == NULL) {
ax_log(ax, "`ax_set_window_size' called while not building a window");
return;
}
ax__window_set_size(ax->winb, w, h);
ax__window_set_flag(ax->winb, AX_WIN_RESIZABLE, resizable);
}
struct ax_window* ax_end_window(struct ax_ctxt* ax)
{
if (ax->winb == NULL) {
ax_log(ax, "`ax_end_window' called while not building a window");
return NULL;
}
struct ax_window* win =
ax__window_builder_finish(
ax->winb,
ax->init_rgn,
ax__backend_msgq(ax->bac));
rgn_clear(ax->winb_rgn);
ax->winb = NULL;
return win;
}
int ax_select_window(struct ax_ctxt* ax, struct ax_window* win)
{
ASSERT_NON_NULL(win, "window");
ax->sel_win = win;
ax_log(ax, "window selected");
return 0;
}
/* -----------------------------------------------------------------------------
* Backend worker thread
* -------------------------------------------------------------------------- */
struct backend_init_ctxt {
// "input"
struct rgn* rgn;
pthread_mutex_t mx;
pthread_cond_t cv;
// "output"
struct ax_backend* backend;
const char* err;
};
static void* backend_worker(void* arg);
static void spawn_backend(
struct rgn* init_rgn,
pthread_t* out_thid,
struct ax_backend** out_backend)
{
pthread_t thid;
struct backend_init_ctxt init;
init.rgn = init_rgn;
init.err = NULL;
pthread_mutex_init(&init.mx, NULL);
pthread_cond_init(&init.cv, NULL);
pthread_mutex_lock(&init.mx);
pthread_create(&thid, NULL, backend_worker, &init);
pthread_cond_wait(&init.cv, &init.mx);
pthread_mutex_unlock(&init.mx);
pthread_cond_destroy(&init.cv);
pthread_mutex_destroy(&init.mx);
if (init.err != NULL) {
ASSERT(0, "failed to initialize `%s' backend: %s",
ax__backend_impl_name, init.err);
}
if (out_thid != NULL) {
*out_thid = thid;
}
if (out_backend != NULL) {
*out_backend = init.backend;
}
}
static void* backend_worker(void* arg)
{
struct backend_init_ctxt* init = arg;
struct rgn bac_rgn[1];
rgn_init(bac_rgn, ax__backend_desired_region_size);
struct ax_backend* bac;
const char* err = NULL;
int rv = ax__backend_new(bac_rgn, &bac, &err);
pthread_mutex_lock(&init->mx);
init->backend = bac;
if (rv != 0) {
init->err = rstrdup(init->rgn, err);
}
pthread_cond_signal(&init->cv);
pthread_mutex_unlock(&init->mx);
if (rv != 0) {
goto cleanup;
}
// `bac` is succesfully initialized here!
while (!ax__backend_is_shutdown(bac)) {
ax__backend_wait_for_event(bac);
ax__backend_step(bac);
}
cleanup:
rgn_cleanup(bac_rgn);
return NULL;
}
static void shutdown(struct msgq* mq, pthread_t thid)
{
(void) msgq_begin_send_typed(mq, ax_msg_shutdown);
msgq_end_send(mq);
pthread_join(thid, NULL);
}