diff --git a/.gitignore b/.gitignore index e09d489..bf9b303 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ deskbar/oriondeskbar nebula/nebula orionpkg/orionpkg +skykit/libskykit.a +skykit/skykit.o +rigel/rigel installer/orioninstall installer/orioninstall-gui launcher/orionlauncher diff --git a/Makefile b/Makefile index ead1c80..4c56309 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ # make clean — remove build artifacts # make iso — build the bootable ISO (requires Arch Linux + archiso) -.PHONY: all wm deskbar launcher installer nebula orionpkg clean install iso +.PHONY: all wm deskbar launcher installer nebula orionpkg skykit rigel clean install iso -all: wm deskbar launcher installer nebula orionpkg +all: wm deskbar launcher installer nebula orionpkg skykit rigel wm: $(MAKE) -C wm @@ -27,6 +27,12 @@ nebula: orionpkg: $(MAKE) -C orionpkg +skykit: + $(MAKE) -C skykit + +rigel: + $(MAKE) -C rigel + clean: $(MAKE) -C wm clean $(MAKE) -C deskbar clean @@ -34,6 +40,8 @@ clean: $(MAKE) -C installer clean $(MAKE) -C nebula clean $(MAKE) -C orionpkg clean + $(MAKE) -C skykit clean + $(MAKE) -C rigel clean install: all # binaries @@ -43,6 +51,7 @@ install: all install -Dm755 themes/orionwallpaper /usr/local/bin/orionwallpaper install -Dm755 nebula/nebula /usr/local/bin/nebula install -Dm755 orionpkg/orionpkg /usr/local/bin/orionpkg + install -Dm755 rigel/rigel /usr/local/bin/rigel # GTK theme mkdir -p /usr/share/themes cp -r themes/OrionOS /usr/share/themes/ diff --git a/deskbar/src/deskbar.c b/deskbar/src/deskbar.c index 306b501..0da5c48 100644 --- a/deskbar/src/deskbar.c +++ b/deskbar/src/deskbar.c @@ -291,6 +291,7 @@ static int read_mem_percent(void) { /* ── drawing ──────────────────────────────────────────────────────────── */ +/* y = baseline; clip_x/clip_w are optional horizontal clip region */ static void draw_str(Deskbar *db, XftDraw *xd, XftFont *font, unsigned long rgb, int x, int y, const char *str, int clip_x, int clip_w) { @@ -302,14 +303,16 @@ static void draw_str(Deskbar *db, XftDraw *xd, XftFont *font, (unsigned short)db->bar_h }; XftDrawSetClipRectangles(xd, 0, 0, &r, 1); } - XGlyphInfo ext; - XftTextExtentsUtf8(db->dpy, font, (const FcChar8 *)str, strlen(str), &ext); - XftDrawStringUtf8(xd, &col, font, x, y + ext.height - ext.y, - (const FcChar8 *)str, strlen(str)); + XftDrawStringUtf8(xd, &col, font, x, y, (const FcChar8 *)str, strlen(str)); if (clip_w > 0) XftDrawSetClip(xd, None); XftColorFree(db->dpy, db->visual, db->cmap, &col); } +/* Compute baseline that visually centers text in a strip */ +static int str_baseline(XftFont *font, int strip_y, int strip_h) { + return strip_y + (strip_h + font->ascent - font->descent) / 2; +} + static void draw_str_centered(Deskbar *db, XftDraw *xd, XftFont *font, unsigned long rgb, int strip_x, int strip_y, int strip_w, int strip_h, @@ -317,7 +320,7 @@ static void draw_str_centered(Deskbar *db, XftDraw *xd, XftFont *font, XGlyphInfo ext; XftTextExtentsUtf8(db->dpy, font, (const FcChar8 *)str, strlen(str), &ext); int tx = strip_x + (strip_w - (int)ext.width) / 2; - int ty = strip_y + (strip_h - (int)ext.height) / 2; + int ty = str_baseline(font, strip_y, strip_h); draw_str(db, xd, font, rgb, tx, ty, str, 0, 0); } @@ -343,8 +346,8 @@ static void draw_letter_icon(Deskbar *db, XftDraw *xd, unsigned long txt_col = mini ? DB_COL_ITEM_MINI : DB_COL_ITEM_HTXT; XGlyphInfo ext; XftTextExtentsUtf8(db->dpy, db->font_icon, (const FcChar8 *)letter, 1, &ext); - int lx = ix + (DB_ICON_SZ - ext.width) / 2; - int ly = iy + (DB_ICON_SZ - ext.height) / 2; + int lx = ix + (DB_ICON_SZ - (int)ext.width) / 2; + int ly = str_baseline(db->font_icon, iy, DB_ICON_SZ); draw_str(db, xd, db->font_icon, txt_col, lx, ly, letter, 0, 0); } @@ -427,7 +430,7 @@ void db_draw(Deskbar *db) { /* app name */ int name_x = icon_x + DB_ICON_SZ + 6; int name_maxw = DB_W - name_x - 14; - int name_y = iy + (DB_ITEM_H - 10) / 2; + int name_y = str_baseline(db->font_ui, iy, DB_ITEM_H); unsigned long tc = (hover || focus) ? DB_COL_ITEM_HTXT : mini ? DB_COL_ITEM_MINI @@ -636,7 +639,7 @@ static void menu_draw_panel(Deskbar *db, Window win, int is_sub, } unsigned long tc = (i == hov) ? DB_COL_ITEM_HTXT : DB_COL_ITEM_TEXT; draw_str(db, xd, db->font_ui, tc, - DB_FLYOUT_LPD, iy + (DB_FLYOUT_IH - 10) / 2, + DB_FLYOUT_LPD, str_baseline(db->font_ui, iy, DB_FLYOUT_IH), lbls[i], 0, 0); } } else { @@ -654,13 +657,13 @@ static void menu_draw_panel(Deskbar *db, Window win, int is_sub, } unsigned long tc = (i == hov) ? DB_COL_ITEM_HTXT : DB_COL_ITEM_TEXT; draw_str(db, xd, db->font_ui, tc, - DB_FLYOUT_LPD, iy + (DB_FLYOUT_IH - 10) / 2, + DB_FLYOUT_LPD, str_baseline(db->font_ui, iy, DB_FLYOUT_IH), MENU[i].lbl, 0, 0); if (MENU[i].has_sub) { unsigned long ac = (i == hov) ? DB_COL_ITEM_HTXT : 0x30486A; /* ▶ U+25B6 */ draw_str(db, xd, db->font_ui, ac, - ww - 15, iy + (DB_FLYOUT_IH - 10) / 2, + ww - 15, str_baseline(db->font_ui, iy, DB_FLYOUT_IH), "\xe2\x96\xb6", 0, 0); } } diff --git a/iso/docker/entrypoint.sh b/iso/docker/entrypoint.sh index 2a3798a..06f325a 100755 --- a/iso/docker/entrypoint.sh +++ b/iso/docker/entrypoint.sh @@ -238,6 +238,10 @@ install -m755 "$SRC/installer/orioninstall" "$AIROOTFS/usr/local/bin/orio if [[ -f "$SRC/installer/orioninstall-gui" ]]; then install -m755 "$SRC/installer/orioninstall-gui" "$AIROOTFS/usr/local/bin/orioninstall-gui" fi +# Rigel file manager (compiled binary) +if [[ -f "$SRC/rigel/rigel" ]]; then + install -m755 "$SRC/rigel/rigel" "$AIROOTFS/usr/local/bin/rigel" +fi # ── 5. GTK theme ────────────────────────────────────────────────────── echo ">>> Installing GTK theme..." diff --git a/rigel/Makefile b/rigel/Makefile new file mode 100644 index 0000000..093a0a8 --- /dev/null +++ b/rigel/Makefile @@ -0,0 +1,21 @@ +BIN = rigel + +CC = gcc +CFLAGS = -Wall -Wextra -O2 \ + $(shell pkg-config --cflags x11 xft fontconfig 2>/dev/null) \ + -I../skykit +LIBS = $(shell pkg-config --libs x11 xft fontconfig 2>/dev/null) \ + -lX11 -lXft -lfontconfig + +SKYKIT = ../skykit/libskykit.a + +$(BIN): rigel.c $(SKYKIT) + $(CC) $(CFLAGS) -o $@ rigel.c $(SKYKIT) $(LIBS) + +$(SKYKIT): + $(MAKE) -C ../skykit + +clean: + rm -f $(BIN) + +.PHONY: clean diff --git a/rigel/rigel.c b/rigel/rigel.c new file mode 100644 index 0000000..cf5dd53 --- /dev/null +++ b/rigel/rigel.c @@ -0,0 +1,832 @@ +/* rigel.c — OrionOS File Manager + * Named after Rigel, the brightest star in Orion. + * Native C/Xlib + SkyKit. No GTK/Qt dependency. + * + * Layout: + * [toolbar: ← → ↑ /path/bar ] + * [sidebar | Name ↑ Size Modified ] + * [ | DIR folder - Jul 23 ] + * [ | TXT file.txt 4 KB Jul 22 ] + * [ | IMG photo.jpg 2 MB Jul 10 ] + * [ Home | ] + * [ Docs |────────────────────────────────────── ] + * [ Down | 47 items ] + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../skykit/skykit.h" + +/* ── Layout ──────────────────────────────────────────────────────── */ +#define WIN_W 920 +#define WIN_H 600 +#define SIDE_W 180 +#define TOOL_H 40 +#define COL_H 22 +#define STAT_H 24 +#define ROW_H 24 + +#define FILE_X (SIDE_W + 1) +#define FILE_W (WIN_W - SIDE_W - 1) +#define LIST_Y (TOOL_H + COL_H) +#define LIST_H (WIN_H - TOOL_H - COL_H - STAT_H) +#define LIST_ROWS (LIST_H / ROW_H) + +/* columns within the file area (relative to FILE_X) */ +#define BADGE_W 44 +#define DATE_W 130 +#define SIZE_W 80 +#define SB_W 8 +#define NAME_W (FILE_W - BADGE_W - SIZE_W - DATE_W - SB_W - 2) + +/* sidebar */ +#define SIDE_HDR_H 36 +#define SIDE_ITEM_H 28 + +/* toolbar */ +#define BTN_W 26 +#define BTN_H 24 +#define BACK_X 8 +#define FWD_X (BACK_X + BTN_W + 4) +#define UP_X (FWD_X + BTN_W + 4) +#define PATH_X (UP_X + BTN_W + 10) +#define PATH_W (WIN_W - PATH_X - 8) +#define PATH_H 26 + +/* ── File types ───────────────────────────────────────────────────── */ +typedef enum { + FT_DIR, FT_IMAGE, FT_AUDIO, FT_VIDEO, FT_ARCHIVE, + FT_CODE, FT_PDF, FT_TEXT, FT_EXEC, FT_UNKNOWN, + FT__N +} FileType; + +static const struct { unsigned long bg; const char *lbl; unsigned long fg; } +BADGE[FT__N] = { + [FT_DIR] = { 0x1A4888, "DIR", 0xA0C8F8 }, + [FT_IMAGE] = { 0x5A3088, "IMG", 0xCCA8F8 }, + [FT_AUDIO] = { 0x1A6038, "SND", 0x80E0A8 }, + [FT_VIDEO] = { 0x782020, "VID", 0xF88080 }, + [FT_ARCHIVE] = { 0x6A3808, "ZIP", 0xF0A060 }, + [FT_CODE] = { 0x0A5868, "SRC", 0x60D8E8 }, + [FT_PDF] = { 0x882010, "PDF", 0xF87060 }, + [FT_TEXT] = { 0x203040, "TXT", 0x80A0C0 }, + [FT_EXEC] = { 0x1A6828, "EXE", 0x80F890 }, + [FT_UNKNOWN] = { 0x1A2030, "---", 0x506070 }, +}; + +static FileType detect_type(const char *name, int is_dir, int is_exec) { + if (is_dir) return FT_DIR; + if (is_exec) return FT_EXEC; + const char *e = strrchr(name, '.'); + if (!e) return FT_UNKNOWN; + e++; +#define EQ(a) (strcasecmp(e,(a))==0) + if (EQ("jpg")||EQ("jpeg")||EQ("png")||EQ("gif")||EQ("bmp")||EQ("webp")||EQ("svg")||EQ("tiff")) return FT_IMAGE; + if (EQ("mp3")||EQ("flac")||EQ("ogg")||EQ("wav")||EQ("aac")||EQ("m4a")||EQ("opus")) return FT_AUDIO; + if (EQ("mp4")||EQ("mkv")||EQ("avi")||EQ("mov")||EQ("webm")||EQ("flv")||EQ("m4v")) return FT_VIDEO; + if (EQ("zip")||EQ("tar")||EQ("gz")||EQ("xz")||EQ("bz2")||EQ("7z")||EQ("rar")||EQ("tgz")) return FT_ARCHIVE; + if (EQ("c")||EQ("h")||EQ("cpp")||EQ("py")||EQ("js")||EQ("ts")||EQ("rs")||EQ("go") + ||EQ("sh")||EQ("bash")||EQ("java")||EQ("rb")||EQ("css")||EQ("html")||EQ("xml") + ||EQ("json")||EQ("toml")||EQ("yaml")||EQ("yml")) return FT_CODE; + if (EQ("pdf")) return FT_PDF; + if (EQ("txt")||EQ("md")||EQ("rst")||EQ("log")||EQ("csv")) return FT_TEXT; +#undef EQ + return FT_UNKNOWN; +} + +/* ── File entry ───────────────────────────────────────────────────── */ +#define FMAX 4096 +typedef struct { + char name[512]; + off_t size; + time_t mtime; + FileType type; +} FileEntry; + +/* ── Bookmarks ────────────────────────────────────────────────────── */ +static const struct { const char *name; const char *path; } BKMARKS[] = { + { "Home", "~" }, + { "Desktop", "~/Desktop" }, + { "Documents", "~/Documents" }, + { "Downloads", "~/Downloads" }, + { "Pictures", "~/Pictures" }, + { "Music", "~/Music" }, + { "Videos", "~/Videos" }, + { "Trash", "~/.local/share/Trash/files" }, +}; +#define NBKMARKS ((int)(sizeof(BKMARKS)/sizeof(BKMARKS[0]))) + +/* ── App struct ───────────────────────────────────────────────────── */ +#define HIST_MAX 64 + +typedef struct { + SKApp sk; + Window win; + GC gc; + Pixmap buf; + SKPainter p; + Atom wm_delete; + int running; + + char cwd[4096]; + char history[HIST_MAX][4096]; + int hist_pos; + int hist_len; + + FileEntry files[FMAX]; + int nfiles; + int scroll; + int hover_row; + int sel_row; + int show_hidden; + 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 */ + + char path_buf[4096]; + int path_editing; + int path_cursor; + + Time last_click_time; + int last_click_row; +} Rigel; + +/* ── Path helpers ─────────────────────────────────────────────────── */ +static void expand_path(const char *in, char *out, int len) { + const char *home = getenv("HOME"); + if (!home) home = "/"; + if (in[0] == '~') { + snprintf(out, len, "%s%s", home, in + 1); + } else { + snprintf(out, len, "%s", in); + } +} + +static void fmt_size(off_t sz, char *buf, int len) { + if (sz < 0) snprintf(buf, len, "—"); + else if (sz < 1024) snprintf(buf, len, "%lld B", (long long)sz); + else if (sz < 1<<20) snprintf(buf, len, "%.1f KB", sz / 1024.0); + else if (sz < (1<<20)*1024LL) snprintf(buf, len, "%.1f MB", sz / (1024.0*1024)); + else snprintf(buf, len, "%.1f GB", sz / (1024.0*1024*1024)); +} + +static void fmt_date(time_t t, char *buf, int len) { + static const char *MO[] = { + "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" + }; + struct tm *tm = localtime(&t); + if (!tm) { snprintf(buf, len, "—"); return; } + snprintf(buf, len, "%s %2d, %4d", MO[tm->tm_mon], tm->tm_mday, 1900+tm->tm_year); +} + +/* ── Sort ────────────────────────────────────────────────────────── */ +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; + switch (g_sort_col) { + case 1: r = (fa->size > fb->size) - (fa->size < fb->size); break; + case 2: r = (fa->mtime > fb->mtime) - (fa->mtime < fb->mtime); break; + default: r = strcasecmp(fa->name, fb->name); break; + } + return r * g_sort_dir; +} + +/* ── Directory listing ───────────────────────────────────────────── */ +static void list_dir(Rigel *r) { + r->nfiles = 0; + r->scroll = 0; + r->sel_row = -1; + + DIR *dir = opendir(r->cwd); + if (!dir) return; + + struct dirent *ent; + while ((ent = readdir(dir)) && r->nfiles < FMAX) { + const char *n = ent->d_name; + if (strcmp(n, ".") == 0 || strcmp(n, "..") == 0) continue; + if (!r->show_hidden && n[0] == '.') continue; + + char full[4096*2]; + snprintf(full, sizeof(full), "%s/%s", r->cwd, n); + + struct stat st; + if (stat(full, &st) != 0) continue; + + FileEntry *fe = &r->files[r->nfiles++]; + snprintf(fe->name, sizeof(fe->name), "%s", n); + fe->size = S_ISDIR(st.st_mode) ? -1 : st.st_size; + fe->mtime = st.st_mtime; + fe->type = detect_type(n, S_ISDIR(st.st_mode), + !S_ISDIR(st.st_mode) && (st.st_mode & S_IXUSR)); + } + closedir(dir); + + g_sort_col = r->sort_col; + g_sort_dir = r->sort_dir; + qsort(r->files, r->nfiles, sizeof(FileEntry), cmp_files); +} + +/* ── Navigation ──────────────────────────────────────────────────── */ +static void nav_cd(Rigel *r, const char *path) { + char real[4096]; + if (realpath(path, real)) { + snprintf(r->cwd, sizeof(r->cwd), "%s", real); + } 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++; + if (r->hist_pos >= HIST_MAX) { + memmove(r->history[0], r->history[1], (HIST_MAX-1) * sizeof(r->history[0])); + r->hist_pos = HIST_MAX - 1; + } + snprintf(r->history[r->hist_pos], sizeof(r->history[0]), "%s", r->cwd); + r->hist_len = r->hist_pos + 1; +done: + list_dir(r); + snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd); +} + +static void nav_back(Rigel *r) { + if (r->hist_pos <= 0) return; + r->hist_pos--; + snprintf(r->cwd, sizeof(r->cwd), "%s", r->history[r->hist_pos]); + list_dir(r); + snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd); +} + +static void nav_fwd(Rigel *r) { + if (r->hist_pos >= r->hist_len - 1) return; + r->hist_pos++; + snprintf(r->cwd, sizeof(r->cwd), "%s", r->history[r->hist_pos]); + list_dir(r); + snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd); +} + +static void nav_up(Rigel *r) { + char parent[4096]; + snprintf(parent, sizeof(parent), "%s", r->cwd); + char *sl = strrchr(parent, '/'); + if (!sl || sl == parent) nav_cd(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]; + if (fe->type == FT_DIR) { + char path[4096*2]; + snprintf(path, sizeof(path), "%s/%s", r->cwd, fe->name); + nav_cd(r, path); + } else { + char cmd[4096*2 + 32]; + snprintf(cmd, sizeof(cmd), "xdg-open \"%s/%s\" 2>/dev/null &", + r->cwd, fe->name); + system(cmd); /* intentional: user-triggered open */ + } +} + +/* ── Drawing helpers ─────────────────────────────────────────────── */ +static void draw_flush(Rigel *r) { + XCopyArea(r->sk.dpy, r->buf, r->win, r->gc, + 0, 0, WIN_W, WIN_H, 0, 0); + XFlush(r->sk.dpy); +} + +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; + + int by = (TOOL_H - BTN_H) / 2; + struct { int id; int x; const char *lbl; int active; } btns[] = { + { 1, BACK_X, "←", can_back }, + { 2, FWD_X, "→", can_fwd }, + { 3, UP_X, "↑", 1 }, + }; + for (int i = 0; i < 3; i++) { + unsigned long bg = btns[i].active ? SK_BTN : SK_PANEL; + unsigned long bgh = btns[i].active ? SK_BTN_HOV : SK_PANEL; + unsigned long fg = btns[i].active ? SK_TEXT_HDR : SK_TEXT_DIM; + int hov = (r->hover_btn == btns[i].id && btns[i].active); + sk_fill(&r->p, hov ? bgh : bg, btns[i].x, by, BTN_W, BTN_H); + sk_rect(&r->p, SK_BORDER, btns[i].x, by, BTN_W, BTN_H); + int tx = btns[i].x + (BTN_W - sk_textw(&r->sk, r->sk.font_ui, btns[i].lbl)) / 2; + sk_text(&r->p, r->sk.font_ui, fg, tx, + sk_baseline(r->sk.font_ui, by, BTN_H), btns[i].lbl); + } + + /* path bar */ + int px = PATH_X, pw = PATH_W, ph = PATH_H, py = (TOOL_H - ph) / 2; + unsigned long pbg = r->path_editing ? SK_PANEL : SK_BG; + sk_fill(&r->p, pbg, px, py, pw, ph); + sk_rect(&r->p, r->path_editing ? SK_ACCENT : SK_BORDER, px, py, pw, ph); + + const char *ptext = r->path_editing ? r->path_buf : r->cwd; + sk_text_clip(&r->p, r->sk.font_ui, SK_TEXT_HDR, + 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; + if (cpos > (int)strlen(r->path_buf)) cpos = (int)strlen(r->path_buf); + snprintf(tmp, sizeof(tmp), "%.*s", cpos, r->path_buf); + int cx = px + 6 + sk_textw(&r->sk, r->sk.font_ui, tmp); + sk_fill(&r->p, SK_TEXT, cx, py + 4, 1, ph - 8); + } +} + +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"); + sk_line(&r->p, SK_BORDER, 0, TOOL_H + SIDE_HDR_H - 1, SIDE_W, TOOL_H + SIDE_HDR_H - 1); + + 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); + + 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); + sk_text(&r->p, r->sk.font_ui, fg, active ? 16 : 12, + sk_baseline(r->sk.font_ui, iy, SIDE_ITEM_H), BKMARKS[i].name); + } +} + +static void draw_col_header(Rigel *r) { + int hy = TOOL_H; + sk_fill(&r->p, 0x0A1020UL, FILE_X, hy, FILE_W, COL_H); + sk_line(&r->p, SK_BORDER, FILE_X, hy + COL_H - 1, WIN_W, hy + COL_H - 1); + + struct { int x; int w; const char *lbl; int col; } cols[] = { + { FILE_X + BADGE_W, NAME_W, "Name", 0 }, + { FILE_X + BADGE_W + NAME_W, SIZE_W, "Size", 1 }, + { FILE_X + BADGE_W + NAME_W + SIZE_W, DATE_W, "Modified", 2 }, + }; + for (int i = 0; i < 3; i++) { + 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, + cols[i].x + 4 + sk_textw(&r->sk, r->sk.font_sm, cols[i].lbl) + 4, + sk_baseline(r->sk.font_sm, hy, COL_H), arr); + } + } +} + +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; + 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]; + + /* 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; + sk_fill(&r->p, bg, FILE_X, ry, FILE_W - SB_W, ROW_H); + + /* type badge */ + int bx = FILE_X + 4, badgew = BADGE_W - 8; + int badgeh = ROW_H - 6, badgey = ry + 3; + sk_fill(&r->p, BADGE[fe->type].bg, bx, badgey, badgew, badgeh); + int lx = bx + (badgew - sk_textw(&r->sk, r->sk.font_sm, BADGE[fe->type].lbl)) / 2; + sk_text(&r->p, r->sk.font_sm, BADGE[fe->type].fg, lx, + sk_baseline(r->sk.font_sm, badgey, badgeh), BADGE[fe->type].lbl); + + /* 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; + sk_text_clip(&r->p, r->sk.font_ui, fg, + FILE_X + BADGE_W + 2, ty, + fe->name, + FILE_X + BADGE_W, ry, NAME_W, ROW_H); + + /* size */ + char sbuf[32]; fmt_size(fe->size, sbuf, sizeof(sbuf)); + int sx = FILE_X + BADGE_W + NAME_W + 4; + sk_text_clip(&r->p, r->sk.font_ui, SK_TEXT_DIM, sx, ty, + sbuf, sx, ry, SIZE_W - 8, ROW_H); + + /* date */ + char dbuf[32]; fmt_date(fe->mtime, dbuf, sizeof(dbuf)); + int dx = FILE_X + BADGE_W + NAME_W + SIZE_W + 4; + 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) { + 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; + if (th < 20) th = 20; + int ty2 = LIST_Y + (LIST_H - th) * r->scroll / (r->nfiles - LIST_ROWS); + sk_fill(&r->p, SK_ACCENT, sbx, ty2, SB_W, th); + } +} + +static void draw_statusbar(Rigel *r) { + int sy = WIN_H - STAT_H; + 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"; + 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(Rigel *r) { + draw_toolbar(r); + draw_sidebar(r); + draw_col_header(r); + draw_filelist(r); + draw_statusbar(r); + draw_flush(r); +} + +/* ── Hit testing ─────────────────────────────────────────────────── */ +static int toolbar_btn_at(int x, int y) { + int by = (TOOL_H - BTN_H) / 2; + if (!sk_in_rect(x, y, 0, by, UP_X + BTN_W, BTN_H)) return 0; + if (sk_in_rect(x, y, BACK_X, by, BTN_W, BTN_H)) return 1; + if (sk_in_rect(x, y, FWD_X, by, BTN_W, BTN_H)) return 2; + if (sk_in_rect(x, y, UP_X, by, BTN_W, BTN_H)) return 3; + return 0; +} + +static int file_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; +} + +static int col_header_at(int x, int y) { + if (!sk_in_rect(x, y, FILE_X, TOOL_H, FILE_W, COL_H)) return -1; + if (x < FILE_X + BADGE_W + NAME_W) return 0; + if (x < FILE_X + BADGE_W + NAME_W + SIZE_W) return 1; + return 2; +} + +static int sidebar_item_at(int x, int y) { + if (!sk_in_rect(x, y, 0, TOOL_H + SIDE_HDR_H, SIDE_W, WIN_H)) return -1; + int i = (y - TOOL_H - SIDE_HDR_H) / SIDE_ITEM_H; + return (i >= 0 && i < NBKMARKS) ? i : -1; +} + +/* ── 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 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->sidebar_hover = hside; + if (redraw) draw(r); +} + +static void on_scroll(Rigel *r, int dir) { + int max = r->nfiles - LIST_ROWS; + if (max < 0) max = 0; + r->scroll += dir * 3; + if (r->scroll < 0) r->scroll = 0; + if (r->scroll > max) r->scroll = max; + draw(r); +} + +static void path_commit(Rigel *r) { + r->path_editing = 0; + char expanded[4096]; + expand_path(r->path_buf, expanded, sizeof(expanded)); + nav_cd(r, expanded); +} + +static void on_click(Rigel *r, int x, int y, Time t) { + /* path bar */ + int py = (TOOL_H - PATH_H) / 2; + if (sk_in_rect(x, y, PATH_X, py, PATH_W, PATH_H)) { + if (!r->path_editing) { + r->path_editing = 1; + snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd); + r->path_cursor = (int)strlen(r->path_buf); + } + draw(r); + 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; } + + /* sidebar */ + int si = sidebar_item_at(x, y); + if (si >= 0) { + char expanded[4096]; + expand_path(BKMARKS[si].path, expanded, sizeof(expanded)); + nav_cd(r, expanded); + draw(r); + return; + } + + /* column header */ + int col = col_header_at(x, y); + if (col >= 0) { + 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); + 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; + + /* double-click detection (300ms) */ + int dbl = (row == r->last_click_row && (t - r->last_click_time) < 300); + r->last_click_time = t; + r->last_click_row = row; + + if (dbl) { + open_entry(r, row); + } else { + r->sel_row = (r->sel_row == row) ? -1 : row; + } + draw(r); +} + +static void on_key(Rigel *r, XKeyEvent *ke) { + KeySym sym = XLookupKeysym(ke, 0); + + if (r->path_editing) { + if (sym == XK_Return || sym == XK_KP_Enter) { + path_commit(r); + } else if (sym == XK_Escape) { + r->path_editing = 0; + snprintf(r->path_buf, sizeof(r->path_buf), "%s", r->cwd); + } else if (sym == XK_BackSpace) { + if (r->path_cursor > 0) { + int n = (int)strlen(r->path_buf); + memmove(r->path_buf + r->path_cursor - 1, + r->path_buf + r->path_cursor, + n - r->path_cursor + 1); + r->path_cursor--; + } + } else if (sym == XK_Left) { + if (r->path_cursor > 0) r->path_cursor--; + } else if (sym == XK_Right) { + int n = (int)strlen(r->path_buf); + if (r->path_cursor < n) r->path_cursor++; + } else if (sym == XK_Home) { + r->path_cursor = 0; + } 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) { + buf[n] = '\0'; + int len = (int)strlen(r->path_buf); + if (len + n < (int)sizeof(r->path_buf) - 1) { + memmove(r->path_buf + r->path_cursor + n, + r->path_buf + r->path_cursor, + len - r->path_cursor + 1); + memcpy(r->path_buf + r->path_cursor, buf, n); + r->path_cursor += n; + } + } + } + draw(r); + return; + } + + /* navigation shortcuts */ + int mod = ke->state; + if (sym == XK_BackSpace || (sym == XK_Left && (mod & Mod1Mask))) { + nav_back(r); + } else if (sym == XK_Right && (mod & Mod1Mask)) { + nav_fwd(r); + } else if (sym == XK_Up && (mod & Mod1Mask)) { + nav_up(r); + } 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++; + else if (r->sel_row < 0 && r->nfiles > 0) r->sel_row = 0; + if (r->sel_row >= r->scroll + LIST_ROWS) r->scroll = r->sel_row - LIST_ROWS + 1; + } else if (sym == XK_Return || sym == XK_KP_Enter) { + if (r->sel_row >= 0) open_entry(r, r->sel_row); + } else if (sym == XK_Page_Up) { + on_scroll(r, -4); return; + } else if (sym == XK_Page_Down) { + on_scroll(r, 4); return; + } else if (sym == XK_h && (mod & ControlMask)) { + r->show_hidden = !r->show_hidden; + list_dir(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); + } else if (sym == XK_slash) { + r->path_editing = 1; + snprintf(r->path_buf, sizeof(r->path_buf), "/"); + r->path_cursor = 1; + } else if (sym == XK_Escape) { + r->sel_row = -1; + } else if (sym == XK_q && (mod & ControlMask)) { + r->running = 0; return; + } + draw(r); +} + +/* ── Init ────────────────────────────────────────────────────────── */ +static void init(Rigel *r) { + sk_app_init(&r->sk); + + r->sort_col = 0; + r->sort_dir = 1; + r->sel_row = -1; + r->hover_row = -1; + r->sidebar_hover = -1; + r->last_click_row = -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); + r->hist_pos = 0; + r->hist_len = 1; + snprintf(r->cwd, sizeof(r->cwd), "%s", home); + 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 + | PointerMotionMask | KeyPressMask | StructureNotifyMask + | LeaveWindowMask, + }; + r->win = XCreateWindow(r->sk.dpy, r->sk.root, + 80, 60, WIN_W, WIN_H, 0, + r->sk.depth, InputOutput, r->sk.vis, + 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"; + XSetClassHint(r->sk.dpy, r->win, ch); XFree(ch); } + + XSizeHints *sh = XAllocSizeHints(); + if (sh) { sh->flags = PMinSize; sh->min_width = 600; sh->min_height = 400; + XSetWMNormalHints(r->sk.dpy, r->win, sh); XFree(sh); } + + r->wm_delete = XInternAtom(r->sk.dpy, "WM_DELETE_WINDOW", False); + Atom wm_prot = XInternAtom(r->sk.dpy, "WM_PROTOCOLS", False); + XChangeProperty(r->sk.dpy, r->win, wm_prot, XA_ATOM, 32, + PropModeReplace, (unsigned char *)&r->wm_delete, 1); + + XMapWindow(r->sk.dpy, r->win); + XFlush(r->sk.dpy); + r->running = 1; +} + +/* ── Event loop ──────────────────────────────────────────────────── */ +static void run(Rigel *r) { + draw(r); + while (r->running) { + XEvent ev; + XNextEvent(r->sk.dpy, &ev); + 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); + else if (ev.xbutton.button == Button4) on_scroll(r, -1); + else if (ev.xbutton.button == Button5) on_scroll(r, 1); + break; + case MotionNotify: + on_motion(r, ev.xmotion.x, ev.xmotion.y); + break; + case LeaveNotify: + if (r->hover_row >= 0 || r->hover_btn != 0 || r->sidebar_hover >= 0) { + r->hover_row = -1; r->hover_btn = 0; r->sidebar_hover = -1; + draw(r); + } + break; + case KeyPress: + on_key(r, &ev.xkey); + break; + case ClientMessage: + if ((Atom)ev.xclient.data.l[0] == r->wm_delete) r->running = 0; + break; + case DestroyNotify: + r->running = 0; + break; + } + } +} + +int main(void) { + Rigel r = {0}; + init(&r); + run(&r); + return 0; +} diff --git a/skykit/Makefile b/skykit/Makefile new file mode 100644 index 0000000..9feac7b --- /dev/null +++ b/skykit/Makefile @@ -0,0 +1,22 @@ +LIB = libskykit.a + +CC = gcc +CFLAGS = -Wall -Wextra -O2 \ + $(shell pkg-config --cflags x11 xft fontconfig 2>/dev/null) + +OBJ = skykit.o + +$(LIB): $(OBJ) + ar rcs $@ $^ + +skykit.o: skykit.c skykit.h + $(CC) $(CFLAGS) -c -o $@ skykit.c + +install: $(LIB) + install -Dm644 skykit.h /usr/local/include/skykit.h + install -Dm644 $(LIB) /usr/local/lib/$(LIB) + +clean: + rm -f $(OBJ) $(LIB) + +.PHONY: install clean diff --git a/skykit/skykit.c b/skykit/skykit.c new file mode 100644 index 0000000..b0a10b0 --- /dev/null +++ b/skykit/skykit.c @@ -0,0 +1,113 @@ +/* skykit.c — OrionOS native UI toolkit implementation */ +#include "skykit.h" + +void sk_app_init(SKApp *app) { + app->dpy = XOpenDisplay(NULL); + if (!app->dpy) { fputs("skykit: cannot open display\n", stderr); exit(1); } + app->screen = DefaultScreen(app->dpy); + app->root = RootWindow(app->dpy, app->screen); + app->cmap = DefaultColormap(app->dpy, app->screen); + app->vis = DefaultVisual(app->dpy, app->screen); + app->depth = DefaultDepth(app->dpy, app->screen); + + app->font_ui = XftFontOpenName(app->dpy, app->screen, SK_FONT_UI); + app->font_sm = XftFontOpenName(app->dpy, app->screen, SK_FONT_SM); + app->font_hdr = XftFontOpenName(app->dpy, app->screen, SK_FONT_HDR); + app->font_mono = XftFontOpenName(app->dpy, app->screen, SK_FONT_MONO); + if (!app->font_ui) { fputs("skykit: cannot open UI font\n", stderr); exit(1); } + if (!app->font_sm) app->font_sm = app->font_ui; + if (!app->font_hdr) app->font_hdr = app->font_ui; + if (!app->font_mono) app->font_mono = app->font_ui; +} + +void sk_app_free(SKApp *app) { + if (app->font_ui) XftFontClose(app->dpy, app->font_ui); + if (app->font_sm && app->font_sm != app->font_ui) XftFontClose(app->dpy, app->font_sm); + if (app->font_hdr && app->font_hdr != app->font_ui) XftFontClose(app->dpy, app->font_hdr); + if (app->font_mono && app->font_mono != app->font_ui) XftFontClose(app->dpy, app->font_mono); + XCloseDisplay(app->dpy); +} + +unsigned long sk_color(SKApp *app, unsigned long rgb) { + XColor c = { + .red = ((rgb >> 16) & 0xFF) * 257, + .green = ((rgb >> 8) & 0xFF) * 257, + .blue = ( rgb & 0xFF) * 257, + .flags = DoRed | DoGreen | DoBlue + }; + XAllocColor(app->dpy, app->cmap, &c); + return c.pixel; +} + +void sk_xft_color(SKApp *app, unsigned long rgb, XftColor *out) { + XRenderColor rc = { + .red = ((rgb >> 16) & 0xFF) * 257, + .green = ((rgb >> 8) & 0xFF) * 257, + .blue = ( rgb & 0xFF) * 257, + .alpha = 0xFFFF + }; + XftColorAllocValue(app->dpy, app->vis, app->cmap, &rc, out); +} + +void sk_fill(SKPainter *p, unsigned long rgb, int x, int y, int w, int h) { + XSetForeground(p->app->dpy, p->gc, sk_color(p->app, rgb)); + XFillRectangle(p->app->dpy, p->drw, p->gc, x, y, (unsigned)w, (unsigned)h); +} + +void sk_rect(SKPainter *p, unsigned long rgb, int x, int y, int w, int h) { + XSetForeground(p->app->dpy, p->gc, sk_color(p->app, rgb)); + XDrawRectangle(p->app->dpy, p->drw, p->gc, x, y, (unsigned)(w-1), (unsigned)(h-1)); +} + +void sk_line(SKPainter *p, unsigned long rgb, int x1, int y1, int x2, int y2) { + XSetForeground(p->app->dpy, p->gc, sk_color(p->app, rgb)); + XDrawLine(p->app->dpy, p->drw, p->gc, x1, y1, x2, y2); +} + +void sk_text(SKPainter *p, XftFont *font, unsigned long rgb, + int x, int y, const char *s) { + if (!s || !s[0]) return; + XftDraw *xd = XftDrawCreate(p->app->dpy, p->drw, p->app->vis, p->app->cmap); + XftColor col; sk_xft_color(p->app, rgb, &col); + XftDrawStringUtf8(xd, &col, font, x, y, (const FcChar8 *)s, (int)strlen(s)); + XftColorFree(p->app->dpy, p->app->vis, p->app->cmap, &col); + XftDrawDestroy(xd); +} + +void sk_text_clip(SKPainter *p, XftFont *font, unsigned long rgb, + int x, int y, const char *s, + int cx, int cy, int cw, int ch) { + if (!s || !s[0]) return; + XftDraw *xd = XftDrawCreate(p->app->dpy, p->drw, p->app->vis, p->app->cmap); + XRectangle clip = { (short)cx, (short)cy, (unsigned short)cw, (unsigned short)ch }; + XftDrawSetClipRectangles(xd, 0, 0, &clip, 1); + XftColor col; sk_xft_color(p->app, rgb, &col); + XftDrawStringUtf8(xd, &col, font, x, y, (const FcChar8 *)s, (int)strlen(s)); + XftColorFree(p->app->dpy, p->app->vis, p->app->cmap, &col); + XftDrawSetClip(xd, None); + XftDrawDestroy(xd); +} + +int sk_textw(SKApp *app, XftFont *font, const char *s) { + if (!s || !s[0]) return 0; + XGlyphInfo ext; + XftTextExtentsUtf8(app->dpy, font, (const FcChar8 *)s, (int)strlen(s), &ext); + return ext.xOff; +} + +int sk_baseline(XftFont *font, int y, int h) { + return y + (h + font->ascent - font->descent) / 2; +} + +int sk_in_rect(int mx, int my, int x, int y, int w, int h) { + return mx >= x && mx < x + w && my >= y && my < y + h; +} + +void sk_draw_btn(SKPainter *p, SKApp *app, XftFont *font, const SKBtn *b, + int hovered, unsigned long bg, unsigned long bghov) { + unsigned long bbg = hovered ? bghov : bg; + sk_fill(p, bbg, b->x, b->y, b->w, b->h); + sk_rect(p, SK_BORDER, b->x, b->y, b->w, b->h); + int tx = b->x + (b->w - sk_textw(app, font, b->label)) / 2; + sk_text(p, font, SK_TEXT_HDR, tx, sk_baseline(font, b->y, b->h), b->label); +} diff --git a/skykit/skykit.h b/skykit/skykit.h new file mode 100644 index 0000000..c9450f4 --- /dev/null +++ b/skykit/skykit.h @@ -0,0 +1,91 @@ +/* skykit.h — OrionOS native UI toolkit + * Shared palette, drawing utilities, and app boilerplate used by all OrionOS + * shell components (Rigel, Nebula, OrionPkg, installer, etc.). + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ── Palette ─────────────────────────────────────────────────────── */ +#define SK_BG 0x0E1220UL +#define SK_SURFACE 0x141828UL +#define SK_SIDEBAR 0x090D1AUL +#define SK_PANEL 0x111828UL +#define SK_BORDER 0x1A2840UL +#define SK_SEP 0x162030UL +#define SK_ACCENT 0x1E58A8UL +#define SK_ACCENT_HOV 0x2A70CCUL +#define SK_BTN 0x182848UL +#define SK_BTN_HOV 0x1E3860UL +#define SK_SEL 0x1A3058UL +#define SK_ROW_ALT 0x0C1520UL +#define SK_ROW_HOV 0x132030UL +#define SK_TEXT 0xC8D4E8UL +#define SK_TEXT_HDR 0xE8F4FFUL +#define SK_TEXT_DIM 0x5A6880UL +#define SK_TEXT_HOV 0xF0F8FFUL +#define SK_TEXT_OK 0x44BB77UL +#define SK_TEXT_ERR 0xFF5555UL +#define SK_TEXT_WARN 0xFFCC44UL + +/* ── Standard fonts ──────────────────────────────────────────────── */ +#define SK_FONT_UI "Sans:size=9" +#define SK_FONT_SM "Sans:size=8" +#define SK_FONT_HDR "Sans:size=10:weight=bold" +#define SK_FONT_MONO "DejaVu Sans Mono:size=8" + +/* ── App context (one per process) ──────────────────────────────── */ +typedef struct { + Display *dpy; + int screen; + Window root; + Visual *vis; + Colormap cmap; + int depth; + XftFont *font_ui; + XftFont *font_sm; + XftFont *font_hdr; + XftFont *font_mono; +} SKApp; + +/* ── Painter (per-window drawing context) ────────────────────────── */ +typedef struct { + SKApp *app; + Drawable drw; + GC gc; +} SKPainter; + +/* ── Simple button descriptor ────────────────────────────────────── */ +typedef struct { + int x, y, w, h; + const char *label; + int id; +} SKBtn; + +/* ── API ─────────────────────────────────────────────────────────── */ +void sk_app_init (SKApp *app); +void sk_app_free (SKApp *app); + +unsigned long sk_color (SKApp *app, unsigned long rgb); +void sk_xft_color (SKApp *app, unsigned long rgb, XftColor *out); + +void sk_fill (SKPainter *p, unsigned long rgb, int x, int y, int w, int h); +void sk_rect (SKPainter *p, unsigned long rgb, int x, int y, int w, int h); +void sk_line (SKPainter *p, unsigned long rgb, int x1, int y1, int x2, int y2); +void sk_text (SKPainter *p, XftFont *font, unsigned long rgb, int x, int y, const char *s); +void sk_text_clip (SKPainter *p, XftFont *font, unsigned long rgb, int x, int y, + const char *s, int cx, int cy, int cw, int ch); +int sk_textw (SKApp *app, XftFont *font, const char *s); +int sk_baseline (XftFont *font, int y, int h); +int sk_in_rect (int mx, int my, int x, int y, int w, int h); + +void sk_draw_btn (SKPainter *p, SKApp *app, XftFont *font, const SKBtn *b, + int hovered, unsigned long bg, unsigned long bghov); diff --git a/wm/src/draw.c b/wm/src/draw.c index ca7f04e..2ac307c 100644 --- a/wm/src/draw.c +++ b/wm/src/draw.c @@ -146,10 +146,7 @@ void frame_draw(WM *wm, Client *c) { xft_color(wm, c->focused ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &col_text); XRectangle clip = { BORDER_W + 4, 1, (unsigned short)text_max_w, TITLE_HEIGHT - 2 }; XftDrawSetClipRectangles(xd, 0, 0, &clip, 1); - XGlyphInfo ext; - XftTextExtentsUtf8(wm->dpy, wm->font_title, - (const FcChar8 *)c->title, strlen(c->title), &ext); - int ty = (TITLE_HEIGHT - ext.height) / 2 + ext.height - ext.y; + int ty = (TITLE_HEIGHT + wm->font_title->ascent - wm->font_title->descent) / 2; XftDrawStringUtf8(xd, &col_text, wm->font_title, BORDER_W + 6, ty, (const FcChar8 *)c->title, strlen(c->title)); diff --git a/wm/src/menu.c b/wm/src/menu.c index c91a3d2..6066907 100644 --- a/wm/src/menu.c +++ b/wm/src/menu.c @@ -7,7 +7,7 @@ static const MenuItem MENU_ITEMS[] = { { "App Launcher", "orionlauncher &" }, { "Terminal", "alacritty &" }, - { "File Manager", "thunar &" }, + { "File Manager", "rigel &" }, { "Text Editor", "mousepad &" }, { "──────────────────", NULL }, { "Web Browser", "firefox &" },