diff options
| author | 2024-01-27 20:39:25 -0500 | |
|---|---|---|
| committer | 2024-01-27 20:39:25 -0500 | |
| commit | b100eb175e0cfa59a15c4125f26ab5474c2347ec (patch) | |
| tree | f1ee4f8bed5d3490ba5d70a6fc194f3206df8df4 /src/fruits/cmc | |
| parent | fb1cbf546300d9f8efdf21c6ac66f7c2f1dfd4f3 (diff) | |
| download | camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.tar.gz camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.tar.bz2 camu-b100eb175e0cfa59a15c4125f26ab5474c2347ec.zip | |
wip
- Mostly work in the client
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/fruits/cmc')
| -rw-r--r-- | src/fruits/cmc/cmc.c | 901 |
1 files changed, 563 insertions, 338 deletions
diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index 89800f6..55f659c 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -1,4 +1,5 @@ #include <al/lib.h> +#include <al/log.h> #include <al/wstr.h> #include <al/random.h> #include <aki/common.h> @@ -14,17 +15,27 @@ #include "../../tree/common.h" enum { - CMC_LIST_SEARCHES = 0, - CMC_CREATE_SEARCH, - CMC_OPEN_SEARCH, - CMC_LIST_LISTS, - CMC_CREATE_LIST + CMC_LIST = 0, + CMC_SEARCH +}; + +enum { + CMC_ACTION_OPEN = 0, + CMC_ACTION_LIST, + CMC_ACTION_CREATE, +}; + +enum { + CMC_VIEW_NONE = 0, + CMC_VIEW_SINGLE, + CMC_VIEW_PARTITION }; struct cmc_view { struct sho_post *post; struct ncplane *container; - bool has_visual_data; + u32 rows; + u32 y; }; struct cmc_view_resource { @@ -36,13 +47,7 @@ struct cmc_view_resource { struct ncvisual_options vopts; }; -enum { - CMC_VIEW_NONE = 0, - CMC_VIEW_SINGLE, - CMC_VIEW_PARTITION -}; - -struct cmc_twitter_view { +struct cmc_view_twitter { struct cmc_view v; struct sho_post *repost; struct { @@ -68,31 +73,45 @@ struct cmc_twitter_view { } stats; }; +struct cmc_view_page { + u32 num; + bool processed; + array(struct cmc_view *) views; +}; + +struct cmc_search { + struct camu_search *search; + bool find_anchor; + s32 anchor; + s32 prev_anchor; + u32 count; + s32 selected; + s32 prev_selected; + u32 max_page_num; + u32 min_page_num; + s32 end_of_page; + array(struct cmc_view_page *) pages; +}; + +struct cmc_tui { + struct notcurses *nc; + u32 cols; + u32 rows; + struct ncplane *p; + struct aki_poll input; + u32 req_index; + array(struct cmc_search *) searches; + struct cmc_search *search; +}; + struct cmc { - u8 mode; + u8 type; + u8 action; struct aki_event_loop loop; struct camu_client client; - struct camu_resource_client resource_client; + struct cmc_tui tui; 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; + struct camu_resource_client resource_client; array(struct cmc_view_resource *) resource_cache; }; @@ -120,6 +139,49 @@ 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 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 u32 count_wrapped_lines(wstr *w, u32 width) { u32 counter = 0, wraps = 1; @@ -179,60 +241,7 @@ static void number_to_shorthand(s64 number, char *out) #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) +static u32 layout_tweet(struct cmc_tui *tui, struct cmc_view_twitter *view, u32 width) { struct sho_post *post = view->v.post; struct sho_post *repost = view->repost; @@ -241,36 +250,24 @@ static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) 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.y = repost ? 1 : 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) { + return rows; } - 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.width = view->text.width - 1; view->preview.x = TEXT_OFFSET + 1; - view->preview.height = 0; + u32 partition_width = view->preview.width / 2; struct ncplane_options opts = { 0 }; opts.cols = view->preview.width; - opts.rows = ncplane_dim_y(notcurses_stdplane(c->nc)) / 3; + opts.rows = ncplane_dim_y(notcurses_stdplane(tui->nc)) / 3; opts.x = 0; opts.y = 0; - struct ncplane *inter = ncplane_create(notcurses_stdplane(c->nc), &opts); + struct ncplane *inter = ncplane_create(notcurses_stdplane(tui->nc), &opts); u32 left = 0, right = 0; struct cmc_view_resource *resource; @@ -278,8 +275,9 @@ static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) 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; + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); + // rcellx == width case very untested. + left += resource->geom.rcelly + (s32)((resource->geom.rcellx != view->preview.width) ? 1 : -1); break; } case CMC_VIEW_PARTITION: { @@ -287,93 +285,92 @@ static u32 layout_tweet(struct cmc *c, struct cmc_twitter_view *view, u32 width) resource = view->preview.resources[0]; resource->vopts.n = inter; - ncvisual_geom(c->nc, resource->v, &resource->vopts, &resource->geom); + ncvisual_geom(tui->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); + ncvisual_geom(tui->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); + ncvisual_geom(tui->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); + ncvisual_geom(tui->nc, resource->v, &resource->vopts, &resource->geom); right += resource->geom.rcelly + 1; } break; } } - view->preview.height = AL_MAX(right, left); ncplane_destroy(inter); + view->preview.height = AL_MAX(right, left); + 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) +static void draw_tweet(struct cmc_tui *tui, struct cmc_view_twitter *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); - } + 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(tui->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(tui->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); - } + 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) + 3); + resource->vopts.n = view->preview.p[1]; + ncvisual_blit(tui->nc, resource->v, &resource->vopts); - break; + 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(tui->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) + 3); + resource->vopts.n = view->preview.p[3]; + ncvisual_blit(tui->nc, resource->v, &resource->vopts); } + + break; + } } ncplane_resize_simple(view->text.p, view->text.height, width); @@ -426,11 +423,6 @@ static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncpl 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); @@ -450,12 +442,13 @@ static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncpl offset += part; if (post->likes.set) { - const wchar_t *like = L"❤"; + //const wchar_t *like = L"❤"; + 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); + ncplane_putstr_yx(view->stats.p, 0, offset + 2, format_buf); } if (view->profile.resource && view->profile.resource->v) { @@ -463,66 +456,178 @@ static void draw_tweet(struct cmc *c, struct cmc_twitter_view *view, struct ncpl 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); + ncvisual_blit(tui->nc, resource->v, &resource->vopts); } } -static void layout_tweets(struct cmc *c) +static struct cmc_view_page *get_page_from_num(struct cmc_search *search, u32 num) { - 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); + struct cmc_view_page *page; + al_array_foreach(search->pages, i, page) { + if (page->num == num) return page; + } + return NULL; +} + +static struct cmc_view_page *get_page_containing_index(struct cmc_search *search, u32 index, u32 *offset) +{ + u32 count = 0, size = 0; + struct cmc_view_page *page = NULL; + for (u32 i = search->min_page_num; i <= search->max_page_num; i++) { + if (!(page = get_page_from_num(search, i))) continue; + size = page->views.size; + count += size; + if (count > index) break; } + if (offset) *offset = size - (count - index); + return page; +} + +static void request_page_internal(struct cmc *c, u32 num) +{ + c->pending_requests++; + camu_client_get_page(&c->client, c->tui.search->search, num); +} - 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); +static bool request_page_at_page_offset(struct cmc *c, struct cmc_search *search, u32 index, s32 offset) +{ + struct cmc_view_page *page = get_page_containing_index(search, index, NULL); + if (page) { + s32 req = (s32)page->num + offset; + if (req < 0) return false; + request_page_internal(c, req); + } + return true; +} + +static bool iterate_views_to_height(struct cmc_search *search, u32 height, u32 anchor, + u32 *count, void (*func)(void *, struct cmc_view_twitter *, u32), void *userdata) +{ + u32 y = 0, offset; + bool out_of_space = false; + struct cmc_view_page *page = get_page_containing_index(search, anchor, &offset); + al_assert(page); + struct cmc_view_twitter *view; + do { + for (; offset < page->views.size; offset++) { + view = (struct cmc_view_twitter *)al_array_at(page->views, offset); + if (y + view->v.rows > height) { + out_of_space = true; + break; } + if (func) func(userdata, view, y); + y += view->v.rows + 1; + (*count)++; } - y += rows + 1; + offset = 0; + } while (!out_of_space && (page = get_page_from_num(search, page->num + 1))); + return out_of_space; +} + +static void draw_tweet_func(void *userdata, struct cmc_view_twitter *view, u32 y) +{ + struct cmc_tui *tui = (struct cmc_tui *)userdata; + draw_tweet(tui, view, view->v.container, view->v.rows, tui->cols); + ncplane_reparent_family(view->v.container, tui->p); + ncplane_move_yx(view->v.container, y, 0); + view->v.y = y; +} + +static void draw_selection_line(struct cmc_view_twitter *view, u32 y, u32 rows) +{ + ncplane_move_yx(view->v.container, y, 0); + wchar_t box = L'█'; + u32 k = view->repost ? 1 : 0; + for (; k < rows - 1; k++) { + ncplane_cursor_move_yx(view->v.container, k, 0); + ncplane_putwc(view->v.container, box); } +} - if (!c->ui.page) c->ui.page = 1; +static struct cmc_view_twitter *get_view_by_visual_index(struct cmc_search *search, s32 index) +{ + u32 offset; + struct cmc_view_page *page = get_page_containing_index(search, search->anchor + index, &offset); + if (!page) return NULL; + return (struct cmc_view_twitter *)al_array_at(page->views, offset); +} - if (c->ui.roffset != c->ui.offset) { - notcurses_refresh(c->nc, NULL, NULL); +static void layout_tweets(struct cmc *c, struct cmc_tui *tui, struct cmc_search *search) +{ + struct cmc_view_page *page; + struct cmc_view_twitter *view; + al_array_foreach(search->pages, i, page) { + if (page->processed) continue; + for (u32 j = 0; j < page->views.size; j++) { + view = (struct cmc_view_twitter *)al_array_at(page->views, j); + view->v.rows = layout_tweet(tui, view, tui->cols); + } + page->processed = true; } - notcurses_render(c->nc); - c->ui.roffset = c->ui.offset; + if (search->find_anchor) { + s32 anchor = search->anchor; + if (anchor < 0) { + if (!request_page_at_page_offset(c, search, 0, -1)) { + search->anchor = 0; + search->selected = 0; + search->find_anchor = false; + } + return; + } + u32 count = 1, prev_count; + while (anchor >= 0) { + count = 0; + iterate_views_to_height(search, tui->rows, anchor, &count, NULL, NULL); + if ((s32)(anchor + count) - 1 < search->anchor) break; + anchor--; + if (anchor < 0 && request_page_at_page_offset(c, search, 0, -1)) { + return; + } + prev_count = count; + } + search->anchor = anchor + 1; + search->prev_selected = -1; + search->selected = prev_count - 1; + search->find_anchor = false; + } - if (c->pending_requests == 0 && c->ui.offset + c->ui.page >= c->ui.views.size) { - camu_client_more_results(&c->client, c->search.search); + bool out_of_space = true; + if (search->prev_anchor == -1 || search->anchor != search->prev_anchor) { + al_array_foreach(search->pages, i, page) { + for (u32 j = 0; j < page->views.size; j++) { + view = (struct cmc_view_twitter *)al_array_at(page->views, j); + ncplane_erase(view->v.container); + ncplane_reparent_family(view->v.container, view->v.container); + } + } + search->count = 0; + out_of_space = iterate_views_to_height(search, tui->rows, search->anchor, &search->count, draw_tweet_func, tui); + notcurses_refresh(tui->nc, NULL, NULL); + } else if (search->prev_selected >= 0) { + view = get_view_by_visual_index(search, search->prev_selected); + if (view) { + ncplane_erase(view->v.container); + } + } + view = get_view_by_visual_index(search, search->selected); + if (view) { + draw_selection_line(view, view->v.y, view->v.rows); + } + notcurses_render(tui->nc); + if (search->count > 0 && !out_of_space && search->end_of_page == -1) { + // Index is the last displayed view. + request_page_at_page_offset(c, search, (search->anchor + search->count) - 1, 1); + search->prev_anchor = -1; + } else { + search->prev_anchor = search->anchor; } } static void resource_client_callback(void *userdata, u16 id, struct aki_buffer *buffer) { struct cmc *c = (struct cmc *)userdata; + struct cmc_tui *tui = &c->tui; struct cmc_view_resource *resource; al_array_foreach(c->resource_cache, i, resource) { if (resource->id == id) { @@ -530,62 +635,87 @@ static void resource_client_callback(void *userdata, u16 id, struct aki_buffer * 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); + ncvisual_geom(c->tui.nc, resource->v, &resource->vopts, &resource->geom); break; } } - if (--c->pending_requests == 0) layout_tweets(c); + if (--c->pending_requests == 0) layout_tweets(c, tui, tui->search); +} + +static void add_selected_from_index(struct cmc *c, struct cmc_search *search, u32 index) +{ + struct cmc_view_twitter *view = get_view_by_visual_index(search, search->selected); + if (view && view->v.post->media.size > index) { + camu_client_add(&c->client, &view->v.post->unique_id, index); + camu_client_skip(&c->client, 1); + } +} + +static bool on_last_page(struct cmc_search *search) +{ + return search->end_of_page >= 0 && search->anchor + search->count > (u32)search->end_of_page; +} + +static bool handle_input_search(struct cmc *c, struct cmc_search *search, struct ncinput *input) +{ + if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false; + if (input->id == 'j') { + if (search->selected + 1 >= (s32)search->count) { + if (c->pending_requests != 0 || on_last_page(search)) return false; + search->anchor += search->count; + search->prev_selected = -1; + search->selected = 0; + } else { + search->prev_selected = search->selected; + search->selected++; + } + return true; + } else if (input->id == 'k') { + if (search->selected - 1 < 0) { + if (c->pending_requests != 0) return false; + search->anchor--; + search->find_anchor = true; + search->prev_selected = -1; + } else { + search->prev_selected = search->selected; + search->selected--; + } + return true; + } else if (input->id == NCKEY_RETURN) { + add_selected_from_index(c, search, 0); + } else if (input->id == '1') { + add_selected_from_index(c, search, 0); + } else if (input->id == '2') { + add_selected_from_index(c, search, 1); + } else if (input->id == '3') { + add_selected_from_index(c, search, 2); + } else if (input->id == '4') { + add_selected_from_index(c, search, 3); + } + return false; } static void input_poll_callback(void *userdata, s32 revents) { struct cmc *c = (struct cmc *)userdata; + struct cmc_tui *tui = &c->tui; + struct cmc_search *search = tui->search; (void)revents; struct ncinput input; u32 ret; - bool layout = false; + bool do_layout = false; do { - ret = notcurses_get_nblock(c->nc, &input); + ret = notcurses_get_nblock(c->tui.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') { + if (input.id == 'q') { aki_event_loop_break(&c->loop); break; } + if (search) do_layout = handle_input_search(c, search, &input); } } while (1); - if (layout) layout_tweets(c); + if (do_layout && c->pending_requests == 0) layout_tweets(c, tui, search); } static struct cmc_view_resource *create_resource(struct cmc *c, str *unique_id, u32 index) @@ -593,9 +723,7 @@ static struct cmc_view_resource *create_resource(struct cmc *c, str *unique_id, 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->vopts.scaling = NCSCALE_SCALE; resource->id = al_rand_u16(); al_str_clone(&resource->unique_id, unique_id); resource->index = index; @@ -615,64 +743,150 @@ static struct cmc_view_resource *get_resource_from_cache(struct cmc *c, str *uni return create_resource(c, unique_id, index); } -static int resize_cb(struct ncplane *p) +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 struct cmc_view_twitter *create_view_twitter(struct cmc *c, str *unique_id) { - struct cmc *c = (struct cmc *)ncplane_userptr(p); + struct cmc_view_twitter *view = al_alloc_object(struct cmc_view_twitter); + 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->tui.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); + + 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); + } + + switch (post->media.size) { + case 0: + view->preview.mode = CMC_VIEW_NONE; + break; + case 1: + view->preview.mode = CMC_VIEW_SINGLE; + break; + default: + view->preview.mode = CMC_VIEW_PARTITION; + break; + } + + view->v.rows = 0; + view->v.y = 0; + + return view; +} + +static struct cmc_search *create_search(struct camu_search *s) +{ + struct cmc_search *search = al_alloc_object(struct cmc_search); + search->search = s; + search->find_anchor = false; + search->anchor = 0; + search->prev_anchor = -1; + search->selected = 0; + search->prev_selected = -1; + search->max_page_num = s->last_page; + search->min_page_num = s->last_page; + search->end_of_page = -1; + al_array_init(search->pages); + return search; +} + +static s32 resize_cb(struct ncplane *p) +{ + struct cmc_tui *tui = (struct cmc_tui *)ncplane_userptr(p); u32 width, height; - notcurses_stddim_yx(c->nc, &height, &width); + notcurses_stddim_yx(tui->nc, &height, &width); struct ncplane_options nopts = { 0 }; - nopts.rows = height - 1; - nopts.cols = width - 4; + tui->cols = nopts.cols = width - 4; + tui->rows = nopts.rows = height - 1; 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); - } + tui->p = ncplane_create(p, &nopts); + // TODO: Consider layout. return 0; } static void client_callback(void *userdata, u8 op, void *opaque) { struct cmc *c = (struct cmc *)userdata; + struct cmc_tui *tui = &c->tui; 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)); + case CAMU_CLIENT_STATE_UPDATED: { + switch (c->action) { + case CMC_ACTION_OPEN: { + if (!(tui->nc = notcurses_init(NULL, stdin))) return; + aki_poll_init(&tui->input, input_poll_callback, c); + aki_poll_set(&tui->input, notcurses_inputready_fd(tui->nc), AKI_POLL_READ); + aki_poll_start(&tui->input, &c->loop); + al_array_init(tui->searches); + struct camu_search *s; + al_array_foreach(c->client.state.searches, i, s) { + al_array_push(tui->searches, create_search(s)); } - 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)); + if (tui->searches.size > tui->req_index) { + tui->search = al_array_at(tui->searches, tui->req_index); + u32 last_page = tui->search->search->last_page; + request_page_internal(c, last_page); + } else { + tui->search = NULL; } - 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); + struct ncplane *stdplane = notcurses_stdplane(tui->nc); ncplane_set_resizecb(stdplane, resize_cb); - ncplane_set_userptr(stdplane, c); + ncplane_set_userptr(stdplane, tui); 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; + } + case CMC_ACTION_LIST: { + switch (c->type) { + case CMC_LIST: { + struct camu_list *list; + al_array_foreach(c->client.state.lists, i, list) { + al_printf("%.*s:\n", AL_STR_PRINTF(&list->name)); + str *unique_id; + al_array_foreach_ptr(list->entries, j, unique_id) { + if ((s32)j == list->current) { + al_printf(" > %.*s\n", AL_STR_PRINTF(unique_id)); + } else { + al_printf(" %.*s\n", AL_STR_PRINTF(unique_id)); + } + } + } + aki_event_loop_break(&c->loop); + break; + } + case CMC_SEARCH: { + struct camu_search *search; + al_array_foreach(c->client.state.searches, i, search) { + al_printf("%u %x %.*s\n", i, search->id, AL_STR_PRINTF(&search->query)); + } + aki_event_loop_break(&c->loop); + break; + } + } break; } } @@ -683,79 +897,77 @@ static void client_callback(void *userdata, u8 op, void *opaque) 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 cmc_search *search = tui->search; // wrong + struct camu_result_list *results = (struct camu_result_list *)opaque; - 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); + struct cmc_view_page *page = al_alloc_object(struct cmc_view_page); + al_array_push(search->pages, page); + page->num = results->num; + page->processed = false; + if (page->num > search->max_page_num) search->max_page_num = page->num; + if (page->num < search->min_page_num) search->min_page_num = page->num; + if (search->find_anchor) { + search->anchor += results->unique_ids.size; + if (search->end_of_page >= 0) { + search->end_of_page += results->unique_ids.size; } - 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); - } + str *unique_id; + struct cmc_view_twitter *view; + al_array_foreach_ptr(results->unique_ids, i, unique_id) { + view = create_view_twitter(c, unique_id); + al_array_push(page->views, (struct cmc_view *)view); + } - view->v.has_visual_data = prev_pending == c->pending_requests; + if (--c->pending_requests == 0) layout_tweets(c, tui, search); - 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; } + case CAMU_CLIENT_END_OF_RESULTS: { + struct cmc_search *search = tui->search; // wrong + search->end_of_page = (search->anchor + search->count) - 1; + if (--c->pending_requests == 0) layout_tweets(c, tui, search); + break; + } + case CAMU_CLIENT_DISCONNECTED: + aki_event_loop_break(&c->loop); + break; } } +static struct cmc c = { 0 }; + 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 module; + str query; str *cmd = al_str_c(argv[1]); if (al_str_eq(cmd, al_str_c("search"))) { + c.type = CMC_SEARCH; if (argc == 2) { - c.mode = CMC_LIST_SEARCHES; + c.action = CMC_ACTION_LIST; } else { - c.mode = CMC_CREATE_SEARCH; - al_str_from(&c.search.provider, "twitter"); - al_str_from(&c.search.query, argv[2]); + c.action = CMC_ACTION_CREATE; + al_str_from(&module, "twitter"); + al_str_from(&query, argv[2]); } } else if (al_str_eq(cmd, al_str_c("open"))) { - c.mode = CMC_OPEN_SEARCH; + c.action = CMC_ACTION_OPEN; + if (argc > 2) { + c.tui.req_index = al_str_to_long(al_str_c(argv[2]), 10); + } else { + c.tui.req_index = 0; + } } else if (al_str_eq(cmd, al_str_c("list"))) { + c.type = CMC_LIST; if (argc == 2) { - c.mode = CMC_LIST_LISTS; + c.action = CMC_ACTION_LIST; } else { + c.action = CMC_ACTION_CREATE; } } else { return EXIT_FAILURE; @@ -764,18 +976,31 @@ s32 main(s32 argc, char *argv[]) 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"), TREE_SERVER_IP, TREE_PORT); + if (!camu_client_login(&c.client, al_str_c("andrew"), TREE_SERVER_IP, TREE_PORT)) { + goto err; + } - camu_resource_client_init(&c.resource_client, &c.loop, resource_client_callback, &c); - camu_resource_client_connect(&c.resource_client, TREE_SERVER_IP, TREE_RESOURCE_PORT); + if (c.action == CMC_ACTION_CREATE) { + if (c.type == CMC_SEARCH) { + camu_client_create_search(&c.client, &module, &query); + } + } + + if (c.action == CMC_ACTION_OPEN) { + c.pending_requests = 0; + camu_resource_client_init(&c.resource_client, &c.loop, resource_client_callback, &c); + camu_resource_client_connect(&c.resource_client, TREE_SERVER_IP, TREE_RESOURCE_PORT); + } aki_event_loop_run(&c.loop); - if (c.mode == CMC_OPEN_SEARCH) notcurses_stop(c.nc); + if (c.action == CMC_ACTION_OPEN && c.tui.nc) notcurses_stop(c.tui.nc); camu_client_close(&c.client); aki_common_close(); return EXIT_SUCCESS; +err: + return EXIT_FAILURE; } |