Phase 4-6: Quill editor, Lens viewer, Rigel context menu, CI, website
Phase 4 — native apps: - quill/: text editor (monospace, double-buffered, find bar, undo, open/save) - lens/: image viewer using imlib2 (zoom, pan, rotate, directory nav) - wm/src/menu.c: launch quill instead of mousepad - iso/docker/entrypoint.sh: add imlib2 package; install quill + lens binaries - skykit/template/: developer app template demonstrating the SkyKit API Phase 5 — file system and search: - rigel/rigel.c: right-click context menu (open, open in quill, copy path, rename, trash, properties); Ctrl+F search/filter bar with name highlighting; F2 rename-in-place overlay; Delete key moves to trash Phase 6 — distribution: - .github/workflows/build-iso.yml: GitHub Actions CI that compiles all components and builds a bootable ISO on every tag push; creates a GitHub release with sha256 checksums Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
d19aca2333
commit
a9efede702
12 files changed
+2186
-92
No files matched your search
+527
-89
@@ -10,7 +10,7 @@
|
||||
* [ | IMG photo.jpg 2 MB Jul 10 ]
|
||||
* [ Home | ]
|
||||
* [ Docs |────────────────────────────────────── ]
|
||||
* [ Down | 47 items ]
|
||||
* [ Down | 47 items [search bar when active] ]
|
||||
*/
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
@@ -64,6 +64,12 @@
|
||||
#define PATH_W (WIN_W - PATH_X - 8)
|
||||
#define PATH_H 26
|
||||
|
||||
/* context menu */
|
||||
#define CTX_W 200
|
||||
#define CTX_IH 24
|
||||
#define CTX_SH 8
|
||||
#define CTX_PAD 4
|
||||
|
||||
/* ── File types ───────────────────────────────────────────────────── */
|
||||
typedef enum {
|
||||
FT_DIR, FT_IMAGE, FT_AUDIO, FT_VIDEO, FT_ARCHIVE,
|
||||
@@ -127,6 +133,21 @@ static const struct { const char *name; const char *path; } BKMARKS[] = {
|
||||
};
|
||||
#define NBKMARKS ((int)(sizeof(BKMARKS)/sizeof(BKMARKS[0])))
|
||||
|
||||
/* ── Context menu items ───────────────────────────────────────────── */
|
||||
typedef struct { const char *lbl; int sep; } CtxItem;
|
||||
static const CtxItem CTX[] = {
|
||||
{ "Open", 0 },
|
||||
{ "Open in Quill", 0 },
|
||||
{ "Open in Terminal", 0 },
|
||||
{ NULL, 1 },
|
||||
{ "Copy Path", 0 },
|
||||
{ "Rename", 0 },
|
||||
{ "Move to Trash", 0 },
|
||||
{ NULL, 1 },
|
||||
{ "Properties", 0 },
|
||||
};
|
||||
#define CTX_N ((int)(sizeof(CTX)/sizeof(CTX[0])))
|
||||
|
||||
/* ── App struct ───────────────────────────────────────────────────── */
|
||||
#define HIST_MAX 64
|
||||
|
||||
@@ -147,21 +168,41 @@ typedef struct {
|
||||
FileEntry files[FMAX];
|
||||
int nfiles;
|
||||
int scroll;
|
||||
int hover_row;
|
||||
int sel_row;
|
||||
int hover_row; /* files[] index, -1 if none */
|
||||
int sel_row; /* files[] index, -1 if none */
|
||||
int show_hidden;
|
||||
int sort_col; /* 0=name 1=size 2=date */
|
||||
int sort_dir; /* +1=asc -1=desc */
|
||||
int sort_col; /* 0=name 1=size 2=date */
|
||||
int sort_dir; /* +1=asc -1=desc */
|
||||
|
||||
int sidebar_hover;
|
||||
int hover_btn; /* 1=back 2=fwd 3=up */
|
||||
int hover_btn; /* 1=back 2=fwd 3=up */
|
||||
|
||||
char path_buf[4096];
|
||||
int path_editing;
|
||||
int path_cursor;
|
||||
|
||||
Time last_click_time;
|
||||
int last_click_row;
|
||||
int last_click_row; /* files[] index */
|
||||
|
||||
/* ── search / filter ──────────────────────────── */
|
||||
char search_str[256];
|
||||
int search_len;
|
||||
int search_on;
|
||||
int filtered[FMAX]; /* files[] indices */
|
||||
int nfiltered;
|
||||
|
||||
/* ── right-click context menu ─────────────────── */
|
||||
Window ctx_win;
|
||||
int ctx_vis;
|
||||
int ctx_x, ctx_y;
|
||||
int ctx_file_idx; /* files[] index */
|
||||
int ctx_hover; /* item index, -1 if none */
|
||||
|
||||
/* ── rename overlay ───────────────────────────── */
|
||||
char rename_buf[512];
|
||||
int rename_len;
|
||||
int rename_on;
|
||||
int rename_idx; /* files[] index */
|
||||
} Rigel;
|
||||
|
||||
/* ── Path helpers ─────────────────────────────────────────────────── */
|
||||
@@ -198,7 +239,6 @@ static int g_sort_col, g_sort_dir;
|
||||
static int cmp_files(const void *a, const void *b) {
|
||||
const FileEntry *fa = (const FileEntry *)a;
|
||||
const FileEntry *fb = (const FileEntry *)b;
|
||||
/* dirs always first */
|
||||
int da = (fa->type == FT_DIR), db = (fb->type == FT_DIR);
|
||||
if (da != db) return da ? -1 : 1;
|
||||
int r = 0;
|
||||
@@ -215,6 +255,10 @@ static void list_dir(Rigel *r) {
|
||||
r->nfiles = 0;
|
||||
r->scroll = 0;
|
||||
r->sel_row = -1;
|
||||
r->hover_row = -1;
|
||||
r->search_on = 0;
|
||||
r->search_len = 0;
|
||||
r->search_str[0] = '\0';
|
||||
|
||||
DIR *dir = opendir(r->cwd);
|
||||
if (!dir) return;
|
||||
@@ -245,6 +289,37 @@ static void list_dir(Rigel *r) {
|
||||
qsort(r->files, r->nfiles, sizeof(FileEntry), cmp_files);
|
||||
}
|
||||
|
||||
/* ── Search filter ───────────────────────────────────────────────── */
|
||||
static void search_filter(Rigel *r) {
|
||||
r->nfiltered = 0;
|
||||
r->scroll = 0;
|
||||
r->sel_row = -1;
|
||||
if (!r->search_str[0]) return;
|
||||
for (int i = 0; i < r->nfiles; i++) {
|
||||
if (strcasestr(r->files[i].name, r->search_str))
|
||||
r->filtered[r->nfiltered++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_nvis(Rigel *r) { return r->search_on ? r->nfiltered : r->nfiles; }
|
||||
static FileEntry *get_file(Rigel *r, int vis_idx) {
|
||||
if (r->search_on) {
|
||||
if (vis_idx < 0 || vis_idx >= r->nfiltered) return NULL;
|
||||
return &r->files[r->filtered[vis_idx]];
|
||||
}
|
||||
if (vis_idx < 0 || vis_idx >= r->nfiles) return NULL;
|
||||
return &r->files[vis_idx];
|
||||
}
|
||||
/* translate visual row (vis_idx) → files[] index */
|
||||
static int get_fidx(Rigel *r, int vis_idx) {
|
||||
if (r->search_on) {
|
||||
if (vis_idx < 0 || vis_idx >= r->nfiltered) return -1;
|
||||
return r->filtered[vis_idx];
|
||||
}
|
||||
if (vis_idx < 0 || vis_idx >= r->nfiles) return -1;
|
||||
return vis_idx;
|
||||
}
|
||||
|
||||
/* ── Navigation ──────────────────────────────────────────────────── */
|
||||
static void nav_cd(Rigel *r, const char *path) {
|
||||
char real[4096];
|
||||
@@ -253,7 +328,6 @@ static void nav_cd(Rigel *r, const char *path) {
|
||||
} else {
|
||||
snprintf(r->cwd, sizeof(r->cwd), "%s", path);
|
||||
}
|
||||
/* push history */
|
||||
if (r->hist_len > 0 && strcmp(r->history[r->hist_pos], r->cwd) == 0)
|
||||
goto done;
|
||||
r->hist_pos++;
|
||||
@@ -292,9 +366,9 @@ static void nav_up(Rigel *r) {
|
||||
else { *sl = '\0'; nav_cd(r, parent); }
|
||||
}
|
||||
|
||||
static void open_entry(Rigel *r, int idx) {
|
||||
if (idx < 0 || idx >= r->nfiles) return;
|
||||
FileEntry *fe = &r->files[idx];
|
||||
static void open_entry(Rigel *r, int fidx) {
|
||||
if (fidx < 0 || fidx >= r->nfiles) return;
|
||||
FileEntry *fe = &r->files[fidx];
|
||||
if (fe->type == FT_DIR) {
|
||||
char path[4096*2];
|
||||
snprintf(path, sizeof(path), "%s/%s", r->cwd, fe->name);
|
||||
@@ -307,6 +381,179 @@ static void open_entry(Rigel *r, int idx) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Context menu ────────────────────────────────────────────────── */
|
||||
static int ctx_item_h(int i) { return CTX[i].sep ? CTX_SH : CTX_IH; }
|
||||
|
||||
static int ctx_total_h(void) {
|
||||
int h = CTX_PAD * 2;
|
||||
for (int i = 0; i < CTX_N; i++) h += ctx_item_h(i);
|
||||
return h;
|
||||
}
|
||||
|
||||
static int ctx_item_y(int idx) {
|
||||
int y = CTX_PAD;
|
||||
for (int i = 0; i < idx; i++) y += ctx_item_h(i);
|
||||
return y;
|
||||
}
|
||||
|
||||
static int ctx_item_at(int py) {
|
||||
int y = CTX_PAD;
|
||||
for (int i = 0; i < CTX_N; i++) {
|
||||
int h = ctx_item_h(i);
|
||||
if (!CTX[i].sep && py >= y && py < y + h) return i;
|
||||
y += h;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ctx_hide(Rigel *r) {
|
||||
if (!r->ctx_vis) return;
|
||||
XUngrabPointer(r->sk.dpy, CurrentTime);
|
||||
XDestroyWindow(r->sk.dpy, r->ctx_win);
|
||||
r->ctx_vis = 0;
|
||||
r->ctx_win = 0;
|
||||
r->ctx_hover = -1;
|
||||
}
|
||||
|
||||
static void ctx_draw(Rigel *r) {
|
||||
if (!r->ctx_vis) return;
|
||||
int ch = ctx_total_h();
|
||||
XftDraw *xd = XftDrawCreate(r->sk.dpy, r->ctx_win, r->sk.vis, r->sk.cmap);
|
||||
|
||||
XSetForeground(r->sk.dpy, r->gc, sk_color(&r->sk, 0x1A2030UL));
|
||||
XFillRectangle(r->sk.dpy, r->ctx_win, r->gc, 0, 0, CTX_W, ch);
|
||||
|
||||
for (int i = 0; i < CTX_N; i++) {
|
||||
int iy = ctx_item_y(i);
|
||||
if (CTX[i].sep) {
|
||||
XSetForeground(r->sk.dpy, r->gc, sk_color(&r->sk, 0x2A3040UL));
|
||||
XDrawLine(r->sk.dpy, r->ctx_win, r->gc, 8, iy + 4, CTX_W - 8, iy + 4);
|
||||
continue;
|
||||
}
|
||||
if (i == r->ctx_hover) {
|
||||
XSetForeground(r->sk.dpy, r->gc, sk_color(&r->sk, 0x264878UL));
|
||||
XFillRectangle(r->sk.dpy, r->ctx_win, r->gc, 2, iy, CTX_W - 4, CTX_IH);
|
||||
}
|
||||
unsigned long fg = (i == r->ctx_hover) ? 0xF0F4FFUL : 0xC8D4E8UL;
|
||||
XftColor col;
|
||||
XRenderColor rc = {
|
||||
.red = ((fg >> 16) & 0xFF) * 257,
|
||||
.green = ((fg >> 8) & 0xFF) * 257,
|
||||
.blue = ( fg & 0xFF) * 257,
|
||||
.alpha = 0xFFFF
|
||||
};
|
||||
XftColorAllocValue(r->sk.dpy, r->sk.vis, r->sk.cmap, &rc, &col);
|
||||
int ty = iy + (CTX_IH + r->sk.font_ui->ascent - r->sk.font_ui->descent) / 2;
|
||||
XftDrawStringUtf8(xd, &col, r->sk.font_ui,
|
||||
CTX_PAD + 4, ty,
|
||||
(const FcChar8 *)CTX[i].lbl, (int)strlen(CTX[i].lbl));
|
||||
XftColorFree(r->sk.dpy, r->sk.vis, r->sk.cmap, &col);
|
||||
}
|
||||
XftDrawDestroy(xd);
|
||||
XFlush(r->sk.dpy);
|
||||
}
|
||||
|
||||
static void ctx_show(Rigel *r, int x, int y, int fidx) {
|
||||
if (r->ctx_vis) ctx_hide(r);
|
||||
r->ctx_file_idx = fidx;
|
||||
r->ctx_hover = -1;
|
||||
|
||||
int sw = DisplayWidth(r->sk.dpy, r->sk.screen);
|
||||
int sh = DisplayHeight(r->sk.dpy, r->sk.screen);
|
||||
int ch = ctx_total_h();
|
||||
int wx, wy;
|
||||
Window dummy;
|
||||
XTranslateCoordinates(r->sk.dpy, r->win, r->sk.root, x, y, &wx, &wy, &dummy);
|
||||
if (wx + CTX_W > sw) wx = sw - CTX_W - 2;
|
||||
if (wy + ch > sh) wy = sh - ch - 2;
|
||||
|
||||
r->ctx_x = wx; r->ctx_y = wy;
|
||||
|
||||
XSetWindowAttributes swa = {
|
||||
.override_redirect = True,
|
||||
.background_pixel = sk_color(&r->sk, 0x1A2030UL),
|
||||
.event_mask = ExposureMask | ButtonPressMask
|
||||
| ButtonReleaseMask | PointerMotionMask
|
||||
| LeaveWindowMask,
|
||||
.save_under = True,
|
||||
};
|
||||
r->ctx_win = XCreateWindow(r->sk.dpy, r->sk.root,
|
||||
wx, wy, CTX_W, ch, 1,
|
||||
r->sk.depth, InputOutput, r->sk.vis,
|
||||
CWOverrideRedirect | CWBackPixel | CWEventMask | CWSaveUnder,
|
||||
&swa);
|
||||
XSetWindowBorder(r->sk.dpy, r->ctx_win, sk_color(&r->sk, SK_BORDER));
|
||||
XMapRaised(r->sk.dpy, r->ctx_win);
|
||||
XGrabPointer(r->sk.dpy, r->ctx_win, False,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
r->ctx_vis = 1;
|
||||
ctx_draw(r);
|
||||
}
|
||||
|
||||
static void ctx_exec(Rigel *r, int item) {
|
||||
int fidx = r->ctx_file_idx;
|
||||
if (fidx < 0 || fidx >= r->nfiles) return;
|
||||
FileEntry *fe = &r->files[fidx];
|
||||
char full[4096 + 512];
|
||||
snprintf(full, sizeof(full), "%s/%s", r->cwd, fe->name);
|
||||
char cmd[sizeof(full) + 256];
|
||||
|
||||
switch (item) {
|
||||
case 0: /* Open */
|
||||
open_entry(r, fidx);
|
||||
break;
|
||||
case 1: /* Open in Quill */
|
||||
snprintf(cmd, sizeof(cmd), "quill \"%s\" &", full);
|
||||
system(cmd);
|
||||
break;
|
||||
case 2: /* Open in Terminal */
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"alacritty --working-directory \"%s\" &",
|
||||
fe->type == FT_DIR ? full : r->cwd);
|
||||
system(cmd);
|
||||
break;
|
||||
case 4: /* Copy Path */
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"printf '%%s' \"%s\" | xclip -selection clipboard 2>/dev/null"
|
||||
" || printf '%%s' \"%s\" | xsel --clipboard --input 2>/dev/null",
|
||||
full, full);
|
||||
system(cmd);
|
||||
break;
|
||||
case 5: /* Rename */
|
||||
snprintf(r->rename_buf, sizeof(r->rename_buf), "%s", fe->name);
|
||||
r->rename_len = (int)strlen(r->rename_buf);
|
||||
r->rename_idx = fidx;
|
||||
r->rename_on = 1;
|
||||
break;
|
||||
case 6: /* Move to Trash */
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"gio trash \"%s\" 2>/dev/null"
|
||||
" || mv \"%s\" ~/.local/share/Trash/files/ 2>/dev/null",
|
||||
full, full);
|
||||
system(cmd);
|
||||
list_dir(r);
|
||||
break;
|
||||
case 8: /* Properties */
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(full, &st) == 0) {
|
||||
char sbuf[32]; fmt_size(st.st_size, sbuf, sizeof(sbuf));
|
||||
char dbuf[32]; fmt_date(st.st_mtime, dbuf, sizeof(dbuf));
|
||||
char msg[1024];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"Name: %s\nPath: %s\nSize: %s\nModified: %s\nMode: %o",
|
||||
fe->name, full, sbuf, dbuf, (unsigned)st.st_mode);
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"alacritty -T 'Properties' -e bash -c "
|
||||
"'echo \"%s\"; read -p \"Press Enter to close...\"' &", msg);
|
||||
system(cmd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Drawing helpers ─────────────────────────────────────────────── */
|
||||
static void draw_flush(Rigel *r) {
|
||||
XCopyArea(r->sk.dpy, r->buf, r->win, r->gc,
|
||||
@@ -318,7 +565,6 @@ static void draw_toolbar(Rigel *r) {
|
||||
sk_fill(&r->p, SK_SURFACE, 0, 0, WIN_W, TOOL_H);
|
||||
sk_line(&r->p, SK_BORDER, 0, TOOL_H-1, WIN_W, TOOL_H-1);
|
||||
|
||||
/* back / fwd / up buttons */
|
||||
int can_back = r->hist_pos > 0;
|
||||
int can_fwd = r->hist_pos < r->hist_len - 1;
|
||||
|
||||
@@ -351,7 +597,6 @@ static void draw_toolbar(Rigel *r) {
|
||||
px + 6, sk_baseline(r->sk.font_ui, py, ph),
|
||||
ptext, px+1, py+1, pw-2, ph-2);
|
||||
|
||||
/* cursor when editing */
|
||||
if (r->path_editing) {
|
||||
char tmp[4096];
|
||||
int cpos = r->path_cursor;
|
||||
@@ -366,7 +611,6 @@ static void draw_sidebar(Rigel *r) {
|
||||
sk_fill(&r->p, SK_SIDEBAR, 0, 0, SIDE_W, WIN_H);
|
||||
sk_line(&r->p, SK_BORDER, SIDE_W, 0, SIDE_W, WIN_H);
|
||||
|
||||
/* header */
|
||||
sk_fill(&r->p, 0x06090FUL, 0, TOOL_H, SIDE_W, SIDE_HDR_H);
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM, 12,
|
||||
sk_baseline(r->sk.font_sm, TOOL_H, SIDE_HDR_H), "BOOKMARKS");
|
||||
@@ -375,7 +619,6 @@ static void draw_sidebar(Rigel *r) {
|
||||
int iy = TOOL_H + SIDE_HDR_H;
|
||||
for (int i = 0; i < NBKMARKS; i++, iy += SIDE_ITEM_H) {
|
||||
int hov = (r->sidebar_hover == i);
|
||||
/* check if active */
|
||||
char expanded[4096];
|
||||
expand_path(BKMARKS[i].path, expanded, sizeof(expanded));
|
||||
int active = (strcmp(expanded, r->cwd) == 0);
|
||||
@@ -383,7 +626,6 @@ static void draw_sidebar(Rigel *r) {
|
||||
unsigned long bg = active ? SK_SEL : (hov ? SK_ROW_HOV : SK_SIDEBAR);
|
||||
sk_fill(&r->p, bg, 0, iy, SIDE_W, SIDE_ITEM_H);
|
||||
|
||||
/* accent bar for active */
|
||||
if (active) sk_fill(&r->p, SK_ACCENT, 0, iy, 3, SIDE_ITEM_H);
|
||||
|
||||
unsigned long fg = active ? SK_TEXT_HDR : (hov ? SK_TEXT_HOV : SK_TEXT);
|
||||
@@ -406,7 +648,6 @@ static void draw_col_header(Rigel *r) {
|
||||
unsigned long fc = (r->sort_col == cols[i].col) ? SK_TEXT_HDR : SK_TEXT_DIM;
|
||||
sk_text(&r->p, r->sk.font_sm, fc, cols[i].x + 4,
|
||||
sk_baseline(r->sk.font_sm, hy, COL_H), cols[i].lbl);
|
||||
/* sort arrow */
|
||||
if (r->sort_col == cols[i].col) {
|
||||
const char *arr = (r->sort_dir > 0) ? "▲" : "▼";
|
||||
sk_text(&r->p, r->sk.font_sm, SK_ACCENT,
|
||||
@@ -419,19 +660,21 @@ static void draw_col_header(Rigel *r) {
|
||||
static void draw_filelist(Rigel *r) {
|
||||
sk_fill(&r->p, SK_BG, FILE_X, LIST_Y, FILE_W, LIST_H);
|
||||
|
||||
int visible = r->nfiles - r->scroll;
|
||||
int nvis = get_nvis(r);
|
||||
int visible = nvis - r->scroll;
|
||||
if (visible > LIST_ROWS) visible = LIST_ROWS;
|
||||
|
||||
for (int i = 0; i < visible; i++) {
|
||||
int ri = i + r->scroll;
|
||||
int ry = LIST_Y + i * ROW_H;
|
||||
FileEntry *fe = &r->files[ri];
|
||||
int vis_idx = i + r->scroll;
|
||||
int ry = LIST_Y + i * ROW_H;
|
||||
int fidx = get_fidx(r, vis_idx);
|
||||
FileEntry *fe = (fidx >= 0) ? &r->files[fidx] : NULL;
|
||||
if (!fe) break;
|
||||
|
||||
/* row background */
|
||||
unsigned long bg = SK_BG;
|
||||
if (ri == r->sel_row) bg = SK_SEL;
|
||||
else if (ri == r->hover_row) bg = SK_ROW_HOV;
|
||||
else if (i % 2 == 1) bg = SK_ROW_ALT;
|
||||
if (fidx == r->sel_row) bg = SK_SEL;
|
||||
else if (fidx == r->hover_row) bg = SK_ROW_HOV;
|
||||
else if (i % 2 == 1) bg = SK_ROW_ALT;
|
||||
sk_fill(&r->p, bg, FILE_X, ry, FILE_W - SB_W, ROW_H);
|
||||
|
||||
/* type badge */
|
||||
@@ -444,10 +687,24 @@ static void draw_filelist(Rigel *r) {
|
||||
|
||||
/* name */
|
||||
int ty = sk_baseline(r->sk.font_ui, ry, ROW_H);
|
||||
unsigned long fg = (ri == r->sel_row || ri == r->hover_row) ? SK_TEXT_HDR : SK_TEXT;
|
||||
unsigned long fg = (fidx == r->sel_row || fidx == r->hover_row) ? SK_TEXT_HDR : SK_TEXT;
|
||||
if (r->search_on && r->search_str[0]) {
|
||||
char *match = strcasestr(fe->name, r->search_str);
|
||||
if (match) {
|
||||
/* accent rect behind the matching substring */
|
||||
int pre_len = (int)(match - fe->name);
|
||||
int match_len = r->search_len;
|
||||
int nx = FILE_X + BADGE_W + 2;
|
||||
char tmp[512];
|
||||
snprintf(tmp, sizeof(tmp), "%.*s", pre_len, fe->name);
|
||||
int mx = nx + sk_textw(&r->sk, r->sk.font_ui, tmp);
|
||||
snprintf(tmp, sizeof(tmp), "%.*s", match_len, match);
|
||||
int mw = sk_textw(&r->sk, r->sk.font_ui, tmp);
|
||||
sk_fill(&r->p, SK_ACCENT, mx, ry + 2, mw, ROW_H - 4);
|
||||
}
|
||||
}
|
||||
sk_text_clip(&r->p, r->sk.font_ui, fg,
|
||||
FILE_X + BADGE_W + 2, ty,
|
||||
fe->name,
|
||||
FILE_X + BADGE_W + 2, ty, fe->name,
|
||||
FILE_X + BADGE_W, ry, NAME_W, ROW_H);
|
||||
|
||||
/* size */
|
||||
@@ -462,17 +719,16 @@ static void draw_filelist(Rigel *r) {
|
||||
sk_text_clip(&r->p, r->sk.font_ui, SK_TEXT_DIM, dx, ty,
|
||||
dbuf, dx, ry, DATE_W - 4, ROW_H);
|
||||
|
||||
/* row separator */
|
||||
sk_line(&r->p, SK_SEP, FILE_X, ry + ROW_H - 1, WIN_W - SB_W, ry + ROW_H - 1);
|
||||
}
|
||||
|
||||
/* scrollbar */
|
||||
if (r->nfiles > LIST_ROWS) {
|
||||
if (nvis > LIST_ROWS) {
|
||||
int sbx = WIN_W - SB_W;
|
||||
sk_fill(&r->p, SK_PANEL, sbx, LIST_Y, SB_W, LIST_H);
|
||||
int th = LIST_H * LIST_ROWS / r->nfiles;
|
||||
int th = LIST_H * LIST_ROWS / nvis;
|
||||
if (th < 20) th = 20;
|
||||
int ty2 = LIST_Y + (LIST_H - th) * r->scroll / (r->nfiles - LIST_ROWS);
|
||||
int ty2 = LIST_Y + (LIST_H - th) * r->scroll / (nvis - LIST_ROWS);
|
||||
sk_fill(&r->p, SK_ACCENT, sbx, ty2, SB_W, th);
|
||||
}
|
||||
}
|
||||
@@ -482,35 +738,77 @@ static void draw_statusbar(Rigel *r) {
|
||||
sk_fill(&r->p, SK_SURFACE, 0, sy, WIN_W, STAT_H);
|
||||
sk_line(&r->p, SK_BORDER, 0, sy, WIN_W, sy);
|
||||
|
||||
char msg[128];
|
||||
if (r->sel_row >= 0 && r->sel_row < r->nfiles) {
|
||||
char sb[32];
|
||||
fmt_size(r->files[r->sel_row].size, sb, sizeof(sb));
|
||||
snprintf(msg, sizeof(msg), "%d items — \"%s\" %s",
|
||||
r->nfiles, r->files[r->sel_row].name,
|
||||
r->files[r->sel_row].type == FT_DIR ? "(folder)" : sb);
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg), "%d item%s", r->nfiles, r->nfiles==1?"":"s");
|
||||
}
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM, 10,
|
||||
sk_baseline(r->sk.font_sm, sy, STAT_H), msg);
|
||||
|
||||
/* show-hidden indicator */
|
||||
if (r->show_hidden) {
|
||||
const char *hint = "· showing hidden";
|
||||
if (r->search_on) {
|
||||
/* show search bar */
|
||||
sk_fill(&r->p, 0x0A1020UL, FILE_X, sy, FILE_W, STAT_H);
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM,
|
||||
WIN_W - sk_textw(&r->sk, r->sk.font_sm, hint) - 10,
|
||||
sk_baseline(r->sk.font_sm, sy, STAT_H), hint);
|
||||
FILE_X + 8, sk_baseline(r->sk.font_sm, sy, STAT_H), "Filter: ");
|
||||
int pfx = FILE_X + 8 + sk_textw(&r->sk, r->sk.font_sm, "Filter: ");
|
||||
if (r->search_len > 0)
|
||||
sk_text(&r->p, r->sk.font_ui, SK_TEXT_HDR, pfx,
|
||||
sk_baseline(r->sk.font_ui, sy, STAT_H), r->search_str);
|
||||
/* cursor */
|
||||
int cx = pfx + sk_textw(&r->sk, r->sk.font_ui, r->search_str);
|
||||
sk_fill(&r->p, SK_ACCENT, cx, sy + 4, 2, STAT_H - 8);
|
||||
/* result count */
|
||||
char cnt[32];
|
||||
snprintf(cnt, sizeof(cnt), "%d match%s Esc: clear",
|
||||
r->nfiltered, r->nfiltered == 1 ? "" : "es");
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM,
|
||||
WIN_W - sk_textw(&r->sk, r->sk.font_sm, cnt) - 10,
|
||||
sk_baseline(r->sk.font_sm, sy, STAT_H), cnt);
|
||||
} else {
|
||||
char msg[128];
|
||||
if (r->sel_row >= 0 && r->sel_row < r->nfiles) {
|
||||
char sb[32];
|
||||
fmt_size(r->files[r->sel_row].size, sb, sizeof(sb));
|
||||
snprintf(msg, sizeof(msg), "%d items — \"%s\" %s",
|
||||
r->nfiles, r->files[r->sel_row].name,
|
||||
r->files[r->sel_row].type == FT_DIR ? "(folder)" : sb);
|
||||
} else {
|
||||
snprintf(msg, sizeof(msg), "%d item%s", r->nfiles, r->nfiles==1?"":"s");
|
||||
}
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM, 10,
|
||||
sk_baseline(r->sk.font_sm, sy, STAT_H), msg);
|
||||
|
||||
if (r->show_hidden) {
|
||||
const char *hint = "· showing hidden Ctrl+F: filter";
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM,
|
||||
WIN_W - sk_textw(&r->sk, r->sk.font_sm, hint) - 10,
|
||||
sk_baseline(r->sk.font_sm, sy, STAT_H), hint);
|
||||
} else {
|
||||
const char *hint = "Ctrl+F: filter";
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM,
|
||||
WIN_W - sk_textw(&r->sk, r->sk.font_sm, hint) - 10,
|
||||
sk_baseline(r->sk.font_sm, sy, STAT_H), hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_rename_overlay(Rigel *r) {
|
||||
if (!r->rename_on) return;
|
||||
int ry = WIN_H - STAT_H - 34;
|
||||
sk_fill(&r->p, 0x0A1020UL, FILE_X, ry, FILE_W, 32);
|
||||
sk_rect(&r->p, SK_ACCENT, FILE_X, ry, FILE_W, 32);
|
||||
sk_text(&r->p, r->sk.font_sm, SK_TEXT_DIM,
|
||||
FILE_X + 8, sk_baseline(r->sk.font_sm, ry, 32), "Rename: ");
|
||||
int pfx = FILE_X + 8 + sk_textw(&r->sk, r->sk.font_sm, "Rename: ");
|
||||
if (r->rename_len > 0)
|
||||
sk_text(&r->p, r->sk.font_ui, SK_TEXT_HDR,
|
||||
pfx, sk_baseline(r->sk.font_ui, ry, 32), r->rename_buf);
|
||||
int cx = pfx + sk_textw(&r->sk, r->sk.font_ui, r->rename_buf);
|
||||
sk_fill(&r->p, SK_ACCENT, cx, ry + 6, 2, 20);
|
||||
}
|
||||
|
||||
static void draw(Rigel *r) {
|
||||
draw_toolbar(r);
|
||||
draw_sidebar(r);
|
||||
draw_col_header(r);
|
||||
draw_filelist(r);
|
||||
draw_statusbar(r);
|
||||
draw_rename_overlay(r);
|
||||
draw_flush(r);
|
||||
if (r->ctx_vis) ctx_draw(r);
|
||||
}
|
||||
|
||||
/* ── Hit testing ─────────────────────────────────────────────────── */
|
||||
@@ -523,7 +821,7 @@ static int toolbar_btn_at(int x, int y) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int file_row_at(int x, int y) {
|
||||
static int file_vis_row_at(int x, int y) {
|
||||
if (!sk_in_rect(x, y, FILE_X, LIST_Y, FILE_W, LIST_H)) return -1;
|
||||
return (y - LIST_Y) / ROW_H;
|
||||
}
|
||||
@@ -543,22 +841,27 @@ static int sidebar_item_at(int x, int y) {
|
||||
|
||||
/* ── Event handlers ──────────────────────────────────────────────── */
|
||||
static void on_motion(Rigel *r, int x, int y) {
|
||||
int hrow = file_row_at(x, y);
|
||||
if (hrow >= 0) hrow += r->scroll;
|
||||
int hbtn = toolbar_btn_at(x, y);
|
||||
int vrow = file_vis_row_at(x, y);
|
||||
int hrow = -1;
|
||||
if (vrow >= 0) {
|
||||
int vis_idx = vrow + r->scroll;
|
||||
hrow = get_fidx(r, vis_idx);
|
||||
}
|
||||
int hbtn = toolbar_btn_at(x, y);
|
||||
int hside = sidebar_item_at(x, y);
|
||||
|
||||
int redraw = (hrow != r->hover_row ||
|
||||
hbtn != r->hover_btn ||
|
||||
hside != r->sidebar_hover);
|
||||
r->hover_row = hrow;
|
||||
r->hover_btn = hbtn;
|
||||
r->hover_row = hrow;
|
||||
r->hover_btn = hbtn;
|
||||
r->sidebar_hover = hside;
|
||||
if (redraw) draw(r);
|
||||
}
|
||||
|
||||
static void on_scroll(Rigel *r, int dir) {
|
||||
int max = r->nfiles - LIST_ROWS;
|
||||
int nvis = get_nvis(r);
|
||||
int max = nvis - LIST_ROWS;
|
||||
if (max < 0) max = 0;
|
||||
r->scroll += dir * 3;
|
||||
if (r->scroll < 0) r->scroll = 0;
|
||||
@@ -573,7 +876,12 @@ static void path_commit(Rigel *r) {
|
||||
nav_cd(r, expanded);
|
||||
}
|
||||
|
||||
static void on_click(Rigel *r, int x, int y, Time t) {
|
||||
static void on_click(Rigel *r, int x, int y, Time t, int btn) {
|
||||
/* dismiss context if open */
|
||||
if (r->ctx_vis) { ctx_hide(r); draw(r); return; }
|
||||
/* dismiss rename if open and clicking outside */
|
||||
if (r->rename_on) { r->rename_on = 0; }
|
||||
|
||||
/* path bar */
|
||||
int py = (TOOL_H - PATH_H) / 2;
|
||||
if (sk_in_rect(x, y, PATH_X, py, PATH_W, PATH_H)) {
|
||||
@@ -586,22 +894,23 @@ static void on_click(Rigel *r, int x, int y, Time t) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* deactivate path edit if clicked elsewhere */
|
||||
if (r->path_editing) {
|
||||
r->path_editing = 0;
|
||||
snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd);
|
||||
draw(r);
|
||||
}
|
||||
|
||||
/* toolbar buttons */
|
||||
int btn = toolbar_btn_at(x, y);
|
||||
if (btn == 1) { nav_back(r); draw(r); return; }
|
||||
if (btn == 2) { nav_fwd(r); draw(r); return; }
|
||||
if (btn == 3) { nav_up(r); draw(r); return; }
|
||||
/* toolbar buttons (left click only) */
|
||||
if (btn == Button1) {
|
||||
int b = toolbar_btn_at(x, y);
|
||||
if (b == 1) { nav_back(r); draw(r); return; }
|
||||
if (b == 2) { nav_fwd(r); draw(r); return; }
|
||||
if (b == 3) { nav_up(r); draw(r); return; }
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
int si = sidebar_item_at(x, y);
|
||||
if (si >= 0) {
|
||||
if (si >= 0 && btn == Button1) {
|
||||
char expanded[4096];
|
||||
expand_path(BKMARKS[si].path, expanded, sizeof(expanded));
|
||||
nav_cd(r, expanded);
|
||||
@@ -611,37 +920,84 @@ static void on_click(Rigel *r, int x, int y, Time t) {
|
||||
|
||||
/* column header */
|
||||
int col = col_header_at(x, y);
|
||||
if (col >= 0) {
|
||||
if (col >= 0 && btn == Button1) {
|
||||
if (r->sort_col == col) r->sort_dir = -r->sort_dir;
|
||||
else { r->sort_col = col; r->sort_dir = 1; }
|
||||
g_sort_col = r->sort_col; g_sort_dir = r->sort_dir;
|
||||
qsort(r->files, r->nfiles, sizeof(FileEntry), cmp_files);
|
||||
if (r->search_on) search_filter(r);
|
||||
draw(r);
|
||||
return;
|
||||
}
|
||||
|
||||
/* file list */
|
||||
int ri = file_row_at(x, y);
|
||||
if (ri < 0) return;
|
||||
int row = ri + r->scroll;
|
||||
if (row >= r->nfiles) return;
|
||||
int vrow = file_vis_row_at(x, y);
|
||||
if (vrow < 0) return;
|
||||
int vis_idx = vrow + r->scroll;
|
||||
int fidx = get_fidx(r, vis_idx);
|
||||
if (fidx < 0) return;
|
||||
|
||||
/* double-click detection (300ms) */
|
||||
int dbl = (row == r->last_click_row && (t - r->last_click_time) < 300);
|
||||
if (btn == Button3) {
|
||||
r->sel_row = fidx;
|
||||
draw(r);
|
||||
ctx_show(r, x, y, fidx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* double-click detection */
|
||||
int dbl = (fidx == r->last_click_row && (t - r->last_click_time) < 300);
|
||||
r->last_click_time = t;
|
||||
r->last_click_row = row;
|
||||
r->last_click_row = fidx;
|
||||
|
||||
if (dbl) {
|
||||
open_entry(r, row);
|
||||
open_entry(r, fidx);
|
||||
} else {
|
||||
r->sel_row = (r->sel_row == row) ? -1 : row;
|
||||
r->sel_row = (r->sel_row == fidx) ? -1 : fidx;
|
||||
}
|
||||
draw(r);
|
||||
}
|
||||
|
||||
static void on_key(Rigel *r, XKeyEvent *ke) {
|
||||
KeySym sym = XLookupKeysym(ke, 0);
|
||||
int mod = ke->state;
|
||||
|
||||
/* ── rename overlay ────────────────────────────────────── */
|
||||
if (r->rename_on) {
|
||||
if (sym == XK_Escape) {
|
||||
r->rename_on = 0;
|
||||
} else if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
/* commit rename */
|
||||
if (r->rename_len > 0 && r->rename_idx >= 0
|
||||
&& r->rename_idx < r->nfiles) {
|
||||
char old_path[4096 + 512];
|
||||
char new_path[4096 + 512];
|
||||
snprintf(old_path, sizeof(old_path), "%s/%s",
|
||||
r->cwd, r->files[r->rename_idx].name);
|
||||
snprintf(new_path, sizeof(new_path), "%s/%s",
|
||||
r->cwd, r->rename_buf);
|
||||
rename(old_path, new_path);
|
||||
list_dir(r);
|
||||
}
|
||||
r->rename_on = 0;
|
||||
} else if (sym == XK_BackSpace) {
|
||||
if (r->rename_len > 0)
|
||||
r->rename_buf[--r->rename_len] = '\0';
|
||||
} else {
|
||||
char buf[16];
|
||||
int n = XLookupString(ke, buf, sizeof(buf)-1, NULL, NULL);
|
||||
if (n > 0 && (unsigned char)buf[0] >= 0x20) {
|
||||
buf[n] = '\0';
|
||||
int len = (int)strlen(r->rename_buf);
|
||||
if (len + n < (int)sizeof(r->rename_buf) - 1) {
|
||||
memcpy(r->rename_buf + len, buf, n + 1);
|
||||
r->rename_len += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
draw(r); return;
|
||||
}
|
||||
|
||||
/* ── path bar editing ──────────────────────────────────── */
|
||||
if (r->path_editing) {
|
||||
if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||
path_commit(r);
|
||||
@@ -666,7 +1022,6 @@ static void on_key(Rigel *r, XKeyEvent *ke) {
|
||||
} else if (sym == XK_End) {
|
||||
r->path_cursor = (int)strlen(r->path_buf);
|
||||
} else {
|
||||
/* printable character */
|
||||
char buf[16];
|
||||
int n = XLookupString(ke, buf, sizeof(buf)-1, NULL, NULL);
|
||||
if (n > 0 && (unsigned char)buf[0] >= 32) {
|
||||
@@ -681,12 +1036,42 @@ static void on_key(Rigel *r, XKeyEvent *ke) {
|
||||
}
|
||||
}
|
||||
}
|
||||
draw(r);
|
||||
return;
|
||||
draw(r); return;
|
||||
}
|
||||
|
||||
/* navigation shortcuts */
|
||||
int mod = ke->state;
|
||||
/* ── search mode ───────────────────────────────────────── */
|
||||
if (r->search_on) {
|
||||
if (sym == XK_Escape) {
|
||||
r->search_on = 0;
|
||||
r->search_len = 0;
|
||||
r->search_str[0] = '\0';
|
||||
} else if (sym == XK_BackSpace) {
|
||||
if (r->search_len > 0) {
|
||||
r->search_str[--r->search_len] = '\0';
|
||||
search_filter(r);
|
||||
}
|
||||
if (r->search_len == 0) {
|
||||
r->search_on = 0;
|
||||
}
|
||||
} else if (sym == XK_Return) {
|
||||
/* open first result */
|
||||
if (r->nfiltered > 0) open_entry(r, r->filtered[0]);
|
||||
} else {
|
||||
char buf[8];
|
||||
int n = XLookupString(ke, buf, sizeof(buf)-1, NULL, NULL);
|
||||
if (n > 0 && (unsigned char)buf[0] >= 0x20) {
|
||||
buf[n] = '\0';
|
||||
if (r->search_len + n < (int)sizeof(r->search_str) - 1) {
|
||||
memcpy(r->search_str + r->search_len, buf, n + 1);
|
||||
r->search_len += n;
|
||||
search_filter(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
draw(r); return;
|
||||
}
|
||||
|
||||
/* ── normal navigation ─────────────────────────────────── */
|
||||
if (sym == XK_BackSpace || (sym == XK_Left && (mod & Mod1Mask))) {
|
||||
nav_back(r);
|
||||
} else if (sym == XK_Right && (mod & Mod1Mask)) {
|
||||
@@ -696,7 +1081,6 @@ static void on_key(Rigel *r, XKeyEvent *ke) {
|
||||
} else if (sym == XK_Up) {
|
||||
if (r->sel_row > 0) r->sel_row--;
|
||||
else if (r->sel_row < 0 && r->nfiles > 0) r->sel_row = 0;
|
||||
/* scroll to keep selection visible */
|
||||
if (r->sel_row >= 0 && r->sel_row < r->scroll) r->scroll = r->sel_row;
|
||||
} else if (sym == XK_Down) {
|
||||
if (r->sel_row < r->nfiles - 1) r->sel_row++;
|
||||
@@ -708,11 +1092,36 @@ static void on_key(Rigel *r, XKeyEvent *ke) {
|
||||
on_scroll(r, -4); return;
|
||||
} else if (sym == XK_Page_Down) {
|
||||
on_scroll(r, 4); return;
|
||||
} else if (sym == XK_F2) {
|
||||
/* rename selected */
|
||||
if (r->sel_row >= 0 && r->sel_row < r->nfiles) {
|
||||
snprintf(r->rename_buf, sizeof(r->rename_buf), "%s",
|
||||
r->files[r->sel_row].name);
|
||||
r->rename_len = (int)strlen(r->rename_buf);
|
||||
r->rename_idx = r->sel_row;
|
||||
r->rename_on = 1;
|
||||
}
|
||||
} else if (sym == XK_Delete) {
|
||||
/* trash selected */
|
||||
if (r->sel_row >= 0 && r->sel_row < r->nfiles) {
|
||||
char cmd[4096 + 512];
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"gio trash \"%s/%s\" 2>/dev/null"
|
||||
" || mv \"%s/%s\" ~/.local/share/Trash/files/ 2>/dev/null",
|
||||
r->cwd, r->files[r->sel_row].name,
|
||||
r->cwd, r->files[r->sel_row].name);
|
||||
system(cmd);
|
||||
list_dir(r);
|
||||
}
|
||||
} else if (sym == XK_h && (mod & ControlMask)) {
|
||||
r->show_hidden = !r->show_hidden;
|
||||
list_dir(r);
|
||||
} else if ((sym == XK_f && (mod & ControlMask)) || sym == XK_F3) {
|
||||
r->search_on = 1;
|
||||
r->search_len = 0;
|
||||
r->search_str[0] = '\0';
|
||||
search_filter(r);
|
||||
} else if (sym == XK_l && (mod & ControlMask)) {
|
||||
/* focus path bar */
|
||||
r->path_editing = 1;
|
||||
snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd);
|
||||
r->path_cursor = (int)strlen(r->path_buf);
|
||||
@@ -738,8 +1147,8 @@ static void init(Rigel *r) {
|
||||
r->hover_row = -1;
|
||||
r->sidebar_hover = -1;
|
||||
r->last_click_row = -1;
|
||||
r->ctx_hover = -1;
|
||||
|
||||
/* start in home directory */
|
||||
const char *home = getenv("HOME");
|
||||
if (!home || chdir(home) != 0) home = "/";
|
||||
snprintf(r->history[0], sizeof(r->history[0]), "%s", home);
|
||||
@@ -749,7 +1158,6 @@ static void init(Rigel *r) {
|
||||
list_dir(r);
|
||||
snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd);
|
||||
|
||||
/* window */
|
||||
XSetWindowAttributes swa = {
|
||||
.background_pixel = sk_color(&r->sk, SK_BG),
|
||||
.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask
|
||||
@@ -762,11 +1170,9 @@ static void init(Rigel *r) {
|
||||
CWBackPixel | CWEventMask, &swa);
|
||||
r->gc = XCreateGC(r->sk.dpy, r->win, 0, NULL);
|
||||
|
||||
/* double-buffer */
|
||||
r->buf = XCreatePixmap(r->sk.dpy, r->win, WIN_W, WIN_H, r->sk.depth);
|
||||
r->p = (SKPainter){ .app = &r->sk, .drw = r->buf, .gc = r->gc };
|
||||
|
||||
/* window hints */
|
||||
XStoreName(r->sk.dpy, r->win, "Rigel — File Manager");
|
||||
XClassHint *ch = XAllocClassHint();
|
||||
if (ch) { ch->res_name = (char*)"rigel"; ch->res_class = (char*)"Rigel";
|
||||
@@ -792,13 +1198,45 @@ static void run(Rigel *r) {
|
||||
while (r->running) {
|
||||
XEvent ev;
|
||||
XNextEvent(r->sk.dpy, &ev);
|
||||
|
||||
/* ── context menu events ─────────────────────────────── */
|
||||
if (r->ctx_vis && ev.xany.window == r->ctx_win) {
|
||||
if (ev.type == Expose) {
|
||||
ctx_draw(r);
|
||||
} else if (ev.type == MotionNotify) {
|
||||
int it = ctx_item_at(ev.xmotion.y);
|
||||
if (it != r->ctx_hover) {
|
||||
r->ctx_hover = it;
|
||||
ctx_draw(r);
|
||||
}
|
||||
} else if (ev.type == ButtonPress) {
|
||||
int it = ctx_item_at(ev.xbutton.y);
|
||||
ctx_hide(r);
|
||||
if (it >= 0 && !CTX[it].sep) ctx_exec(r, it);
|
||||
draw(r);
|
||||
} else if (ev.type == LeaveNotify) {
|
||||
r->ctx_hover = -1;
|
||||
ctx_draw(r);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* click outside context menu */
|
||||
if (r->ctx_vis && (ev.type == ButtonPress || ev.type == KeyPress)) {
|
||||
ctx_hide(r);
|
||||
draw(r);
|
||||
if (ev.type == KeyPress) on_key(r, &ev.xkey);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ── main window events ──────────────────────────────── */
|
||||
switch (ev.type) {
|
||||
case Expose:
|
||||
if (ev.xexpose.count == 0) draw(r);
|
||||
break;
|
||||
case ButtonPress:
|
||||
if (ev.xbutton.button == Button1)
|
||||
on_click(r, ev.xbutton.x, ev.xbutton.y, ev.xbutton.time);
|
||||
if (ev.xbutton.button == Button1 || ev.xbutton.button == Button3)
|
||||
on_click(r, ev.xbutton.x, ev.xbutton.y, ev.xbutton.time,
|
||||
ev.xbutton.button);
|
||||
else if (ev.xbutton.button == Button4) on_scroll(r, -1);
|
||||
else if (ev.xbutton.button == Button5) on_scroll(r, 1);
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user