#include #include #include #include #include #include "til.h" #include "til_fb.h" #include "til_module_context.h" #include "til_stream.h" #include "til_tap.h" #include "txt/txt.h" /* Copyright (C) 2023 Vito Caputo */ /* This is intended primarily for diagnostic purposes or as a stand-in you'd eventually * replace with something a more visually interesting font/style. */ #define ASC_DEFAULT_STRING "Hello rototiller!" #define ASC_DEFAULT_JUSTIFY ASC_JUSTIFY_ALIGNED #define ASC_DEFAULT_HALIGN TXT_HALIGN_CENTER #define ASC_DEFAULT_VALIGN TXT_VALIGN_CENTER #define ASC_DEFAULT_HOFFSET "auto" #define ASC_DEFAULT_VOFFSET "auto" #define ASC_DEFAULT_X 0 #define ASC_DEFAULT_Y 0 typedef enum asc_justify_t { ASC_JUSTIFY_ALIGNED, ASC_JUSTIFY_OFFSETTED, ASC_JUSITFY_CNT } asc_justify_t; typedef struct asc_setup_t { til_setup_t til_setup; const char *string; asc_justify_t justify; txt_halign_t halign; txt_valign_t valign; float hoffset, voffset; float x, y; } asc_setup_t; typedef struct asc_context_t { til_module_context_t til_module_context; struct { til_tap_t x, y; til_tap_t hoffset, voffset; } taps; struct { float x, y; float hoffset, voffset; } vars; float *x, *y; float *hoffset, *voffset; txt_t *txt; } asc_context_t; static void asc_update_taps(asc_context_t *ctxt, til_stream_t *stream) { if (!til_stream_tap_context(stream, &ctxt->til_module_context, NULL, &ctxt->taps.x)) *ctxt->x = ((asc_setup_t *)ctxt->til_module_context.setup)->x; else ctxt->vars.x = *ctxt->x; if (!til_stream_tap_context(stream, &ctxt->til_module_context, NULL, &ctxt->taps.y)) *ctxt->y = ((asc_setup_t *)ctxt->til_module_context.setup)->y; else ctxt->vars.y = *ctxt->y; /* XXX: maybe clamp to -1.0..+1.0 ? It's not a crash risk since txt_render_fragment_aligned() * clips to the fragment by using ...put_pixel_checked() *shrug* */ if (((asc_setup_t *)ctxt->til_module_context.setup)->justify != ASC_JUSTIFY_OFFSETTED) return; if (!til_stream_tap_context(stream, &ctxt->til_module_context, NULL, &ctxt->taps.hoffset)) *ctxt->hoffset = ((asc_setup_t *)ctxt->til_module_context.setup)->hoffset; else ctxt->vars.hoffset = *ctxt->hoffset; if (!til_stream_tap_context(stream, &ctxt->til_module_context, NULL, &ctxt->taps.voffset)) *ctxt->voffset = ((asc_setup_t *)ctxt->til_module_context.setup)->voffset; else ctxt->vars.voffset = *ctxt->voffset; } static til_module_context_t * asc_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup) { asc_context_t *ctxt; ctxt = til_module_context_new(module, sizeof(asc_context_t), stream, seed, ticks, n_cpus, setup); if (!ctxt) return NULL; ctxt->txt = txt_new(((asc_setup_t *)setup)->string); if (!ctxt->txt) return til_module_context_free(&ctxt->til_module_context); ctxt->taps.x = til_tap_init_float(ctxt, &ctxt->x, 1, &ctxt->vars.x, "x"); ctxt->taps.y = til_tap_init_float(ctxt, &ctxt->y, 1, &ctxt->vars.y, "y"); if (((asc_setup_t *)setup)->justify == ASC_JUSTIFY_OFFSETTED) { ctxt->taps.hoffset = til_tap_init_float(ctxt, &ctxt->hoffset, 1, &ctxt->vars.hoffset, "hoffset"); ctxt->taps.voffset = til_tap_init_float(ctxt, &ctxt->voffset, 1, &ctxt->vars.voffset, "voffset"); } asc_update_taps(ctxt, stream); return &ctxt->til_module_context; } static void asc_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr) { asc_context_t *ctxt = (asc_context_t *)context; asc_setup_t *s = (asc_setup_t *)context->setup; til_fb_fragment_t *fragment = *fragment_ptr; asc_update_taps(ctxt, stream); til_fb_fragment_clear(fragment); switch (s->justify) { case ASC_JUSTIFY_ALIGNED: return txt_render_fragment_aligned(ctxt->txt, fragment, 0xffffffff, ctxt->vars.x * ((float)fragment->frame_width) * .5f + .5f * ((float)fragment->frame_width), ctxt->vars.y * ((float)fragment->frame_height) * .5f + .5f * ((float)fragment->frame_height), (txt_align_t){ .horiz = s->halign, .vert = s->valign }); case ASC_JUSTIFY_OFFSETTED: { float hoffset = ctxt->vars.hoffset, voffset = ctxt->vars.voffset; if (isnan(hoffset)) hoffset = ctxt->vars.x; if (isnan(voffset)) voffset = ctxt->vars.y; return txt_render_fragment_offsetted(ctxt->txt, fragment, 0xffffffff, ctxt->vars.x * ((float)fragment->frame_width) * .5f + .5f * ((float)fragment->frame_width), ctxt->vars.y * ((float)fragment->frame_height) * .5f + .5f * ((float)fragment->frame_height), hoffset, voffset); } default: assert(0); } } static void asc_setup_free(til_setup_t *setup) { asc_setup_t *s = (asc_setup_t *)setup; free((void *)s->string); free(s); } static int asc_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup); til_module_t asc_module = { .create_context = asc_create_context, .render_fragment = asc_render_fragment, .setup = asc_setup, .name = "asc", .description = "ASCII text (built-in)", .author = "Vito Caputo ", .flags = TIL_MODULE_OVERLAYABLE | TIL_MODULE_BUILTIN, }; static int asc_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) { til_setting_t *string; til_setting_t *justify; til_setting_t *valign, *voffset; til_setting_t *halign, *hoffset; til_setting_t *x, *y; const char *justify_values[] = { "aligned", "offsetted", NULL }; const char *valign_values[] = { "center", "top", "bottom", NULL }; const char *halign_values[] = { "center", "left", "right", NULL }; int r; r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Text string", .key = "string", /* .regex = "" TODO */ .preferred = ASC_DEFAULT_STRING, }, &string, res_setting, res_desc); if (r) return r; r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Justification", .key = "justify", /* .regex = "" TODO */ .preferred = justify_values[ASC_DEFAULT_JUSTIFY], .values = justify_values, .annotations = NULL }, &justify, res_setting, res_desc); if (r) return r; if (!strcasecmp(justify->value, justify_values[ASC_JUSTIFY_ALIGNED])) { r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Vertical alignment", .key = "valign", /* .regex = "" TODO */ .preferred = valign_values[ASC_DEFAULT_VALIGN], .values = valign_values, .annotations = NULL }, &valign, res_setting, res_desc); if (r) return r; r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Horizontal alignment", .key = "halign", /* .regex = "" TODO */ .preferred = halign_values[ASC_DEFAULT_HALIGN], .values = halign_values, .annotations = NULL }, &halign, res_setting, res_desc); if (r) return r; } else { r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Vertical offset [-1.0...1.0] or 'auto'", .key = "voffset", /* .regex = "" TODO */ .preferred = ASC_DEFAULT_VOFFSET, .annotations = NULL }, &voffset, res_setting, res_desc); if (r) return r; r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Horizontal offset [-1.0...1.0] or 'auto'", .key = "hoffset", /* .regex = "" TODO */ .preferred = ASC_DEFAULT_HOFFSET, .annotations = NULL }, &hoffset, res_setting, res_desc); if (r) return r; } r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "X coordinate [-1.0...1.0]", .key = "x", /* .regex = "" TODO */ .preferred = TIL_SETTINGS_STR(ASC_DEFAULT_X), .annotations = NULL }, &x, res_setting, res_desc); if (r) return r; r = til_settings_get_and_describe_setting(settings, &(til_setting_spec_t){ .name = "Y coordinate [-1.0...1.0]", .key = "y", /* .regex = "" TODO */ .preferred = TIL_SETTINGS_STR(ASC_DEFAULT_Y), .annotations = NULL }, &y, res_setting, res_desc); if (r) return r; if (res_setup) { asc_setup_t *setup; setup = til_setup_new(settings, sizeof(*setup), asc_setup_free, &asc_module); if (!setup) return -ENOMEM; setup->string = strdup(string->value); if (!setup->string) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, string, res_setting, -ENOMEM); r = til_value_to_pos(justify_values, justify->value, (unsigned *)&setup->justify); if (r < 0) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, justify, res_setting, -EINVAL); if (setup->justify == ASC_JUSTIFY_ALIGNED) { r = til_value_to_pos(halign_values, halign->value, (unsigned *)&setup->halign); if (r < 0) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, halign, res_setting, -EINVAL); r = til_value_to_pos(valign_values, valign->value, (unsigned *)&setup->valign); if (r < 0) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, valign, res_setting, -EINVAL); } else { if (!strcasecmp(hoffset->value, "auto")) setup->hoffset = NAN; else if (sscanf(hoffset->value, "%f", &setup->hoffset) != 1) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, hoffset, res_setting, -EINVAL); if (!strcasecmp(voffset->value, "auto")) setup->voffset = NAN; else if (sscanf(voffset->value, "%f", &setup->voffset) != 1) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, voffset, res_setting, -EINVAL); } if (sscanf(x->value, "%f", &setup->x) != 1) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, x, res_setting, -EINVAL); if (sscanf(y->value, "%f", &setup->y) != 1) return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, y, res_setting, -EINVAL); *res_setup = &setup->til_setup; } return 0; }