summaryrefslogtreecommitdiff
path: root/src/fruits
diff options
context:
space:
mode:
Diffstat (limited to 'src/fruits')
-rw-r--r--src/fruits/cmc/cmc.c901
-rw-r--r--src/fruits/sink/sink.c4
2 files changed, 567 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;
}
diff --git a/src/fruits/sink/sink.c b/src/fruits/sink/sink.c
index d20f7c4..08d2886 100644
--- a/src/fruits/sink/sink.c
+++ b/src/fruits/sink/sink.c
@@ -106,8 +106,10 @@ static void screen_callback(void *userdata, u8 op, f64 float0)
struct cmv *c = (struct cmv *)userdata;
switch (op) {
case CAMU_SCREEN_NEXT:
+ camu_sink_skip(&c->sink, 1);
break;
case CAMU_SCREEN_PREVIOUS:
+ camu_sink_skip(&c->sink, -1);
break;
case CAMU_SCREEN_TOGGLE_PAUSE:
camu_sink_toggle_pause(&c->sink);
@@ -189,6 +191,8 @@ s32 wmain(s32 argc, wchar_t **argv)
aki_thread_join(&thread0);
+ camu_sink_free(&c.sink);
+
camu_mixer_close(&c.mixer);
c.renderer->free(&c.renderer);
camu_screen_close(&c.scr);