diff options
-rw-r--r-- | main.c | 216 |
1 files changed, 175 insertions, 41 deletions
@@ -70,6 +70,7 @@ X(CPAIR_TIME , COLOR_BLUE , COLOR_BLACK)\ X(CPAIR_PFP , COLOR_BLUE , COLOR_BLACK)\ X(CPAIR_PFP_SELF , COLOR_YELLOW , COLOR_BLACK)\ + X(CPAIR_BANNER , COLOR_WHITE , COLOR_BLACK)\ X(CPAIR_BORDER , COLOR_BLUE , COLOR_BLACK)\ X(CPAIR_TAGLINE_OK , COLOR_GREEN , COLOR_BLACK)\ X(CPAIR_TAGLINE_WARN , COLOR_YELLOW , COLOR_BLACK)\ @@ -223,23 +224,24 @@ int timestamp_invalid(struct timespec *ts) { typedef struct { int lines, cols; Str *line; -} Pfp; +} Pic; typedef struct { Str name; Str pronouns; - Pfp pfp; + Pic pfp, banner; + unsigned post_count; } User; -void pfp_load(Pfp *pfp, Str src, Arena *a) { +void pic_load(Pic *pfp, Str src, int min_lines, int min_cols, Arena *a) { int lines = 0, cols = 0; for (Str s = src, l = {0}; next_line(&s, &l);) { int w = fast_utf8_width(l); if (w > cols) cols = w; lines++; } - pfp->lines = lines > GFX_PFP_MIN_LINES ? lines : GFX_PFP_MIN_LINES; - pfp->cols = cols > GFX_PFP_MIN_COLS ? cols : GFX_PFP_MIN_COLS; + pfp->lines = MAX(lines, min_lines); + pfp->cols = MAX(cols, min_cols); pfp->line = new_arr(a, Str, pfp->lines); lines = 0; for (Str s = src, l = {0}; next_line(&s, &l);) { @@ -250,26 +252,32 @@ void pfp_load(Pfp *pfp, Str src, Arena *a) { } } +void pic_load_path(Pic *dest, const char *path, Str or_src, int min_lines, int min_cols, Arena *a) { + FILE *f = fopen(path, "r/o"); + if (f) { + Str pic_src = {0}; + read_all(f, &pic_src, a); + fclose(f); + pic_load(dest, pic_src, min_lines, min_cols, a); + } else { + pic_load(dest, or_src, min_lines, min_cols, a); + } +} + User *user_load(Str name, Str homedir, Str binkdir, Arena *a) { (void)binkdir; User *u = new(a, User); u->name = str_dup(name, a); - FILE *f = fopen(cstr_fmt(a, "%S/.binkpfp", homedir), "r/o"); - Str pfp_src = {0}; - if (f) { - read_all(f, &pfp_src, a); - fclose(f); - } else { - pfp_src = S( + pic_load_path(&u->pfp, cstr_fmt(a, "%S/.binkpfp", homedir), S( "bi __ \n" "nk_(''<\n" " (____)\n" - ); - } - pfp_load(&u->pfp, pfp_src, a); + ), GFX_PFP_MIN_LINES, GFX_PFP_MIN_COLS, a); - f = fopen(cstr_fmt(a, "%S/.pronouns", homedir), "r/o"); + pic_load_path(&u->banner, cstr_fmt(a, "%S/.binkbanner", homedir), S(""), 0, 0, a); + + FILE *f = fopen(cstr_fmt(a, "%S/.pronouns", homedir), "r/o"); if (f) { Str buf = { 0 }; read_all(f, &buf, a); @@ -357,6 +365,7 @@ void posts_gather_from(PostList *posts, Str username, Str homedir, Arena *a) { p.user = user; p.path = cstr_fmt(a, "%S/%s", binkdir, de->d_name); DA_PUSH(posts, p); + user->post_count++; } closedir(d); } @@ -508,6 +517,54 @@ int gfx_post_height(GfxPost *post) { return base; } +void gfx_draw_box(int y, int x, int height, int width) { + color_set(CPAIR_BORDER, 0); + int right = x + width - 1, bottom = y + height - 1; + gfx_hline(y, x, right - x, ACS_HLINE); + gfx_vline(y, x, height - 1, ACS_VLINE); + gfx_vline(y, right, height - 1, ACS_VLINE); + gfx_hline(bottom, x, right - x, ACS_HLINE); + mvaddch(bottom, right, ACS_LRCORNER); + mvaddch(bottom, x, ACS_LLCORNER); + mvaddch(y, x, ACS_ULCORNER); + mvaddch(y, right, ACS_URCORNER); +} + +void gfx_cleared_box(int y, int x, int height, int width) { + for (int i = y; i < y + height; i++) { + for (int j = x; j < x + width; j++) { + mvaddch(i, j, ' '); + } + } + gfx_draw_box(y, x, height, width); +} + +typedef enum { + PIC_NONE, + PIC_CTR_Y = 1, + PIC_CTR_X = 2, + PIC_LEFT = 4, + PIC_TOP = 8, + PIC_BORDER = 16, +} PicDrawFlags; + +void gfx_draw_pic(Pic *pfp, int y, int x, int max_lines, int max_cols, PicDrawFlags f) { + int w = 0; + int h = MIN(pfp->lines, max_lines); + for (int i = 0; i < h; i++) { + w = MAX(w, MIN(pfp->line[i].n, max_cols)); + } + if (f & PIC_CTR_X) x -= w >> 1; + if (f & PIC_CTR_Y) y -= h >> 1; + if (f & PIC_LEFT) x -= w; + if (f & PIC_TOP) y -= h; + for (int i = 0; i < h; i++) { + int n = utf8_cp_idx(pfp->line[i], max_cols); + mvaddnstr(y + i, x, pfp->line[i].s, n); + } + if (f & PIC_BORDER) gfx_draw_box(y - 1, x - 1, h + 2, w + 2); +} + void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) { int height = gfx_post_text_height(post); int total_height = gfx_post_height(post); @@ -518,26 +575,16 @@ void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) { } int left = x + post_left_margin(post->src), top = y + opt.margin.top; - int right = left + width - 1, bottom = top + height - 1; + int right = left + width - 1; int self = is_post_mine(post->src); User *u = post->src->user; if (total_height > height) { int ydiff = (total_height - height) >> 1; top += ydiff; - bottom += ydiff; } - color_set(CPAIR_BORDER, 0); - gfx_hline(top, left, right - left, ACS_HLINE); - gfx_vline(top, left, height - 1, ACS_VLINE); - gfx_vline(top, right, height - 1, ACS_VLINE); - gfx_hline(bottom, left, right - left, ACS_HLINE); - - mvaddch(bottom, right, ACS_LRCORNER); - mvaddch(bottom, left, ACS_LLCORNER); - mvaddch(top, left, ACS_ULCORNER); - mvaddch(top, right, ACS_URCORNER); + gfx_draw_box(top, left, height, width); color_set(CPAIR_TIME, 0); Str s = str_trim(str_from_cstr(ctime(&(time_t){post->src->timestamp.tv_sec}))); @@ -545,19 +592,16 @@ void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) { if (opt.see.pfp) { color_set(self ? CPAIR_PFP_SELF : CPAIR_PFP, 0); - Pfp *pfp = &u->pfp; - int h = MIN(pfp->lines, MAX(post->lines, GFX_PFP_MAX_LINES)); - int pfptop = y + GFX_TEXT_MARGIN_Y; - if (height > h) pfptop += (height - h) >> 1; - int pfpleft = left - pfp->cols - GFX_PFP_MARGIN; - for (int i = 0; i < h; i++) { - if (pfp->line[i].n > GFX_PFP_MAX_COLS) { - int w = utf8_cp_idx(pfp->line[i], GFX_PFP_MAX_COLS); - mvaddnstr(pfptop + i, pfpleft, pfp->line[i].s, w); - } else { - mvaddnstr(pfptop + i, pfpleft, pfp->line[i].s, pfp->line[i].n); - } - } + Pic *pfp = &u->pfp; + int pfpy = y; + int h = MIN(pfp->lines, GFX_PFP_MAX_LINES); + if (height > h) pfpy += (height - h) >> 1; + gfx_draw_pic(pfp, + pfpy, + left - GFX_PFP_MARGIN, + GFX_PFP_MAX_LINES, + GFX_PFP_MAX_COLS, + PIC_LEFT); } color_set(CPAIR_USER, 0); @@ -586,6 +630,92 @@ void gfx_draw(Gfx *gfx, int cur, Arena *scratch) { } } +/* maybe should write an ui layer to not have to deal with all the manual layout stuff */ + +/* + * ui_begin(&gfx, .align = UI_CTR_X | UI_CTR_Y); + * ui_begin_row(&gfx); + * ui_begin_col(&gfx, .align = UI_CTR_X | UI_CTR_Y); + * ui_pic(&gfx, &user->banner); + * ui_end_col(&gfx); + * ui_end_row(&gfx); + * ui_begin_row(&gfx); + * ui_begin_col(&gfx, .align = UI_CTR_Y); + * ui_pic(&gfx, &user->pfp); + * ui_end_col(&gfx); + * ui_begin_col(&gfx, .border = 1); + * ui_text_row(&gfx, user->name); + * ui_text_row(&gfx, user->pronouns); + * ui_text_row(&gfx, str_fmt(scratch, "%u binks", user->post_count)); + * ui_end_col(&gfx); + * ui_end_row(&gfx); + * ui_end(&gfx); + * + */ + +/* + * ui_begin(&gfx, .align = UI_CTR_X | UI_CTR_Y); + * UI_ROW(&gfx, { + * ui_begin_col(&gfx, .align = UI_CTR_X | UI_CTR_Y); + * ui_pic(&gfx, &user->banner); + * ui_end_col(&gfx); + * }); + * UI_ROW(&gfx, { + * UI_COL(&gfx, ui_pic(&gfx, &user->pfp)); + * UI_COL(&gfx, { + * ui_text_row(&gfx, user->name); + * ui_text_row(&gfx, user->pronouns); + * ui_text_row(&gfx, str_fmt(scratch, "%u binks", user->post_count)); + * }, .border = 1); + * }); + * ui_end(&gfx); + * + */ + +void gfx_draw_user(User *user, Arena *scratch) { + int cy = getmaxy(stdscr) >> 1, cx = getmaxx(stdscr) >> 1; + + const char *binks = cstr_fmt(scratch, "%u binks", user->post_count); + int box_height = 3 + GFX_TEXT_MARGIN_Y; + int box_width = MAX((int)strlen(binks), MAX(fast_utf8_width(user->name), fast_utf8_width(user->pronouns))); + + int total_height = (user->banner.lines ? user->banner.lines + 1 : 0) + MAX(box_height, user->pfp.lines) + GFX_TEXT_MARGIN_Y * 2; + int total_width = MAX(user->banner.cols, user->pfp.cols + 2 + box_width) + GFX_TEXT_MARGIN_X * 2; + gfx_cleared_box(cy - user->banner.lines - 2 - GFX_TEXT_MARGIN_Y, cx - (total_width >> 1) - 2 - GFX_TEXT_MARGIN_X, + total_height + 4, total_width + 4); + + if (user->banner.lines > 0) { + color_set(CPAIR_BANNER, 0); + gfx_draw_pic(&user->banner, cy, cx, cy, cx, PIC_CTR_X | PIC_TOP); + } else { + //cy -= MAX(user->pfp.lines, box_height) >> 1; + cy--; + } + + int left = cx - (total_width >> 1); + color_set(CPAIR_PFP, 0); + int pfpy = cy + 1; + if (box_height > user->pfp.lines) { + pfpy += (box_height - user->pfp.lines) >> 1; + } + gfx_draw_pic(&user->pfp, pfpy, left, GFX_PFP_MAX_LINES, GFX_PFP_MAX_COLS, 0); + color_set(CPAIR_USER, 0); + left += user->pfp.cols + 2; + + if (user->pfp.lines > box_height) { + cy += (user->pfp.lines - box_height) >> 1; + } + + int y = cy + 1; + mvaddnstr(y++, left + GFX_TEXT_MARGIN_X, user->name.s, user->name.n); + if (user->pronouns.n > 0) { + color_set(CPAIR_PRONOUNS, 0); + mvaddnstr(y++, left + GFX_TEXT_MARGIN_X, user->pronouns.s, user->pronouns.n); + } + color_set(CPAIR_TEXT, 0); + mvaddstr(y++, left + GFX_TEXT_MARGIN_X, binks); +} + /* will skip at least one post, so not suitable for just scrolling one line at * a time like bink.py does */ int gfx_line_skip(Gfx *gfx, int cur, int amt) { @@ -814,6 +944,10 @@ resize: system(cstr_fmt(&temp_arena, "less %s", posts.data[cur].path)); init_curses(); break; + case 'i': + gfx_draw_user(posts.data[cur].user, &temp_arena); + (void)getch(); + break; } } |