From bbf3314165182e402ff25acccddc004a87f81ef0 Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 12 Feb 2024 21:09:10 -0500 Subject: Initial cmc implementation, sink fixes Signed-off-by: Andrew Opalach --- .gitignore | 3 + flake.lock | 6 +- src/bimu/vcr.c | 11 +- src/buffer/audio.c | 5 +- src/fruits/cmc/cmc.c | 1098 +----------------------- src/fruits/cmc/cmc.h | 27 + src/fruits/cmc/meson.build | 11 +- src/fruits/cmc/tui/pager.c | 88 ++ src/fruits/cmc/tui/pager.h | 33 + src/fruits/cmc/tui/pane_search.c | 330 +++++++ src/fruits/cmc/tui/pane_search.h | 29 + src/fruits/cmc/tui/resources.c | 85 ++ src/fruits/cmc/tui/resources.h | 31 + src/fruits/cmc/tui/ui.c | 133 +++ src/fruits/cmc/tui/ui.h | 38 + src/fruits/cmc/tui/view.h | 24 + src/fruits/cmc/tui/view_twitter.c | 370 ++++++++ src/fruits/cmc/tui/view_twitter.h | 35 + src/libclient/client.c | 2 +- src/libclient/client.h | 2 +- src/libclient/resource_client.c | 4 +- src/libsink/sink.c | 17 +- src/screen/screen.c | 4 +- src/tree/common.h | 2 + src/tree/tree.c | 16 +- src/tree/tree.h | 2 - src/util/meson.build | 5 +- subprojects/BLAKE3.wrap | 6 + subprojects/packagefiles/BLAKE3/CMakeLists.txt | 1 + 29 files changed, 1296 insertions(+), 1122 deletions(-) create mode 100644 src/fruits/cmc/cmc.h create mode 100644 src/fruits/cmc/tui/pager.c create mode 100644 src/fruits/cmc/tui/pager.h create mode 100644 src/fruits/cmc/tui/pane_search.c create mode 100644 src/fruits/cmc/tui/pane_search.h create mode 100644 src/fruits/cmc/tui/resources.c create mode 100644 src/fruits/cmc/tui/resources.h create mode 100644 src/fruits/cmc/tui/ui.c create mode 100644 src/fruits/cmc/tui/ui.h create mode 100644 src/fruits/cmc/tui/view.h create mode 100644 src/fruits/cmc/tui/view_twitter.c create mode 100644 src/fruits/cmc/tui/view_twitter.h create mode 100644 subprojects/BLAKE3.wrap create mode 100644 subprojects/packagefiles/BLAKE3/CMakeLists.txt diff --git a/.gitignore b/.gitignore index aea0bfc..06778dd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ doc/**.html env/bin/ old/ old_old/ +subprojects/BLAKE3/ subprojects/c89atomic/ subprojects/c89atomic.wrap subprojects/ffmpeg-*/ @@ -17,6 +18,8 @@ subprojects/jansson/ subprojects/jansson.wrap subprojects/libakiyo subprojects/libalabaster +subprojects/libakiyo/ +subprojects/libalabaster/ subprojects/libass/ subprojects/libcdio-paranoia/ subprojects/libcurl/ diff --git a/flake.lock b/flake.lock index 06c1ded..f792602 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1706732774, - "narHash": "sha256-hqJlyJk4MRpcItGYMF+3uHe8HvxNETWvlGtLuVpqLU0=", + "lastModified": 1707546158, + "narHash": "sha256-nYYJTpzfPMDxI8mzhQsYjIUX+grorqjKEU9Np6Xwy/0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b8b232ae7b8b144397fdb12d20f592e5e7c1a64d", + "rev": "d934204a0f8d9198e1e4515dd6fec76a139c87f0", "type": "github" }, "original": { diff --git a/src/bimu/vcr.c b/src/bimu/vcr.c index fcef62e..109edd7 100644 --- a/src/bimu/vcr.c +++ b/src/bimu/vcr.c @@ -26,8 +26,7 @@ static aki_thread_result AKI_THREADCALL vcr_stream_thread(void *userdata) { struct bmu_vcr_stream *stream = (struct bmu_vcr_stream *)userdata; struct bmu_vcr *vcr = stream->vcr; - do { - if (!aki_packet_cache_wait(&stream->cache)) break; + while (aki_packet_cache_wait(&stream->cache)) { struct aki_packet *packet = aki_packet_cache_pop(&stream->cache); if (!packet) { stream->client->handle_eof(stream->client); @@ -47,7 +46,7 @@ static aki_thread_result AKI_THREADCALL vcr_stream_thread(void *userdata) } stream->client->handle_data_packet(stream->client, packet); aki_packet_free(packet); - } while (1); + } return 0; } @@ -101,6 +100,8 @@ bool bmu_vcr_push_packet(struct bmu_vcr *vcr, struct aki_packet *packet) aki_packet_free(packet); al_log_warn("bimu", "Unhandled error packet."); break; + default: + al_assert(false); } return true; } @@ -114,7 +115,9 @@ static void vcr_stream_close_internal(struct bmu_vcr_stream *stream) aki_cond_signal(&stream->cond); } aki_mutex_unlock(&stream->mutex); - aki_thread_join(&stream->thread); + if (stream->running) { + aki_thread_join(&stream->thread); + } struct aki_packet *packet; while ((packet = aki_packet_cache_pop(&stream->cache))) { aki_packet_free(packet); diff --git a/src/buffer/audio.c b/src/buffer/audio.c index 037d977..e32ef1f 100644 --- a/src/buffer/audio.c +++ b/src/buffer/audio.c @@ -4,7 +4,7 @@ #include "common.h" #define BUFFER_USEC (12 * 1000000L) -#define BUFFER_WATERMARK_LOW (3 * 1000000L) // Must be a most half of the buffer size. +#define BUFFER_WATERMARK_LOW (4 * 1000000L) // Must be a most half of the buffer size. #define BUFFER_WATERMARK_HIGH (3 * 1000000L) #define DESYNC_PTS 0.022 @@ -35,8 +35,7 @@ static bool setup_optimal_resampler(struct camu_audio_buffer *buf, struct camu_l struct camu_lav_resample_fmt *fmt) { /* - sw - // Update node start?itch ((enum AVSampleFormat)fmt->in_format) { + switch ((enum AVSampleFormat)fmt->in_format) { case AV_SAMPLE_FMT_S16P: fmt->req_format = AV_SAMPLE_FMT_S16; break; diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c index f70cf8d..97ae15d 100644 --- a/src/fruits/cmc/cmc.c +++ b/src/fruits/cmc/cmc.c @@ -4,1068 +4,19 @@ #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 = 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; - u32 rows; - u32 y; - u32 pending_requests; -}; - -struct cmc_view_resource { - u16 id; - str unique_id; - u32 index; - array(struct cmc_view *) refs; - struct ncvisual *v; - struct ncvgeom geom; - struct ncvisual_options vopts; -}; - -struct cmc_view_twitter { - 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_view_page { - u32 num; - bool processed; - array(struct cmc_view *) views; -}; - -enum { - SEARCH_NONE = 0, - SEARCH_UP, - SEARCH_DOWN -}; - -struct cmc_search { - struct camu_search *search; - u8 find_anchor; - s32 anchor; - bool redraw; - u32 count; - s32 selected; - array(struct cmc_view *) prev; - u32 max_page_num; - u32 min_page_num; - s32 end_of_page; - u32 pending_requests; - array(struct cmc_view_page *) pages; -}; - -struct cmc_tui { - struct notcurses *nc; - u32 cols; - u32 rows; - struct ncplane *p; - struct { - struct ncplane *p; - u32 cols; - u32 rows; - bool active; - s32 selected; - } overlay; - struct aki_poll input; - u32 req_index; - array(struct cmc_search *) searches; - struct cmc_search *search; -}; - -struct cmc { - u8 type; - u8 action; - struct aki_event_loop loop; - struct camu_client client; - struct cmc_tui tui; - u32 pending_requests; - struct camu_resource_client resource_client; - 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 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; - 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; -} - -#define TEXT_OFFSET 10 - -#define PROFILE_SIZE 5 -#define PREVIEW_OFFSET_WIDTH 26 - -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; - - u32 rows = 1; - - view->text.width = width - TEXT_OFFSET; - view->text.height = 1 + count_wrapped_lines(&post->text, view->text.width); - view->text.y = repost ? 1 : 0; - view->text.height += view->text.y; - rows += view->text.height; - - if (view->preview.mode == CMC_VIEW_NONE) { - return rows; - } - - view->preview.width = view->text.width - 1; - view->preview.x = TEXT_OFFSET; - u32 partition_width = view->preview.width / 2; - - struct ncplane_options opts = { 0 }; - opts.cols = view->preview.width; - opts.rows = ncplane_dim_y(notcurses_stdplane(tui->nc)) / 3; - opts.x = 0; - opts.y = 0; - struct ncplane *inter = ncplane_create(notcurses_stdplane(tui->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(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: { - ncplane_resize_simple(inter, opts.rows, partition_width); - - resource = view->preview.resources[0]; - resource->vopts.n = inter; - 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(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(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(tui->nc, resource->v, &resource->vopts, &resource->geom); - right += resource->geom.rcelly + 1; - } - - break; - } - } - - ncplane_destroy(inter); - - view->preview.height = AL_MAX(right, left); - - return rows + view->preview.height; -} - -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 >= 1000000000) { - al_sprintf(out, "%.1fB", number / 1000000000.f); - } else 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); - } -} - - -static void draw_tweet_resources(struct cmc_tui *tui, struct cmc_view_twitter *view) -{ - 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); - - 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); - - 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[1]->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; - } - } - - 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(tui->nc, resource->v, &resource->vopts); - } -} - -static char format_buf[32]; - -static void draw_tweet(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); - - 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 = 0; - - if (post->comments.set) { - 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"❤"; - 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 + 2, format_buf); - } -} - -static struct cmc_view_page *get_page_from_num(struct cmc_search *search, u32 num) -{ - 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) { - if (offset) *offset = size - (count - index); - return page; - } - } - return NULL; -} - -static void request_page_internal(struct cmc *c, u32 num) -{ - struct cmc_search *search = c->tui.search; - search->pending_requests++; - camu_client_get_page(&c->client, search->search, num); -} - -// Given an index of a post, request the page to the offset of the page containing that post. -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; - struct cmc_view_page *page = get_page_containing_index(search, anchor, &offset); - al_assert(page); - struct cmc_view_twitter *view; - do { - if (!page->processed) { - // If the page is not processed, return out of space as to not display - // a discontinuity (if the page after happens to be processed). - return true; - } - for (; offset < page->views.size; offset++) { - view = (struct cmc_view_twitter *)al_array_at(page->views, offset); - // If this post would draw past the height, return out of space. - if (y + view->v.rows > height) return true; - if (func) func(userdata, view, y); - (*count)++; - y += view->v.rows + 1; - } - } while ((offset = 0, (page = get_page_from_num(search, page->num + 1)))); - return false; -} - -static void draw_tweet_func(void *userdata, struct cmc_view_twitter *view, u32 y) -{ - struct cmc_tui *tui = (struct cmc_tui *)userdata; - struct cmc_search *search = tui->search; - ncplane_reparent_family(view->v.container, tui->p); - ncplane_move_yx(view->v.container, y, 0); - draw_tweet_resources(tui, view); - view->v.y = y; - al_array_push(search->prev, &view->v); -} - -static void draw_selection_line(struct cmc_view *view, u32 y, u32 rows) -{ - ncplane_move_yx(view->container, y, 0); - wchar_t box = L'█'; - u32 k = ((struct cmc_view_twitter *)view)->repost ? 1 : 0; - for (; k < rows - 1; k++) { - ncplane_cursor_move_yx(view->container, k, 0); - ncplane_putwc(view->container, box); - } -} - -static struct cmc_view *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 || !page->processed) return NULL; - return al_array_at(page->views, offset); -} - -static void draw_overlay(struct cmc_tui *tui) -{ - ncplane_cursor_move_yx(tui->overlay.p, 0, 0); - ncplane_erase(tui->overlay.p); - - u64 color = 0; - u32 background = 0; - ncchannel_set_rgb8(&background, 255, 255, 255); - ncchannel_set_alpha(&background, 255); - ncchannels_set_fchannel(&color, background); - - /* - struct nccell ul, ur; nccell_init(&ul); nccell_init(&ur); - struct nccell ll, lr; nccell_init(&ll); nccell_init(&lr); - struct nccell hl, vl; nccell_init(&hl); nccell_init(&vl); - //nccell_set_channels(&hl, color); - //nccell_set_channels(&vl, color); - - ncplane_box(tui->overlay.p, &ul, &ur, &ll, &lr, &hl, &vl, tui->overlay.rows - 1, tui->overlay.cols - 1, 0); - - nccell_release(tui->overlay.p, &ul); nccell_release(tui->overlay.p, &ur); - nccell_release(tui->overlay.p, &ll); nccell_release(tui->overlay.p, &lr); - nccell_release(tui->overlay.p, &hl); nccell_release(tui->overlay.p, &vl); - */ - - //ncplane_double_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0); - //ncplane_rounded_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0); - - //ncchannel_set_palindex(&background, 1); - ncchannel_set_rgb8(&background, 124, 12, 53); - ncchannels_set_bchannel(&color, background); - ncchannels_set_fchannel(&color, background); - ncplane_gradient(tui->overlay.p, 0, 0, tui->overlay.rows, tui->overlay.cols, "", 0, color, color, color, color); - - ncplane_set_bg_rgb8(tui->overlay.p, 124, 12, 53); - //ncplane_set_fg_palindex(tui->overlay.p, 4); - //ncplane_set_bg_alpha(tui->overlay.p, 0); - //ncplane_cursor_move_yx(tui->overlay.p, 1, 1); - //ncplane_cursor_move_yx(tui->overlay.p, 0, 1); - - struct cmc_search *search; - al_array_foreach(tui->searches, i, search) { - if ((s32)i == tui->overlay.selected) { - ncplane_set_fg_palindex(tui->overlay.p, 4); - } else { - ncplane_set_fg_default(tui->overlay.p); - } - str *query = &search->search->query; - for (u32 j = 0; j < query->len; j++) { - ncplane_putchar_yx(tui->overlay.p, i, j + 1, al_str_at(query, j)); - } - } -} - -static void refresh_pane_search(struct cmc *c, struct cmc_tui *tui, struct cmc_search *search) -{ - if (search->find_anchor == SEARCH_UP) { - u32 count; - // If find_anchor = SEARCH_UP, search->anchor is index of the requested view. - s32 anchor = search->anchor; - while (anchor >= 0) { - count = 0; - iterate_views_to_height(search, tui->rows, anchor, &count, NULL, NULL); - // Is the requested view still visible? - if ((s32)(anchor + count) - 1 < search->anchor) break; - anchor--; - // Load more posts if needed. - if (anchor < 0 && request_page_at_page_offset(c, search, 0, -1)) return; - } - anchor++; // We want the anchor before the last attempt above. - search->selected = abs(search->anchor - anchor); - search->anchor = anchor; - search->redraw = true; - search->find_anchor = SEARCH_NONE; - } else if (search->find_anchor == SEARCH_DOWN) { - // When moving down we already know how many views are being displayed - // so just move the anchor 1 past that. - search->selected = 0; - search->anchor += search->count; - search->redraw = true; - search->find_anchor = SEARCH_NONE; - } - - if (tui->overlay.active) draw_overlay(tui); - - struct cmc_view *view; - bool out_of_space = true; - if (search->redraw) { - al_array_foreach(search->prev, j, view) { - ncplane_erase(view->container); - ncplane_reparent_family(view->container, view->container); - } - search->prev.size = 0; - search->count = 0; - out_of_space = iterate_views_to_height(search, tui->rows, search->anchor, &search->count, draw_tweet_func, tui); - } else { - al_array_foreach(search->prev, j, view) { - ncplane_erase(view->container); - } - } - if ((view = get_view_by_visual_index(search, search->selected))) { - draw_selection_line(view, view->y, view->rows); - } - if (search->redraw) { - search->redraw = false; - notcurses_refresh(tui->nc, NULL, NULL); - } - notcurses_render(tui->nc); - if (search->pending_requests == 0 && search->count > 0 && !out_of_space && search->end_of_page == -1) { - request_page_at_page_offset(c, search, (search->anchor + search->count) - 1, 1); - } -} - -static void maybe_process_pages(struct cmc *c) -{ - struct cmc_tui *tui = &c->tui; - bool do_redraw = false; - struct cmc_search *search; - al_array_foreach(tui->searches, i, search) { - struct cmc_view_page *page; - al_array_foreach(search->pages, j, page) { - if (page->processed) continue; - struct cmc_view *view; - bool any_pending_requests = false; - al_array_foreach(page->views, k, view) { - if (view->pending_requests > 0) { - any_pending_requests = true; - break; - } - } - if (any_pending_requests) continue; - do_redraw = true; - al_array_foreach(page->views, k, view) { - view->rows = layout_tweet(tui, (struct cmc_view_twitter *)view, tui->cols); - draw_tweet((struct cmc_view_twitter *)view, view->container, view->rows, tui->cols); - } - page->processed = true; - } - } - search = tui->search; - if (do_redraw) { - search->redraw = true; - refresh_pane_search(c, tui, 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, 0); - if (channels == 3) { - resource->v = ncvisual_from_rgb_packed(pixels, h, w * channels, w, 255); - } else if (channels == 4) { - resource->v = ncvisual_from_rgba(pixels, h, w * channels, w); - } else { - al_log_warn("cmc", "Image has unsupported channel count."); - } - stbi_image_free(pixels); - ncvisual_geom(c->tui.nc, resource->v, &resource->vopts, &resource->geom); - struct cmc_view *view; - al_array_foreach(resource->refs, j, view) { - view->pending_requests--; - } - al_array_free(resource->refs); - break; - } - } - maybe_process_pages(c); -} - -static void add_selected_from_index(struct cmc *c, struct cmc_search *search, u32 index) -{ - struct cmc_view *view = get_view_by_visual_index(search, search->selected); - if (view && view->post->media.size > index) { - camu_client_add(&c->client, &view->post->unique_id, index); - camu_client_skip(&c->client, 1); - } -} - -#define ON_LAST_PAGE(search) \ - (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 (search->pending_requests > 0 || search->find_anchor != SEARCH_NONE) return false; - if (ON_LAST_PAGE(search)) return false; - u32 index = search->anchor + search->count; - search->find_anchor = SEARCH_DOWN; - struct cmc_view_page *page = get_page_containing_index(search, index, NULL); - if (!page) { - request_page_at_page_offset(c, search, index - 1, 1); - return false; - } - } else { - search->selected++; - } - return true; - } else if (input->id == 'k') { - if (search->selected - 1 < 0) { - if (search->pending_requests > 0 || search->find_anchor != SEARCH_NONE) return false; - if (search->anchor - 1 < 0 && !request_page_at_page_offset(c, search, 0, -1)) { - // The reason to do this here instead of letting it resolve in - // refresh_pane_search() is so we can return early if - // we are at the top of the page. - return false; - } - search->anchor--; - search->find_anchor = SEARCH_UP; - // Wait for the requested previous page to come in. - if (search->anchor < 0) return false; - } else { - search->selected--; - } - return true; - } else if (input->id == NCKEY_RETURN || 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 toggle_overlay_internal(struct cmc_tui *tui) -{ - if (tui->overlay.active) { - ncplane_reparent_family(tui->overlay.p, tui->overlay.p); - } else { - ncplane_reparent_family(tui->overlay.p, tui->p); - } - tui->overlay.active = !tui->overlay.active; -} - -static bool handle_input_overlay(struct cmc *c, struct cmc_tui *tui, struct ncinput *input) -{ - if (input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN) { - if (input->id == 'j') { - if (tui->overlay.selected + 1 < (s32)tui->searches.size) { - tui->overlay.selected++; - return true; - } - } else if (input->id == 'k') { - if (tui->overlay.selected - 1 >= 0) { - tui->overlay.selected--; - return true; - } - } else if (input->id == NCKEY_RETURN) { - toggle_overlay_internal(tui); - struct cmc_search *search = tui->search; - struct cmc_view *view; - al_array_foreach(search->prev, j, view) { - ncplane_erase(view->container); - ncplane_reparent_family(view->container, view->container); - } - search->prev.size = 0; - tui->search = al_array_at(tui->searches, tui->overlay.selected); - search = tui->search; - search->redraw = true; - u32 last_page = search->search->last_page; - if (!get_page_from_num(search, last_page)) { - request_page_internal(c, last_page); - } else { - return true; - } - } - } - return false; -} - -static void input_poll_callback(void *userdata, s32 revents) -{ - struct cmc *c = (struct cmc *)userdata; - struct cmc_tui *tui = &c->tui; - (void)revents; - struct ncinput input; - u32 ret; - bool do_refresh = false; - do { - 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 == 'q') { - aki_event_loop_break(&c->loop); - break; - } else if (input.id == NCKEY_TAB) { - toggle_overlay_internal(tui); - do_refresh = true; - tui->search->redraw = true; - continue; - } - if (tui->overlay.active) do_refresh |= handle_input_overlay(c, tui, &input); - else if (tui->search) do_refresh |= handle_input_search(c, tui->search, &input); - } - } while (1); - if (do_refresh) refresh_pane_search(c, tui, tui->search); -} - -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.scaling = NCSCALE_SCALE; - resource->id = al_rand_u16(); - al_str_clone(&resource->unique_id, unique_id); - resource->index = index; - al_array_init(resource->refs); - 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, struct cmc_view *view, - 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; - } - } - resource = create_resource(c, unique_id, index); - view->pending_requests++; - al_array_push(resource->refs, view); - return resource; -} - -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 void strip_link_from_tweet(struct sho_post *post) -{ - wstr *check = al_wstr_c(L"https://t.co/"); - s32 index = al_wstr_rfind(&post->text, L':'); - if (index < 5) return; - index -= 5; // [h]ttps:// (if matched). - if (al_wstr_cmp(&post->text, check, index, check->len) == 0) { - al_wstr_at(&post->text, index) = L'\0'; - post->text.len = index; - } -} - -static struct cmc_view_twitter *create_view_twitter(struct cmc *c, str *unique_id) -{ - 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); - - if (view->v.post->media.size > 0) { - strip_link_from_tweet(view->v.post); - } - - view->v.rows = 0; - view->v.y = 0; - view->v.pending_requests = 0; - - 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, &view->v, &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, &view->v, &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; - } - - 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 = SEARCH_NONE; - search->anchor = 0; - search->redraw = true; - search->selected = 0; - al_array_init(search->prev); - search->max_page_num = s->last_page; - search->min_page_num = s->last_page; - search->end_of_page = -1; - search->pending_requests = 0; - 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(tui->nc, &height, &width); - struct ncplane_options nopts = { 0 }; - tui->cols = nopts.cols = width - 4; - tui->rows = nopts.rows = height - 1; - nopts.x = 3; - nopts.y = 1; - nopts.flags = 0; - tui->p = ncplane_create(p, &nopts); - tui->overlay.cols = nopts.cols = 35; - tui->overlay.rows = nopts.rows = 20; - nopts.x = (tui->cols / 2) - (tui->overlay.cols / 2); - nopts.y = (tui->rows / 2) - (tui->overlay.rows / 2); - tui->overlay.p = ncpile_create(tui->nc, &nopts); - //ncplane_reparent_family(tui->overlay.p, tui->p); - return 0; -} - -static struct cmc_search *get_search_from_id(struct cmc_tui *tui, s32 id) -{ - struct cmc_search *search; - al_array_foreach(tui->searches, i, search) { - if (search->search->id == id) return search; - } - return NULL; -} +#include "cmc.h" 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_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)); - } - 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; - } - tui->overlay.active = false; - tui->overlay.selected = 0; - struct ncplane *stdplane = notcurses_stdplane(tui->nc); - ncplane_set_resizecb(stdplane, resize_cb); - ncplane_set_userptr(stdplane, tui); - resize_cb(stdplane); + cmc_ui_open(&c->ui, c); break; } case CMC_ACTION_LIST: { @@ -1105,41 +56,11 @@ static void client_callback(void *userdata, u8 op, void *opaque) break; } case CAMU_CLIENT_RESULTS: { - struct camu_result_list *results = (struct camu_result_list *)opaque; - struct cmc_search *search = get_search_from_id(tui, results->search->id); - if (!search) return; - search->pending_requests--; - - 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_UP) { - search->anchor += results->unique_ids.size; - if (search->end_of_page >= 0) { - search->end_of_page += results->unique_ids.size; - } - } - - 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, &view->v); - } - - maybe_process_pages(c); - + cmc_ui_handle_results(&c->ui.search, (struct camu_result_list *)opaque); break; } case CAMU_CLIENT_END_OF_RESULTS: { - struct cmc_search *search = get_search_from_id(tui, *((s32 *)opaque)); - if (!search) return; - search->pending_requests--; - search->end_of_page = (search->anchor + search->count) - 1; - maybe_process_pages(c); + cmc_ui_handle_end_of_results(&c->ui.search, *((s32 *)opaque)); break; } case CAMU_CLIENT_DISCONNECTED: @@ -1169,11 +90,6 @@ s32 main(s32 argc, char *argv[]) } } else if (al_str_eq(cmd, al_str_c("open"))) { 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) { @@ -1196,15 +112,11 @@ s32 main(s32 argc, char *argv[]) if (c.type == CMC_SEARCH) { camu_client_create_search(&c.client, &module, &query); } - } else 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.action == CMC_ACTION_OPEN && c.tui.nc) notcurses_stop(c.tui.nc); + if (c.action == CMC_ACTION_OPEN && c.ui.nc) notcurses_stop(c.ui.nc); camu_client_close(&c.client); diff --git a/src/fruits/cmc/cmc.h b/src/fruits/cmc/cmc.h new file mode 100644 index 0000000..625d256 --- /dev/null +++ b/src/fruits/cmc/cmc.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "../../libclient/client.h" + +#include "tui/ui.h" + +enum { + CMC_LIST = 0, + CMC_SEARCH +}; + +enum { + CMC_ACTION_OPEN = 0, + CMC_ACTION_LIST, + CMC_ACTION_CREATE, +}; + +struct cmc { + u8 type; + u8 action; + struct aki_event_loop loop; + struct camu_client client; + struct cmc_ui ui; +}; diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build index 45ac9c2..6dd3960 100644 --- a/src/fruits/cmc/meson.build +++ b/src/fruits/cmc/meson.build @@ -1,5 +1,12 @@ -cmc_src = ['cmc.c'] -cmc_deps = [common_deps, libclient, cache, stb_image] +cmc_src = [ + 'cmc.c', + 'tui/ui.c', + 'tui/resources.c', + 'tui/pager.c', + 'tui/view_twitter.c', + 'tui/pane_search.c' +] +cmc_deps = [common_deps, libclient, subproject('stb').get_variable('stb')] notcurses = dependency('notcurses') cmc_deps += [notcurses] diff --git a/src/fruits/cmc/tui/pager.c b/src/fruits/cmc/tui/pager.c new file mode 100644 index 0000000..00c5665 --- /dev/null +++ b/src/fruits/cmc/tui/pager.c @@ -0,0 +1,88 @@ +#include "pager.h" + +void cmc_pager_init(struct cmc_pager *pager, u32 page) +{ + pager->find_anchor = PAGE_NONE; + pager->anchor = 0; + pager->redraw = true; + pager->selected = 0; + al_array_init(pager->prev); + pager->max_page_num = page; + pager->min_page_num = page; + pager->end_of_page = -1; + pager->pending_requests = 0; + al_array_init(pager->pages); +} + +static bool any_pending_requests(struct cmc_view_page *page) +{ + struct cmc_view *view; + al_array_foreach(page->views, j, view) { + if (view->pending_requests > 0) { + return true; + } + } + return false; +} + +bool cmc_pager_maybe_process_pages(struct cmc_pager *pager, void (*func)(void *, struct cmc_view_page *), void *userdata) +{ + bool any_page_processed = false; + struct cmc_view_page *page; + al_array_foreach(pager->pages, i, page) { + if (page->processed) continue; + if (!any_pending_requests(page)) { + page->processed = true; + func(userdata, page); + any_page_processed = true; + } + } + return any_page_processed; +} + +struct cmc_view_page *cmc_get_page_from_num(struct cmc_pager *pager, u32 num) +{ + struct cmc_view_page *page; + al_array_foreach(pager->pages, i, page) { + if (page->num == num) return page; + } + return NULL; +} + +struct cmc_view_page *cmc_pager_page_containing_index(struct cmc_pager *pager, u32 index, u32 *offset) +{ + u32 count = 0, size = 0; + struct cmc_view_page *page = NULL; + for (u32 i = pager->min_page_num; i <= pager->max_page_num; i++) { + if (!(page = cmc_get_page_from_num(pager, i))) continue; + size = page->views.size; + count += size; + if (count > index) { + if (offset) *offset = size - (count - index); + return page; + } + } + return NULL; +} + +bool cmc_pager_iterate_to_height(struct cmc_pager *pager, u32 height, u32 anchor, u32 *count, + void (*func)(void *, struct cmc_view *, u32), void *userdata) +{ + u32 y = 0, offset; + struct cmc_view_page *page = cmc_pager_page_containing_index(pager, anchor, &offset); + al_assert(page); + struct cmc_view *view; + do { + // If the page is not processed, return out of space as to not display a discontinuity. + if (!page->processed) return true; + for (; offset < page->views.size; offset++) { + view = al_array_at(page->views, offset); + // If this post would draw past the height, return out of space. + if (y + view->rows > height) return true; + if (func) func(userdata, view, y); + (*count)++; + y += view->rows + 1; + } + } while ((offset = 0, (page = cmc_get_page_from_num(pager, page->num + 1)))); + return false; +} diff --git a/src/fruits/cmc/tui/pager.h b/src/fruits/cmc/tui/pager.h new file mode 100644 index 0000000..6aeeb9c --- /dev/null +++ b/src/fruits/cmc/tui/pager.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "view.h" + +enum { + PAGE_NONE = 0, + PAGE_UP, + PAGE_DOWN +}; + +struct cmc_pager { + u8 find_anchor; + s32 anchor; + bool redraw; + u32 count; + s32 selected; + array(struct cmc_view *) prev; + u32 max_page_num; + u32 min_page_num; + s32 end_of_page; + u32 pending_requests; + array(struct cmc_view_page *) pages; +}; + +void cmc_pager_init(struct cmc_pager *pager, u32 page); +bool cmc_pager_maybe_process_pages(struct cmc_pager *pager, void (*func)(void *, struct cmc_view_page *), void *userdata); +struct cmc_view_page *cmc_get_page_from_num(struct cmc_pager *pager, u32 num); +struct cmc_view_page *cmc_pager_page_containing_index(struct cmc_pager *pager, u32 index, u32 *offset); +bool cmc_pager_iterate_to_height(struct cmc_pager *pager, u32 height, u32 anchor, u32 *count, + void (*func)(void *, struct cmc_view *, u32), void *userdata); diff --git a/src/fruits/cmc/tui/pane_search.c b/src/fruits/cmc/tui/pane_search.c new file mode 100644 index 0000000..4c14939 --- /dev/null +++ b/src/fruits/cmc/tui/pane_search.c @@ -0,0 +1,330 @@ +#include +#include + +#include "../cmc.h" + +#include "pane_search.h" +#include "view_twitter.h" +#include "ui.h" + +static void request_page_internal(struct cmc *c, struct cmc_search *search, u32 num) +{ + search->pager.pending_requests++; + camu_client_get_page(&c->client, search->search, num); +} + +void cmc_pane_search_init(struct cmc_pane_search *pane, struct cmc_ui *ui) +{ + pane->ui = ui; + al_array_init(pane->searches); + pane->search = NULL; + struct camu_search *s; + al_array_foreach(pane->ui->c->client.state.searches, i, s) { + struct cmc_search *search = al_alloc_object(struct cmc_search); + search->search = s; + cmc_pager_init(&search->pager, s->page); + al_array_push(pane->searches, search); + } + if (pane->searches.size > 0) { + pane->search = al_array_at(pane->searches, 0); + request_page_internal(pane->ui->c, pane->search, pane->search->search->page); + } +} + +static struct cmc_view *get_view_by_visual_index(struct cmc_pager *pager, s32 index) +{ + u32 offset; + struct cmc_view_page *page = cmc_pager_page_containing_index(pager, pager->anchor + index, &offset); + if (!page || !page->processed) return NULL; + return al_array_at(page->views, offset); +} + +// Given an index of a post, request the page to the offset of the page containing that post. +static bool request_page_at_page_offset(struct cmc_pane_search *pane, struct cmc_pager *pager, u32 index, s32 offset) +{ + struct cmc_view_page *page = cmc_pager_page_containing_index(pager, index, NULL); + if (page) { + s32 req = (s32)page->num + offset; + if (req < 0) return false; + request_page_internal(pane->ui->c, pane->search, req); + } + return true; +} + +static void add_selected_from_index(struct cmc *c, struct cmc_pager *pager, u32 index) +{ + struct cmc_view *view = get_view_by_visual_index(pager, pager->selected); + if (view && view->post->media.size > index) { + camu_client_add(&c->client, &view->post->unique_id, index); + camu_client_skip(&c->client, 1); + } +} + +#define ON_LAST_PAGE(pager) \ + (pager->end_of_page >= 0 && pager->anchor + pager->count > (u32)pager->end_of_page) + +bool cmc_pane_search_handle_input(struct cmc_pane_search *pane, struct ncinput *input) +{ + struct cmc_pager *pager = &pane->search->pager; + if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false; + if (input->id == 'j') { + if (pager->selected + 1 >= (s32)pager->count) { + if (pager->pending_requests > 0 || pager->find_anchor != PAGE_NONE) return false; + if (ON_LAST_PAGE(pager)) return false; + u32 index = pager->anchor + pager->count; + pager->find_anchor = PAGE_DOWN; + struct cmc_view_page *page = cmc_pager_page_containing_index(pager, index, NULL); + if (!page) { + request_page_at_page_offset(pane, pager, index - 1, 1); + return false; + } + } else { + pager->selected++; + } + return true; + } else if (input->id == 'k') { + if (pager->selected - 1 < 0) { + if (pager->pending_requests > 0 || pager->find_anchor != PAGE_NONE) return false; + if (pager->anchor - 1 < 0 && !request_page_at_page_offset(pane, pager, 0, -1)) { + // The reason to do this here instead of letting it resolve in + // refresh_pane_pager() is so we can return early if + // we are at the top of the page. + return false; + } + pager->anchor--; + pager->find_anchor = PAGE_UP; + // Wait for the requested previous page to come in. + if (pager->anchor < 0) return false; + } else { + pager->selected--; + } + return true; + } else if (input->id == NCKEY_RETURN || input->id == '1') { + add_selected_from_index(pane->ui->c, pager, 0); + } else if (input->id == '2') { + add_selected_from_index(pane->ui->c, pager, 1); + } else if (input->id == '3') { + add_selected_from_index(pane->ui->c, pager, 2); + } else if (input->id == '4') { + add_selected_from_index(pane->ui->c, pager, 3); + } + return false; +} + +bool cmc_pane_search_handle_input_overlay(struct cmc_pane_search *pane, struct ncinput *input) +{ + if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false; + struct cmc_ui *ui = pane->ui; + struct cmc_pager *pager = &pane->search->pager; + if (input->id == 'j') { + if (ui->overlay.selected + 1 < (s32)pane->searches.size) { + ui->overlay.selected++; + return true; + } + } else if (input->id == 'k') { + if (ui->overlay.selected - 1 >= 0) { + ui->overlay.selected--; + return true; + } + } else if (input->id == NCKEY_RETURN) { + cmc_ui_toggle_overlay(ui); + struct cmc_view *view; + al_array_foreach(pager->prev, j, view) { + ncplane_erase(view->container); + ncplane_reparent_family(view->container, view->container); + } + pager->prev.size = 0; + pane->search = al_array_at(pane->searches, ui->overlay.selected); + pager = &pane->search->pager; + pager->redraw = true; + u32 page = pane->search->search->page; + if (!cmc_get_page_from_num(pager, page)) { + request_page_internal(ui->c, pane->search, page); + } else { + return true; + } + } + return false; +} + +void cmc_pane_search_maybe_draw_overlay(struct cmc_pane_search *pane) +{ + struct cmc_ui *ui = pane->ui; + + if (!ui->overlay.active) return; + + ncplane_cursor_move_yx(ui->overlay.p, 0, 0); + ncplane_erase(ui->overlay.p); + + u64 color = 0; + u32 background = 0; + ncchannel_set_rgb8(&background, 255, 255, 255); + ncchannel_set_alpha(&background, 255); + ncchannels_set_fchannel(&color, background); + + ncchannel_set_rgb8(&background, 124, 12, 53); + ncchannels_set_bchannel(&color, background); + ncchannels_set_fchannel(&color, background); + ncplane_gradient(ui->overlay.p, 0, 0, ui->overlay.rows, ui->overlay.cols, "", 0, color, color, color, color); + + ncplane_set_bg_rgb8(ui->overlay.p, 124, 12, 53); + + struct cmc_search *search; + al_array_foreach(ui->search.searches, i, search) { + if ((s32)i == ui->overlay.selected) { + ncplane_set_fg_palindex(ui->overlay.p, 4); + } else { + ncplane_set_fg_default(ui->overlay.p); + } + str *query = &search->search->query; + for (u32 j = 0; j < query->len; j++) { + ncplane_putchar_yx(ui->overlay.p, i, j + 1, al_str_at(query, j)); + } + } +} + +static void draw_selection_line(struct cmc_view *view, u32 y, u32 rows) +{ + ncplane_move_yx(view->container, y, 0); + wchar_t box = L'█'; + u32 k = ((struct cmc_view_twitter *)view)->repost ? 1 : 0; + for (; k < rows - 1; k++) { + ncplane_cursor_move_yx(view->container, k, 0); + ncplane_putwc(view->container, box); + } +} + +void cmc_pane_search_refresh(struct cmc_pane_search *pane) +{ + struct cmc_pager *pager = &pane->search->pager; + if (pager->find_anchor == PAGE_UP) { + u32 count; + // If find_anchor = PAGE_UP, search->anchor is index of the requested view. + s32 anchor = pager->anchor; + while (anchor >= 0) { + count = 0; + cmc_pager_iterate_to_height(pager, pane->ui->rows, anchor, &count, NULL, NULL); + // Is the requested view still visible? + if ((s32)(anchor + count) - 1 < pager->anchor) break; + anchor--; + // Load more posts if needed. + if (anchor < 0 && request_page_at_page_offset(pane, pager, 0, -1)) return; + } + anchor++; // We want the anchor before the last attempt above. + pager->selected = abs(pager->anchor - anchor); + pager->anchor = anchor; + pager->redraw = true; + pager->find_anchor = PAGE_NONE; + } else if (pager->find_anchor == PAGE_DOWN) { + // When moving down we already know how many views are being displayed + // so just move the anchor 1 past that. + pager->selected = 0; + pager->anchor += pager->count; + pager->redraw = true; + pager->find_anchor = PAGE_NONE; + } + + cmc_pane_search_maybe_draw_overlay(pane); + + struct cmc_view *view; + bool out_of_space = true; + if (pager->redraw) { + al_array_foreach(pager->prev, j, view) { + ncplane_erase(view->container); + ncplane_reparent_family(view->container, view->container); + } + pager->prev.size = 0; + pager->count = 0; + out_of_space = cmc_pager_iterate_to_height(pager, pane->ui->rows, pager->anchor, &pager->count, cmc_draw_tweet_func, pane->ui); + } else { + al_array_foreach(pager->prev, j, view) { + ncplane_erase(view->container); + } + } + if ((view = get_view_by_visual_index(pager, pager->selected))) { + draw_selection_line(view, view->y, view->rows); + } + if (pager->redraw) { + pager->redraw = false; + notcurses_refresh(pane->ui->nc, NULL, NULL); + } + notcurses_render(pane->ui->nc); + if (pager->pending_requests == 0 && pager->count > 0 && !out_of_space && pager->end_of_page == -1) { + request_page_at_page_offset(pane, pager, (pager->anchor + pager->count) - 1, 1); + } +} + +static struct cmc_search *get_search_from_id(struct cmc_pane_search *pane, s32 id) +{ + struct cmc_search *search; + al_array_foreach(pane->searches, i, search) { + if (search->search->id == id) return search; + } + return NULL; +} + +static void process_page_func(void *userdata, struct cmc_view_page *page) +{ + struct cmc_ui *ui = (struct cmc_ui *)userdata; + struct cmc_view *view; + al_array_foreach(page->views, i, view) { + view->rows = cmc_layout_tweet(ui, (struct cmc_view_twitter *)view, ui->cols); + cmc_draw_tweet((struct cmc_view_twitter *)view, view->container, view->rows, ui->cols); + } +} + +void cmc_ui_resources_callback(void *userdata) +{ + struct cmc_pane_search *pane = (struct cmc_pane_search *)userdata; + struct cmc_pager *pager = &pane->search->pager; + if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) { + pager->redraw = true; + cmc_pane_search_refresh(pane); + } +} + +void cmc_ui_handle_results(struct cmc_pane_search *pane, struct camu_result_list *results) +{ + struct cmc_search *search = get_search_from_id(pane, results->search->id); + if (!search) return; + struct cmc_pager *pager = &search->pager; + pager->pending_requests--; + + struct cmc_view_page *page = al_alloc_object(struct cmc_view_page); + al_array_push(pager->pages, page); + page->num = results->num; + page->processed = false; + if (page->num > pager->max_page_num) pager->max_page_num = page->num; + if (page->num < pager->min_page_num) pager->min_page_num = page->num; + if (pager->find_anchor == PAGE_UP) { + pager->anchor += results->unique_ids.size; + if (pager->end_of_page >= 0) { + pager->end_of_page += results->unique_ids.size; + } + } + + str *unique_id; + struct cmc_view_twitter *view; + al_array_foreach_ptr(results->unique_ids, i, unique_id) { + view = cmc_create_view_twitter(pane->ui, unique_id); + al_array_push(page->views, &view->v); + } + + if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) { + pager->redraw = true; + cmc_pane_search_refresh(pane); + } +} + +void cmc_ui_handle_end_of_results(struct cmc_pane_search *pane, s32 id) +{ + struct cmc_search *search = get_search_from_id(pane, id); + if (!search) return; + struct cmc_pager *pager = &search->pager; + pager->pending_requests--; + pager->end_of_page = (pager->anchor + pager->count) - 1; + if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) { + pager->redraw = true; + cmc_pane_search_refresh(pane); + } +} diff --git a/src/fruits/cmc/tui/pane_search.h b/src/fruits/cmc/tui/pane_search.h new file mode 100644 index 0000000..b7c5e2c --- /dev/null +++ b/src/fruits/cmc/tui/pane_search.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "../../libclient/client.h" + +#include "pager.h" + +struct cmc_search { + struct camu_search *search; + struct cmc_pager pager; +}; + +struct cmc_ui; +struct cmc_pane_search { + array(struct cmc_search *) searches; + struct cmc_search *search; + struct cmc_ui *ui; +}; + +void cmc_pane_search_init(struct cmc_pane_search *pane, struct cmc_ui *ui); +bool cmc_pane_search_handle_input(struct cmc_pane_search *pane, struct ncinput *input); +bool cmc_pane_search_handle_input_overlay(struct cmc_pane_search *pane, struct ncinput *input); +void cmc_pane_search_draw_overlay(struct cmc_pane_search *pane); +void cmc_pane_search_refresh(struct cmc_pane_search *pane); + +void cmc_ui_resources_callback(void *userdata); +void cmc_ui_handle_results(struct cmc_pane_search *pane, struct camu_result_list *results); +void cmc_ui_handle_end_of_results(struct cmc_pane_search *pane, s32 id); diff --git a/src/fruits/cmc/tui/resources.c b/src/fruits/cmc/tui/resources.c new file mode 100644 index 0000000..19513bf --- /dev/null +++ b/src/fruits/cmc/tui/resources.c @@ -0,0 +1,85 @@ +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#define STBI_NO_STDIO +#define STBI_NO_FAILURE_STRINGS +#define STBI_MALLOC al_malloc +#define STBI_FREE al_free +#define STBI_REALLOC al_realloc +#include + +#include "resources.h" +#include "view.h" + +static void resource_client_callback(void *userdata, u16 id, struct aki_buffer *buffer) +{ + struct cmc_resources *resources = (struct cmc_resources *)userdata; + struct cmc_view_resource *resource; + al_array_foreach(resources->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, 0); + if (channels == 3) { + resource->v = ncvisual_from_rgb_packed(pixels, h, w * channels, w, 255); + } else if (channels == 4) { + resource->v = ncvisual_from_rgba(pixels, h, w * channels, w); + } else { + al_log_warn("cmc", "Image has unsupported channel count."); + } + stbi_image_free(pixels); + ncvisual_geom(resources->nc, resource->v, &resource->vopts, &resource->geom); + struct cmc_view *view; + al_array_foreach(resource->refs, j, view) { + view->pending_requests--; + } + al_array_free(resource->refs); + break; + } + } + resources->callback(resources->userdata); +} + +bool cmc_resources_connect(struct cmc_resources *resources, struct notcurses *nc, + struct aki_event_loop *loop, str *addr, s32 port) +{ + resources->nc = nc; + resources->pending_requests = 0; + camu_resource_client_init(&resources->client, loop, resource_client_callback, resources); + return camu_resource_client_connect(&resources->client, addr, port); +} + +void cmc_resources_set_callback(struct cmc_resources *resources, void (*callback)(void *), void *userdata) +{ + resources->callback = callback; + resources->userdata = userdata; +} + +static struct cmc_view_resource *create_resource(struct cmc_resources *resources, str *unique_id, u32 index) +{ + struct cmc_view_resource *resource = al_alloc_object(struct cmc_view_resource); + al_array_push(resources->cache, resource); + resource->vopts.blitter = NCBLIT_PIXEL; + resource->vopts.scaling = NCSCALE_SCALE; + resource->id = al_rand_u16(); + al_str_clone(&resource->unique_id, unique_id); + resource->index = index; + al_array_init(resource->refs); + camu_resource_request(&resources->client, unique_id, index, resource->id); + return resource; +} + +struct cmc_view_resource *cmc_get_resource_from_cache(struct cmc_resources *resources, struct cmc_view *view, + str *unique_id, u32 index) +{ + struct cmc_view_resource *resource; + al_array_foreach(resources->cache, i, resource) { + if (al_str_eq(&resource->unique_id, unique_id) && resource->index == index) { + return resource; + } + } + resource = create_resource(resources, unique_id, index); + view->pending_requests++; + al_array_push(resource->refs, view); + return resource; +} diff --git a/src/fruits/cmc/tui/resources.h b/src/fruits/cmc/tui/resources.h new file mode 100644 index 0000000..f12d861 --- /dev/null +++ b/src/fruits/cmc/tui/resources.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include "../../libclient/resource_client.h" + +struct cmc_view; +struct cmc_view_resource { + u16 id; + str unique_id; + u32 index; + array(struct cmc_view *) refs; + struct ncvisual *v; + struct ncvgeom geom; + struct ncvisual_options vopts; +}; + +struct cmc_resources { + struct camu_resource_client client; + u32 pending_requests; + struct notcurses *nc; + array(struct cmc_view_resource *) cache; + void (*callback)(void *); + void *userdata; +}; + +bool cmc_resources_connect(struct cmc_resources *resources, struct notcurses *nc, + struct aki_event_loop *loop, str *addr, s32 port); +void cmc_resources_set_callback(struct cmc_resources *resources, void (*callback)(void *), void *userdata); +struct cmc_view_resource *cmc_get_resource_from_cache(struct cmc_resources *resources, struct cmc_view *view, + str *unique_id, u32 index); diff --git a/src/fruits/cmc/tui/ui.c b/src/fruits/cmc/tui/ui.c new file mode 100644 index 0000000..dac84a7 --- /dev/null +++ b/src/fruits/cmc/tui/ui.c @@ -0,0 +1,133 @@ +#include "../../tree/common.h" + +#include "../cmc.h" + +#include "ui.h" + +/* +struct nccell ul, ur; nccell_init(&ul); nccell_init(&ur); +struct nccell ll, lr; nccell_init(&ll); nccell_init(&lr); +struct nccell hl, vl; nccell_init(&hl); nccell_init(&vl); +//nccell_set_channels(&hl, color); +//nccell_set_channels(&vl, color); + +ncplane_box(tui->overlay.p, &ul, &ur, &ll, &lr, &hl, &vl, tui->overlay.rows - 1, tui->overlay.cols - 1, 0); + +nccell_release(tui->overlay.p, &ul); nccell_release(tui->overlay.p, &ur); +nccell_release(tui->overlay.p, &ll); nccell_release(tui->overlay.p, &lr); +nccell_release(tui->overlay.p, &hl); nccell_release(tui->overlay.p, &vl); +*/ + +//ncplane_double_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0); +//ncplane_rounded_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0); + +void cmc_ui_toggle_overlay(struct cmc_ui *ui) +{ + if (ui->overlay.active) { + ncplane_reparent_family(ui->overlay.p, ui->overlay.p); + } else { + ncplane_reparent_family(ui->overlay.p, ui->p); + } + ui->overlay.active = !ui->overlay.active; +} + +static void input_poll_callback(void *userdata, s32 revents) +{ + struct cmc_ui *ui = (struct cmc_ui *)userdata; + (void)revents; + struct ncinput input; + u32 ret; + bool do_refresh = false; + do { + ret = notcurses_get_nblock(ui->nc, &input); + if (ret == (u32)-1 || ret == 0) break; + if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) { + if (input.id == 'q') { + aki_event_loop_break(&ui->c->loop); + break; + } else if (input.id == NCKEY_TAB) { + cmc_ui_toggle_overlay(ui); + do_refresh = true; + ui->search.search->pager.redraw = true; + continue; + } + switch (ui->pane) { + case CMC_PANE_LIBRARY: + break; + case CMC_PANE_LISTS: + break; + case CMC_PANE_BROWSE: + break; + case CMC_PANE_SEARCH: + if (ui->overlay.active) { + do_refresh |= cmc_pane_search_handle_input_overlay(&ui->search, &input); + } else { + do_refresh |= cmc_pane_search_handle_input(&ui->search, &input); + } + break; + } + } + } while (1); + if (!do_refresh) return; + switch (ui->pane) { + case CMC_PANE_LIBRARY: + break; + case CMC_PANE_LISTS: + break; + case CMC_PANE_BROWSE: + break; + case CMC_PANE_SEARCH: + cmc_pane_search_refresh(&ui->search); + break; + } +} + +static s32 resize_cb(struct ncplane *p) +{ + struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p); + notcurses_stddim_yx(ui->nc, &ui->rows, &ui->cols); + ui->cols -= 4; + ui->rows -= 1; + struct ncplane_options nopts = { 0 }; + nopts.cols = ui->cols; + nopts.rows = ui->rows; + nopts.x = 3; + nopts.y = 1; + nopts.flags = 0; + ui->p = ncplane_create(p, &nopts); + ui->pane = CMC_PANE_SEARCH; + ui->overlay.cols = nopts.cols = 35; + ui->overlay.rows = nopts.rows = 20; + nopts.x = (ui->cols / 2) - (ui->overlay.cols / 2); + nopts.y = (ui->rows / 2) - (ui->overlay.rows / 2); + ui->overlay.p = ncpile_create(ui->nc, &nopts); + return 0; +} + +bool cmc_ui_open(struct cmc_ui *ui, struct cmc *c) +{ + if (!(ui->nc = notcurses_init(NULL, stdin))) return false; + + ui->c = c; + + cmc_pane_search_init(&ui->search, ui); + + if (!cmc_resources_connect(&ui->resources, ui->nc, &ui->c->loop, TREE_SERVER_IP, TREE_RESOURCE_PORT)) { + return false; + } + cmc_resources_set_callback(&ui->resources, cmc_ui_resources_callback, &ui->search); + + aki_poll_init(&ui->input, input_poll_callback, ui); + aki_poll_set(&ui->input, notcurses_inputready_fd(ui->nc), AKI_POLL_READ); + aki_poll_start(&ui->input, &c->loop); + + ui->overlay.active = false; + ui->overlay.selected = 0; + + struct ncplane *stdplane = notcurses_stdplane(ui->nc); + ncplane_set_resizecb(stdplane, resize_cb); + ncplane_set_userptr(stdplane, ui); + resize_cb(stdplane); + + return true; +} diff --git a/src/fruits/cmc/tui/ui.h b/src/fruits/cmc/tui/ui.h new file mode 100644 index 0000000..2f81392 --- /dev/null +++ b/src/fruits/cmc/tui/ui.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "resources.h" +#include "pane_search.h" + +enum { + CMC_PANE_LIBRARY = 0, + CMC_PANE_LISTS, + CMC_PANE_BROWSE, + CMC_PANE_SEARCH +}; + +struct cmc; +struct cmc_ui { + struct notcurses *nc; + u32 cols; + u32 rows; + struct ncplane *p; + u8 pane; + struct { + struct ncplane *p; + u32 cols; + u32 rows; + bool active; + s32 selected; + } overlay; + struct aki_poll input; + struct cmc_resources resources; + struct cmc_pane_search search; + struct cmc *c; +}; + +bool cmc_ui_open(struct cmc_ui *ui, struct cmc *c); +void cmc_ui_toggle_overlay(struct cmc_ui *ui); +void cmc_ui_maybe_draw_overlay(struct cmc_ui *ui); diff --git a/src/fruits/cmc/tui/view.h b/src/fruits/cmc/tui/view.h new file mode 100644 index 0000000..3c521c3 --- /dev/null +++ b/src/fruits/cmc/tui/view.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +enum { + CMC_VIEW_NONE = 0, + CMC_VIEW_SINGLE, + CMC_VIEW_PARTITION +}; + +struct cmc_view { + struct sho_post *post; + struct ncplane *container; + u32 pending_requests; + u32 rows; + u32 y; +}; + +struct cmc_view_page { + u32 num; + bool processed; + array(struct cmc_view *) views; +}; diff --git a/src/fruits/cmc/tui/view_twitter.c b/src/fruits/cmc/tui/view_twitter.c new file mode 100644 index 0000000..b1070a4 --- /dev/null +++ b/src/fruits/cmc/tui/view_twitter.c @@ -0,0 +1,370 @@ +#include "../cmc.h" + +#include "view_twitter.h" + +#define TEXT_OFFSET 10 + +#define PROFILE_SIZE 5 +#define PREVIEW_OFFSET_WIDTH 26 + +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; +} + +u32 cmc_layout_tweet(struct cmc_ui *ui, struct cmc_view_twitter *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); + view->text.y = repost ? 1 : 0; + view->text.height += view->text.y; + rows += view->text.height; + + if (view->preview.mode == CMC_VIEW_NONE) { + return rows; + } + + view->preview.width = view->text.width - 1; + view->preview.x = TEXT_OFFSET; + u32 partition_width = view->preview.width / 2; + + struct ncplane_options opts = { 0 }; + opts.cols = view->preview.width; + opts.rows = ncplane_dim_y(notcurses_stdplane(ui->nc)) / 3; + opts.x = 0; + opts.y = 0; + struct ncplane *inter = ncplane_create(notcurses_stdplane(ui->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(ui->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: { + ncplane_resize_simple(inter, opts.rows, partition_width); + + resource = view->preview.resources[0]; + resource->vopts.n = inter; + ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom); + left += resource->geom.rcelly + 1; + + resource = view->preview.resources[1]; + resource->vopts.n = inter; + ncvisual_geom(ui->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(ui->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(ui->nc, resource->v, &resource->vopts, &resource->geom); + right += resource->geom.rcelly + 1; + } + + break; + } + } + + ncplane_destroy(inter); + + view->preview.height = AL_MAX(right, left); + + return rows + view->preview.height; +} + +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 >= 1000000000) { + al_sprintf(out, "%.1fB", number / 1000000000.f); + } else 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); + } +} + +static void draw_tweet_resources(struct cmc_ui *ui, struct cmc_view_twitter *view) +{ + 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(ui->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(ui->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(ui->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(ui->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[1]->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(ui->nc, resource->v, &resource->vopts); + } + + break; + } + } + + 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(ui->nc, resource->v, &resource->vopts); + } +} + +void cmc_draw_tweet_func(void *userdata, struct cmc_view *view, u32 y) +{ + struct cmc_ui *ui = (struct cmc_ui *)userdata; + struct cmc_search *search = ui->search.search; + ncplane_reparent_family(view->container, ui->p); + ncplane_move_yx(view->container, y, 0); + draw_tweet_resources(ui, (struct cmc_view_twitter *)view); + view->y = y; + al_array_push(search->pager.prev, view); +} + +static char format_buf[32]; + +void cmc_draw_tweet(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); + + 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 = 0; + + if (post->comments.set) { + 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"❤"; + 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 + 2, format_buf); + } +} + +static void get_post_and_maybe_repost(struct cmc_ui *ui, str *unique_id, struct sho_post **post, struct sho_post **repost) +{ + struct sho_post_cache *cache = &ui->c->client.cache; + *post = sho_post_cache_get(cache, unique_id); + *repost = NULL; + if ((*post)->type == SHOKI_POST_REPOST) { + *repost = *post; + *post = sho_post_cache_get(cache, &(*post)->post.unique_id); + } +} + +static void strip_link_from_tweet(struct sho_post *post) +{ + wstr *check = al_wstr_c(L"https://t.co/"); + s32 index = al_wstr_rfind(&post->text, L':'); + if (index < 5) return; + index -= 5; // [h]ttps:// (if matched). + if (al_wstr_cmp(&post->text, check, index, check->len) == 0) { + al_wstr_at(&post->text, index) = L'\0'; + post->text.len = index; + } +} + +struct cmc_view_twitter *cmc_create_view_twitter(struct cmc_ui *ui, str *unique_id) +{ + struct cmc_view_twitter *view = al_alloc_object(struct cmc_view_twitter); + get_post_and_maybe_repost(ui, unique_id, &view->v.post, &view->repost); + + if (view->v.post->media.size > 0) { + strip_link_from_tweet(view->v.post); + } + + view->v.rows = 0; + view->v.y = 0; + view->v.pending_requests = 0; + + 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(ui->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 = cmc_get_resource_from_cache(&ui->resources, &view->v, &post->author.unique_id, 0); + struct sho_post_media *media; + al_array_foreach_ptr(post->media, i, media) { + (void)media; + view->preview.resources[i] = cmc_get_resource_from_cache(&ui->resources, &view->v, &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; + } + + return view; +} diff --git a/src/fruits/cmc/tui/view_twitter.h b/src/fruits/cmc/tui/view_twitter.h new file mode 100644 index 0000000..9792826 --- /dev/null +++ b/src/fruits/cmc/tui/view_twitter.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ui.h" +#include "view.h" + +struct cmc_view_twitter { + 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_view_twitter *cmc_create_view_twitter(struct cmc_ui *ui, str *unique_id); +void cmc_draw_tweet(struct cmc_view_twitter *view, struct ncplane *container, u32 rows, u32 width); +u32 cmc_layout_tweet(struct cmc_ui *ui, struct cmc_view_twitter *view, u32 width); +void cmc_draw_tweet_func(void *userdata, struct cmc_view *view, u32 y); diff --git a/src/libclient/client.c b/src/libclient/client.c index 1194994..bf22fee 100644 --- a/src/libclient/client.c +++ b/src/libclient/client.c @@ -68,7 +68,7 @@ static bool update_state_command_callback(void *userdata, struct aki_rpc_connect al_str_clone(&search->module, &s); aki_packet_read_str(packet, &s); al_str_clone(&search->query, &s); - search->last_page = aki_packet_read_u32(packet); + search->page = aki_packet_read_u32(packet); al_array_push(client->state.searches, search); } diff --git a/src/libclient/client.h b/src/libclient/client.h index edb7881..68b0826 100644 --- a/src/libclient/client.h +++ b/src/libclient/client.h @@ -31,7 +31,7 @@ struct camu_search { s32 id; str module; str query; - u32 last_page; + u32 page; array(struct camu_result_list *) lists; }; diff --git a/src/libclient/resource_client.c b/src/libclient/resource_client.c index 0318305..a016fa6 100644 --- a/src/libclient/resource_client.c +++ b/src/libclient/resource_client.c @@ -60,8 +60,8 @@ void camu_resource_client_init(struct camu_resource_client *client, client->userdata = userdata; camu_queue_init(client->queue); aki_signal_init(&client->signal, signal_callback, client); - aki_packet_stream_init(&client->client, AKI_SOCKET_TCP, connection_callback, - connection_closed_callback, packet_callback, packet_sent_callback, client); + aki_packet_stream_init(&client->client, AKI_SOCKET_TCP, connection_callback, connection_closed_callback, + packet_callback, packet_sent_callback, client); } bool camu_resource_client_connect(struct camu_resource_client *client, str *addr, s32 port) diff --git a/src/libsink/sink.c b/src/libsink/sink.c index 55485b0..3468a48 100644 --- a/src/libsink/sink.c +++ b/src/libsink/sink.c @@ -321,18 +321,24 @@ static void audio_buffer_callback(void *userdata, u8 op) aki_mutex_unlock(&entry->sink->mutex); break; case CAMU_BUFFER_CORK: + bmu_vcr_stream_cork(entry->audio.stream); + /* queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = CORK, .value.i = CAMU_SINK_AUDIO, .opaque = entry }); + */ break; case CAMU_BUFFER_UNCORK: + bmu_vcr_stream_uncork(entry->audio.stream); + /* queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = UNCORK, .value.i = CAMU_SINK_AUDIO, .opaque = entry }); + */ break; case CAMU_BUFFER_PAUSED: queue_cmd(entry->sink, (struct camu_sink_cmd){ @@ -378,18 +384,24 @@ static void video_buffer_callback(void *userdata, u8 op) aki_mutex_unlock(&entry->sink->mutex); break; case CAMU_BUFFER_CORK: + bmu_vcr_stream_cork(entry->video.stream); + /* queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = CORK, .value.i = CAMU_SINK_VIDEO, .opaque = entry }); + */ break; case CAMU_BUFFER_UNCORK: + bmu_vcr_stream_uncork(entry->video.stream); + /* queue_cmd(entry->sink, (struct camu_sink_cmd){ .op = UNCORK, .value.i = CAMU_SINK_VIDEO, .opaque = entry }); + */ break; case CAMU_BUFFER_EOF: queue_cmd(entry->sink, (struct camu_sink_cmd){ @@ -636,7 +648,6 @@ static bool buffer_command_callback(void *userdata, struct aki_rpc_connection *c return false; } - static bool set_command_callback(void *userdata, struct aki_rpc_connection *conn, struct aki_packet *packet, struct aki_packet *rpacket) { @@ -694,6 +705,8 @@ static bool queue_command_callback(void *userdata, struct aki_rpc_connection *co sink->queued = ensure_entry_buffered_internal(sink, &addr, port, node_id); aki_mutex_unlock(&sink->mutex); + aki_packet_free(packet); + return false; } @@ -802,8 +815,8 @@ void camu_sink_close(struct camu_sink *sink) struct camu_sink_entry *entry; al_array_foreach(sink->entries, i, entry) { bmu_client_close(&entry->client); - al_array_remove_at_iter(sink->entries, i); } + sink->entries.size = 0; aki_mutex_unlock(&sink->mutex); } diff --git a/src/screen/screen.c b/src/screen/screen.c index 938e01d..604a357 100644 --- a/src/screen/screen.c +++ b/src/screen/screen.c @@ -308,12 +308,12 @@ static void run_queue_internal(struct camu_screen *scr) struct camu_video_buffer *buf; al_array_foreach(scr->add_queue, i, buf) { add_buffer_internal(scr, buf); - al_array_remove_at_iter(scr->add_queue, i); } + scr->add_queue.size = 0; al_array_foreach(scr->rem_queue, i, buf) { remove_buffer_internal(scr, buf); - al_array_remove_at_iter(scr->rem_queue, i); } + scr->rem_queue.size = 0; } #endif diff --git a/src/tree/common.h b/src/tree/common.h index de6258b..f740a4e 100644 --- a/src/tree/common.h +++ b/src/tree/common.h @@ -9,6 +9,8 @@ #define TREE_SERVER_IP al_str_c("127.0.0.1") //#define TREE_SERVER_IP al_str_c("108.52.160.112") +#define CAMU_DB_PATH al_str_c("/mnt/store/files/camu_db") + enum { TREE_NODE = 0, TREE_CLIENT, diff --git a/src/tree/tree.c b/src/tree/tree.c index 1be4891..c57d56c 100644 --- a/src/tree/tree.c +++ b/src/tree/tree.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "tree.h" @@ -97,7 +98,7 @@ static void send_current_state(struct tree_server *server, struct tree_user *use aki_packet_write_s32(packet, search->id); aki_packet_write_str(packet, &search->module); aki_packet_write_str(packet, &search->query); - aki_packet_write_u32(packet, search->last_page); + aki_packet_write_u32(packet, search->page); } aki_rpc_connection_command(conn, packet, NULL, NULL); } @@ -326,9 +327,14 @@ static bool open_user(struct tree_server *tree, struct aki_dir_entry *dir) str s; aki_file_read_as_str(&file, &s); json_error_t error; - json_t *json = json_loadb(s.data, s.len, 0, &error); + json_t *root = json_loadb(s.data, s.len, 0, &error); + if (!root) { + al_log_error("tree", "Failed to parse json: %.*s:%d:%d (%s).", AL_STR_PRINTF(&dir->path), + error.line, error.column, error.text); + return false; + } struct tree_user *user = al_alloc_object(struct tree_user); - al_str_from(&user->username, json_string_value(json_object_get(json, "username"))); + al_str_from(&user->username, json_string_value(json_object_get(root, "username"))); sho_client_init(&user->search, &tree->resources.cache); al_array_init(user->lists); struct tree_list *default_list = al_alloc_object(struct tree_list); @@ -409,9 +415,7 @@ s32 main(void) al_array_init(tree.sinks); al_array_init(tree.users); - if (!open_db(&tree, al_str_c(CAMU_DB_PATH))) { - return EXIT_FAILURE; - } + if (!open_db(&tree, CAMU_DB_PATH)) return EXIT_FAILURE; sho_post_cache_init(&tree.resources.cache); diff --git a/src/tree/tree.h b/src/tree/tree.h index 9f8a19a..b8edaad 100644 --- a/src/tree/tree.h +++ b/src/tree/tree.h @@ -15,8 +15,6 @@ #include "resource_manager.h" #include "list.h" -#define CAMU_DB_PATH "/home/andrew/c/camu/data/camu_db_test" - struct tree_node { struct aki_rpc_connection *conn; }; diff --git a/src/util/meson.build b/src/util/meson.build index 3cdaf28..eff0fe6 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -1,3 +1,6 @@ util_src = ['color_palette.c', 'blocking_ring_buffer.c'] -util_deps = [] +blake3_opts = cmake.subproject_options() +blake3_opts.add_cmake_defines({ 'CMAKE_BUILD_TYPE': is_debug ? 'Debug' : 'Release' }) +blake3 = cmake.subproject('BLAKE3').dependency('blake3') +util_deps = [blake3] util = declare_dependency(sources: util_src, dependencies: util_deps) diff --git a/subprojects/BLAKE3.wrap b/subprojects/BLAKE3.wrap new file mode 100644 index 0000000..0a26602 --- /dev/null +++ b/subprojects/BLAKE3.wrap @@ -0,0 +1,6 @@ +[wrap-git] +url = https://github.com/BLAKE3-team/BLAKE3.git +revision = 8fc36186b84385d36d8339606e4d1ea6ff471965 +depth = 1 +patch_directory = BLAKE3 +method = cmake diff --git a/subprojects/packagefiles/BLAKE3/CMakeLists.txt b/subprojects/packagefiles/BLAKE3/CMakeLists.txt new file mode 100644 index 0000000..20bc4cc --- /dev/null +++ b/subprojects/packagefiles/BLAKE3/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(c) -- cgit v1.2.3-101-g0448