From 130a0edc0405e53b45f8b2f8da0b94356480a644 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 1 Jan 2024 11:23:22 -0500 Subject: wip Signed-off-by: Andrew Opalach --- src/fruits/cmc/cmc.c | 781 +++++++++++++++++++++++++++++++++++++++++++++ src/fruits/cmc/meson.build | 7 + 2 files changed, 788 insertions(+) create mode 100644 src/fruits/cmc/cmc.c create mode 100644 src/fruits/cmc/meson.build (limited to 'src/fruits/cmc') diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c new file mode 100644 index 0000000..7fd8691 --- /dev/null +++ b/src/fruits/cmc/cmc.c @@ -0,0 +1,781 @@ +#include +#include +#include +#include +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#include "../../libclient/client.h" +#include "../../libclient/resource_client.h" +#include "../../tree/common.h" + +enum { + CMC_LIST_SEARCHES = 0, + CMC_CREATE_SEARCH, + CMC_OPEN_SEARCH, + CMC_LIST_LISTS, + CMC_CREATE_LIST +}; + +struct cmc_view { + struct sho_post *post; + struct ncplane *container; + bool has_visual_data; +}; + +struct cmc_view_resource { + u16 id; + str unique_id; + u32 index; + struct ncvisual *v; + struct ncvgeom geom; + struct ncvisual_options vopts; +}; + +enum { + CMC_VIEW_NONE = 0, + CMC_VIEW_SINGLE, + CMC_VIEW_PARTITION +}; + +struct cmc_twitter_view { + struct cmc_view v; + struct sho_post *repost; + struct { + u32 width; + u32 height; + u32 y; + struct ncplane *p; + } text; + struct { + struct ncplane *p; + struct cmc_view_resource *resource; + } profile; + struct { + u8 mode; + u32 width; + u32 height; + u32 x; + struct ncplane *p[4]; + struct cmc_view_resource *resources[4]; + } preview; + struct { + struct ncplane *p; + } stats; +}; + +struct cmc { + u8 mode; + struct aki_event_loop loop; + struct camu_client client; + struct camu_resource_client resource_client; + u32 pending_requests; + struct notcurses *nc; + struct aki_poll input; + u32 rows; + u32 cols; + struct { + str provider; + str query; + s32 id; + struct camu_search *search; + } search; + struct { + struct ncplane *p; + array(struct cmc_view *) views; + u32 offset; + u32 roffset; + u32 prev; + u32 page; + u32 selected; + } ui; + array(struct cmc_view_resource *) resource_cache; +}; + +/* +u64 color = 0; +u32 background = 0; +ncchannel_set_rgb8(&background, 255, 255, 255); +ncchannel_set_alpha(&background, 0); +//ncchannels_set_bchannel(&color, background); +ncchannels_set_fchannel(&color, background); +*/ + +//struct nccell cell = NCCELL_INITIALIZER(L'█', ncplane_styles(view->v.container), ncplane_channels(view->v.container)); +//ncplane_vline(view->v.container, &cell, rows - 1); +/* +//ncplane_gradient(view->v.container, 0, 0, rows, width, "", 0, color, color, color, color); +struct nccell ul = NCCELL_TRIVIAL_INITIALIZER, ur = NCCELL_TRIVIAL_INITIALIZER; +struct nccell ll = NCCELL_TRIVIAL_INITIALIZER, lr = NCCELL_TRIVIAL_INITIALIZER; +struct nccell hl = NCCELL_TRIVIAL_INITIALIZER, vl = NCCELL_TRIVIAL_INITIALIZER; +nccells_ascii_box(view->v.container, 0, color, &ul, &ur, &ll, &lr, &hl, &vl); +ncplane_box(view->v.container, &ul, &ur, &ll, &lr, &hl, &vl, rows, width, 0); +nccell_release(view->v.container, &ul); nccell_release(view->v.container, &ur); +nccell_release(view->v.container, &ll); nccell_release(view->v.container, &lr); +nccell_release(view->v.container, &hl); nccell_release(view->v.container, &vl); +//ncplane_double_box(view->v.container, NCSTYLE_NONE, color, rows, width, 0); +*/ + +static u32 count_wrapped_lines(wstr *w, u32 width) +{ + u32 counter = 0, wraps = 1; + for (u32 i = 0; i < w->len; i++) { + wchar_t wc = al_wstr_at(w, i); + s32 wcwidth = al_wchar_width(wc); + bool newline = wc == L'\n'; + if (newline || counter + wcwidth > width) { + wraps++; + counter = 0; + } + if (newline) continue; + counter += wcwidth; + } + return wraps; +} + +static void date_to_relative_date(struct sho_post *post, char *out) +{ + struct sho_post_date *created = NULL; + struct sho_post_date *date; + al_array_foreach_ptr(post->dates, i, date) { + if (date->type == SHOKI_DATE_CREATED) { + created = date; + break; + } + } + if (!created) return; + f64 seconds = (aki_get_timestamp() / 1000000.0 - created->timestamp); + s64 minutes = seconds / 60L; + s64 hours = minutes / 60L; + s64 days = hours / 24L; + if (days > 0) { + al_sprintf(out, "%lud", days); + } else if (hours > 0) { + al_sprintf(out, "%luh", hours); + } else if (minutes > 0) { + al_sprintf(out, "%lum", minutes); + } else { + al_sprintf(out, "%lus", (s64)seconds); + } +} + +static void number_to_shorthand(s64 number, char *out) +{ + if (number >= 1000000) { + al_sprintf(out, "%.1fM", number / 1000000.f); + } else if (number >= 1000) { + al_sprintf(out, "%.1fK", number / 1000.f); + } else { + al_sprintf(out, "%ld", number); + } +} + +#define TEXT_OFFSET 10 + +#define PROFILE_SIZE 5 +#define PREVIEW_OFFSET_WIDTH 26 + +/* +static u32 calc_height_for_display(struct ncvgeom *geom, u32 width) +{ + f32 ratio = geom->pixx / (f32)geom->pixy; + if (ratio >= 1.f) { + return ceil((width / ratio) / 2.f) - 2; + } else { + return ceil((width / ratio) / 2.f) - 2; + } +} +*/ + +static void resource_center_zoom(struct cmc_view_resource *resource, s32 width, s32 height) +{ + struct ncvgeom *geom = &resource->geom; + struct ncvisual_options *vopts = &resource->vopts; + + f32 ratio = geom->pixx / (f32)geom->pixy; + f32 preview_ratio = width / (f32)height; + f32 diff; + + f32 x0 = 0.f; + f32 y0 = 0.f; + f32 x1 = geom->pixx; + f32 y1 = geom->pixy; + + // Not properly tested. + if (preview_ratio > ratio) { + diff = (geom->pixy - (geom->pixy / (preview_ratio * ratio))) / 2.f; + y0 += diff; + y1 -= diff; + } else { + diff = (geom->pixx - (geom->pixx / (preview_ratio / ratio))) / 2.f; + x0 += diff; + x1 -= diff; + } + + vopts->begx = x0; + vopts->begy = y0; + vopts->lenx = x1 - x0; + vopts->leny = y1 - y0; +} + +static void get_post_and_maybe_repost(struct cmc *c, str *unique_id, struct sho_post **post, struct sho_post **repost) +{ + *post = sho_post_cache_get(&c->client.cache, unique_id); + *repost = NULL; + if ((*post)->type == SHOKI_POST_REPOST) { + *repost = *post; + *post = sho_post_cache_get(&c->client.cache, &(*post)->post.unique_id); + } +} + +static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) +{ + struct sho_post *post = view->v.post; + struct sho_post *repost = view->repost; + + u32 rows = 1; + + view->text.width = width - TEXT_OFFSET; + view->text.height = 1 + count_wrapped_lines(&post->text, view->text.width); + if (repost) { + view->text.y = 1; + } else { + view->text.y = 0; + } + view->text.height += view->text.y; + rows += view->text.height; + + view->v.has_visual_data = true; + + for (u32 i = 0; i < 4; i++) { + if (view->preview.resources[i] && !view->preview.resources[i]->v) { + view->v.has_visual_data = false; + break; + } + } + + if (view->preview.mode == CMC_VIEW_NONE || !view->v.has_visual_data) return rows; + + view->preview.width = view->text.width; + u32 partition_width = view->preview.width / 2; + view->preview.x = TEXT_OFFSET + 1; + view->preview.height = 0; + + struct ncplane_options opts = { 0 }; + opts.cols = view->preview.width; + opts.rows = ncplane_dim_y(notcurses_stdplane(c->nc)) / 3; + opts.x = 0; + opts.y = 0; + struct ncplane *inter = ncplane_create(notcurses_stdplane(c->nc), &opts); + + u32 left = 0, right = 0; + struct cmc_view_resource *resource; + switch (view->preview.mode) { + case CMC_VIEW_SINGLE: { + resource = view->preview.resources[0]; + resource->vopts.n = inter; + ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + left += resource->geom.rcelly + 1; + break; + } + case CMC_VIEW_PARTITION: { + ncplane_resize_simple(inter, opts.rows, partition_width); + + resource = view->preview.resources[0]; + resource->vopts.n = inter; + ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + left += resource->geom.rcelly + 1; + + resource = view->preview.resources[1]; + resource->vopts.n = inter; + ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + right += resource->geom.rcelly + 1; + + if (view->preview.resources[2]) { + resource = view->preview.resources[2]; + resource->vopts.n = inter; + ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + left += resource->geom.rcelly + 1; + } + + if (view->preview.resources[3]) { + resource = view->preview.resources[3]; + resource->vopts.n = inter; + ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + right += resource->geom.rcelly + 1; + } + + break; + } + } + view->preview.height = AL_MAX(right, left); + + ncplane_destroy(inter); + + return rows + view->preview.height; +} + +static char format_buf[32]; + +static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncplane *container, u32 rows, u32 width) +{ + struct sho_post *post = view->v.post; + struct sho_post *repost = view->repost; + + ncplane_resize_simple(container, rows, width); + + if (view->v.has_visual_data) { + u32 partition_width = view->preview.width / 2; + struct cmc_view_resource *resource; + switch (view->preview.mode) { + case CMC_VIEW_NONE: + break; + case CMC_VIEW_SINGLE: { + resource = view->preview.resources[0]; + ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, view->preview.width); + ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x); + resource->vopts.n = view->preview.p[0]; + ncvisual_blit(c->nc, resource->v, &resource->vopts); + break; + } + case CMC_VIEW_PARTITION: { + resource = view->preview.resources[0]; + resource->vopts.n = view->preview.p[0]; + ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x); + ncvisual_blit(c->nc, resource->v, &resource->vopts); + + resource = view->preview.resources[1]; + ncplane_resize_simple(view->preview.p[1], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[1], view->text.height, (view->preview.x + view->preview.resources[0]->geom.rcellx) + 2); + resource->vopts.n = view->preview.p[1]; + ncvisual_blit(c->nc, resource->v, &resource->vopts); + + if (view->preview.resources[2]) { + resource = view->preview.resources[2]; + ncplane_resize_simple(view->preview.p[2], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[2], view->preview.resources[0]->geom.rcelly + view->text.height + 2, view->preview.x); + resource->vopts.n = view->preview.p[2]; + ncvisual_blit(c->nc, resource->v, &resource->vopts); + } + + if (view->preview.resources[3]) { + resource = view->preview.resources[3]; + ncplane_resize_simple(view->preview.p[3], resource->geom.rcelly + 1, partition_width); + ncplane_move_yx(view->preview.p[3], view->preview.resources[2]->geom.rcelly + view->text.height + 2, (view->preview.x + view->preview.resources[2]->geom.rcellx) + 2); + resource->vopts.n = view->preview.p[3]; + ncvisual_blit(c->nc, resource->v, &resource->vopts); + } + + break; + } + } + } + + ncplane_resize_simple(view->text.p, view->text.height, width); + ncplane_move_yx(view->text.p, 0, 0); + + s32 offset = TEXT_OFFSET; + + if (repost) { + const wchar_t *retweet = L"⮤⮧"; + ncplane_set_styles(view->text.p, NCSTYLE_ITALIC); + ncplane_putwstr_yx(view->text.p, 0, offset - 3, retweet); + ncplane_set_styles(view->text.p, NCSTYLE_NONE); + ncplane_putwstr_yx(view->text.p, 0, offset, &al_wstr_at(&repost->author.display_name, 0)); + } + + ncplane_set_styles(view->text.p, NCSTYLE_BOLD); + + ncplane_putwstr_yx(view->text.p, view->text.y, offset, &al_wstr_at(&post->author.display_name, 0)); + offset += al_wstr_width(&post->author.display_name) + 1; + + ncplane_set_styles(view->text.p, NCSTYLE_NONE); + ncplane_putwstr_yx(view->text.p, view->text.y, offset, &al_wstr_at(&post->author.username, 0)); + offset += al_wstr_width(&post->author.username) + 1; + + ncplane_putwstr_yx(view->text.p, view->text.y, offset, L"∙"); + offset += 2; + + date_to_relative_date(post, format_buf); + ncplane_putstr_yx(view->text.p, view->text.y, offset, format_buf); + + u32 text_y = view->text.y + 1, text_x = TEXT_OFFSET; + for (u32 i = 0; i < post->text.len; i++) { + wchar_t wc = al_wstr_at(&post->text, i); + s32 wcwidth = al_wchar_width(wc); + bool newline = wc == L'\n'; + if (newline || text_x + wcwidth > width) { + text_y++; + text_x = TEXT_OFFSET; + } + if (newline) continue; + ncplane_putwc_yx(view->text.p, text_y, text_x, wc); + text_x += wcwidth; + } + + ncplane_resize_simple(view->stats.p, 1, view->text.width); + ncplane_move_yx(view->stats.p, rows - 1, TEXT_OFFSET); + + u32 part = 10; + offset = 1; + + if (post->comments.set) { + const wchar_t *comment = L"🗩"; + //const wchar_t *comment = L"[]"; + //const wchar_t *comment = L"。○"; + //const wchar_t *comment = L"❑"; + //const wchar_t *comment = L"。ロ"; + //const wchar_t *comment = L".❍"; + ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC); + ncplane_putwstr_yx(view->stats.p, 0, offset, comment); + ncplane_set_styles(view->stats.p, NCSTYLE_NONE); + number_to_shorthand(post->comments.i, format_buf); + ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf); + } + offset += part; + + if (post->reposts.set) { + const wchar_t *retweet = L"⮤⮧"; + ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC); + ncplane_putwstr_yx(view->stats.p, 0, offset, retweet); + ncplane_set_styles(view->stats.p, NCSTYLE_NONE); + number_to_shorthand(post->reposts.i, format_buf); + ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf); + } + offset += part; + + if (post->likes.set) { + const wchar_t *like = L"❤"; + ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC); + ncplane_putwstr_yx(view->stats.p, 0, offset, like); + ncplane_set_styles(view->stats.p, NCSTYLE_NONE); + number_to_shorthand(post->likes.i, format_buf); + ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf); + } + + if (view->profile.resource && view->profile.resource->v) { + struct cmc_view_resource *resource = view->profile.resource; + ncplane_resize_simple(view->profile.p, (PROFILE_SIZE + 1) / 2, PROFILE_SIZE); + ncplane_move_yx(view->profile.p, view->text.y, PROFILE_SIZE - 2); + resource->vopts.n = view->profile.p; + ncvisual_blit(c->nc, resource->v, &resource->vopts); + } +} + +static void layout_tweets(struct cmc *c) +{ + struct cmc_twitter_view *view; + for (u32 i = 0; i < c->ui.views.size; i++) { + view = (struct cmc_twitter_view *)al_array_at(c->ui.views, i); + ncplane_erase(view->v.container); + ncplane_erase(view->text.p); + ncplane_erase(view->profile.p); + for (u32 i = 0; i < 4; i++) { + ncplane_erase(view->preview.p[i]); + } + ncplane_erase(view->stats.p); + ncplane_reparent_family(view->v.container, view->v.container); + } + + u32 y = 0; + u32 width = ncplane_dim_x(c->ui.p); + u32 rows = 0; + u32 i = c->ui.offset; + c->ui.page = 0; + for (; i < c->ui.views.size; i++) { + view = (struct cmc_twitter_view *)al_array_at(c->ui.views, i); + rows = layout_tweet(c, view, width); + if (y + rows > ncplane_dim_y(c->ui.p)) break; + c->ui.page++; + draw_tweet(c, view, view->v.container, rows, width); + ncplane_reparent_family(view->v.container, c->ui.p); + ncplane_move_yx(view->v.container, y, 0); + if (i - c->ui.offset == c->ui.selected) { + wchar_t box = L'█'; + u32 i = view->repost ? 1 : 0; + for (; i < rows - 1; i++) { + ncplane_cursor_move_yx(view->v.container, i, 0); + ncplane_putwc(view->v.container, box); + } + } + y += rows + 1; + } + + if (!c->ui.page) c->ui.page = 1; + + if (c->ui.roffset != c->ui.offset) { + notcurses_refresh(c->nc, NULL, NULL); + } + notcurses_render(c->nc); + + c->ui.roffset = c->ui.offset; + + if (c->pending_requests == 0 && c->ui.offset + c->ui.page >= c->ui.views.size) { + camu_client_more_results(&c->client, c->search.search); + } +} + +static void resource_client_callback(void *userdata, u16 id, struct aki_buffer *buffer) +{ + struct cmc *c = (struct cmc *)userdata; + struct cmc_view_resource *resource; + al_array_foreach(c->resource_cache, i, resource) { + if (resource->id == id) { + s32 w, h, channels; + u8 *pixels = stbi_load_from_memory(aki_buffer_get_ptr(buffer, 0), buffer->size, &w, &h, &channels, 4); + resource->v = ncvisual_from_rgba(pixels, h, w * 4, w); + stbi_image_free(pixels); + ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + break; + } + } + if (--c->pending_requests == 0) layout_tweets(c); +} + +static void input_poll_callback(void *userdata, s32 revents) +{ + struct cmc *c = (struct cmc *)userdata; + (void)revents; + struct ncinput input; + u32 ret; + bool layout = false; + do { + ret = notcurses_get_nblock(c->nc, &input); + if (ret == (u32)-1 || ret == 0) break; + if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) { + if (input.id == 'j') { + if (c->ui.selected + 1 >= c->ui.page) { + if (c->ui.offset + c->ui.page >= c->ui.views.size) { + return; + } + c->ui.offset += c->ui.page; + c->ui.prev = c->ui.page; + c->ui.page = 0; + c->ui.selected = 0; + } else { + c->ui.selected++; + } + layout = true; + } else if (input.id == 'k') { + if ((s32)c->ui.selected - 1 < 0) { + if (c->ui.offset == 0 || (s32)c->ui.offset - (s32)c->ui.prev < 0) { + return; + } + c->ui.offset -= c->ui.prev; + c->ui.selected = c->ui.prev - 1; + c->ui.prev = 0; + } else { + c->ui.selected--; + } + layout = true; + } else if (input.id == NCKEY_RETURN) { + if (c->ui.views.size > 0) { + u32 selected = c->ui.offset + c->ui.selected; + struct cmc_twitter_view *view = (struct cmc_twitter_view *)al_array_at(c->ui.views, selected); + camu_client_add(&c->client, &view->v.post->unique_id, 0); + } + } else if (input.id == 'q') { + aki_event_loop_break(&c->loop); + break; + } + } + } while (1); + if (layout) layout_tweets(c); +} + +static struct cmc_view_resource *create_resource(struct cmc *c, str *unique_id, u32 index) +{ + struct cmc_view_resource *resource = al_alloc_object(struct cmc_view_resource); + al_array_push(c->resource_cache, resource); + resource->vopts.blitter = NCBLIT_PIXEL; + //resource->vopts.blitter = NCBLIT_2x1; + resource->vopts.scaling = NCSCALE_SCALE_HIRES; + //resource->vopts.scaling = NCSCALE_STRETCH; + resource->id = al_rand_u16(); + al_str_clone(&resource->unique_id, unique_id); + resource->index = index; + c->pending_requests++; + camu_resource_request(&c->resource_client, unique_id, index, resource->id); + return resource; +} + +static struct cmc_view_resource *get_resource_from_cache(struct cmc *c, str *unique_id, u32 index) +{ + struct cmc_view_resource *resource; + al_array_foreach(c->resource_cache, i, resource) { + if (al_str_eq(&resource->unique_id, unique_id) && resource->index == index) { + return resource; + } + } + return create_resource(c, unique_id, index); +} + +static int resize_cb(struct ncplane *p) +{ + struct cmc *c = (struct cmc *)ncplane_userptr(p); + u32 width, height; + notcurses_stddim_yx(c->nc, &height, &width); + struct ncplane_options nopts = { 0 }; + nopts.rows = height - 1; + nopts.cols = width - 4; + nopts.x = 3; + nopts.y = 1; + nopts.flags = 0; + struct ncplane *oldp = c->ui.p; + c->ui.p = ncplane_create(p, &nopts); + if (oldp) { + ncplane_destroy(oldp); + layout_tweets(c); + } + return 0; +} + +static void client_callback(void *userdata, u8 op, void *opaque) +{ + struct cmc *c = (struct cmc *)userdata; + switch (op) { + case CAMU_CLIENT_STATUS_UPDATED: { + switch (c->mode) { + case CMC_LIST_SEARCHES: { + struct camu_search *search; + al_array_foreach(c->client.state.searches, i, search) { + al_printf("%i %.*s\n", search->id, AL_STR_PRINTF(&search->query)); + } + aki_event_loop_break(&c->loop); + break; + } + case CMC_LIST_LISTS: { + struct camu_list *list; + al_array_foreach(c->client.state.lists, i, list) { + al_printf("%.*s\n", AL_STR_PRINTF(&list->name)); + } + aki_event_loop_break(&c->loop); + break; + } + case CMC_CREATE_SEARCH: { + camu_client_create_search(&c->client, &c->search.provider, &c->search.query); + break; + } + case CMC_OPEN_SEARCH: { + if (!(c->nc = notcurses_init(NULL, stdin))) return; + if (c->client.state.searches.size == 0) return; + c->search.search = al_array_at(c->client.state.searches, 0); + struct ncplane *stdplane = notcurses_stdplane(c->nc); + ncplane_set_resizecb(stdplane, resize_cb); + ncplane_set_userptr(stdplane, c); + resize_cb(stdplane); + camu_client_resume_search(&c->client, c->search.search); + aki_poll_init(&c->input, input_poll_callback, c); + aki_poll_set(&c->input, notcurses_inputready_fd(c->nc), AKI_POLL_READ); + aki_poll_start(&c->input, &c->loop); + break; + } + } + break; + } + case CAMU_CLIENT_SEARCH_CREATED: { + aki_event_loop_break(&c->loop); + break; + } + case CAMU_CLIENT_RESULTS: { + struct camu_search_results *results = (struct camu_search_results *)opaque; + str *unique_id; + al_array_foreach_ptr(results->unique_ids, i, unique_id) { + struct cmc_twitter_view *view = al_alloc_object(struct cmc_twitter_view); + al_array_push(c->ui.views, (struct cmc_view *)view); + get_post_and_maybe_repost(c, unique_id, &view->v.post, &view->repost); + + struct ncplane_options default_options = { 0 }; + default_options.cols = 1; + default_options.rows = 1; + default_options.x = 0; + default_options.y = 0; + + view->v.container = ncpile_create(c->nc, &default_options); + view->text.p = ncplane_create(view->v.container, &default_options); + view->profile.p = ncplane_create(view->v.container, &default_options); + for (u32 i = 0; i < 4; i++) { + view->preview.p[i] = ncplane_create(view->v.container, &default_options); + } + view->stats.p = ncplane_create(view->v.container, &default_options); + + u32 prev_pending = c->pending_requests; + + struct sho_post *post = view->v.post; + view->profile.resource = get_resource_from_cache(c, &post->author.unique_id, 0); + struct sho_post_media *media; + al_array_foreach_ptr(post->media, i, media) { + (void)media; + view->preview.resources[i] = get_resource_from_cache(c, &post->unique_id, i); + } + + view->v.has_visual_data = prev_pending == c->pending_requests; + + if (post->media.size == 0) { + view->preview.mode = CMC_VIEW_NONE; + } else if (post->media.size == 1) { + view->preview.mode = CMC_VIEW_SINGLE; + } else { + view->preview.mode = CMC_VIEW_PARTITION; + } + } + if (c->pending_requests == 0) layout_tweets(c); + break; + } + } +} + +s32 main(s32 argc, char *argv[]) +{ + if (argc < 2 || !aki_common_init()) return EXIT_FAILURE; + + struct cmc c = { 0 }; + + al_array_init(c.ui.views); + al_array_init(c.resource_cache); + c.pending_requests = 0; + c.ui.offset = 0; + + str *cmd = al_str_c(argv[1]); + if (al_str_eq(cmd, al_str_c("search"))) { + if (argc == 2) { + c.mode = CMC_LIST_SEARCHES; + } else { + c.mode = CMC_CREATE_SEARCH; + al_str_from(&c.search.provider, "twitter"); + al_str_from(&c.search.query, argv[2]); + } + } else if (al_str_eq(cmd, al_str_c("open"))) { + c.mode = CMC_OPEN_SEARCH; + } else if (al_str_eq(cmd, al_str_c("list"))) { + if (argc == 2) { + c.mode = CMC_LIST_LISTS; + } else { + } + } else { + return EXIT_FAILURE; + } + + aki_event_loop_init(&c.loop); + + camu_client_init(&c.client, &c.loop, client_callback, &c); + camu_client_login(&c.client, al_str_c("andrew"), al_str_c("127.0.0.1"), TREE_PORT); + + camu_resource_client_init(&c.resource_client, &c.loop, resource_client_callback, &c); + camu_resource_client_connect(&c.resource_client, al_str_c("127.0.0.1"), TREE_RESOURCE_PORT); + + aki_event_loop_run(&c.loop); + + if (c.mode == CMC_OPEN_SEARCH) notcurses_stop(c.nc); + + camu_client_close(&c.client); + + aki_common_close(); + + return EXIT_SUCCESS; +} diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build new file mode 100644 index 0000000..45ac9c2 --- /dev/null +++ b/src/fruits/cmc/meson.build @@ -0,0 +1,7 @@ +cmc_src = ['cmc.c'] +cmc_deps = [common_deps, libclient, cache, stb_image] + +notcurses = dependency('notcurses') +cmc_deps += [notcurses] + +executable('cmc', cmc_src, dependencies: cmc_deps) -- cgit v1.2.3-101-g0448