Phase 4: SkyKit library + Rigel file manager; fix text centering

SkyKit (skykit/):
- Shared palette constants, font setup, and Xlib/Xft drawing utilities
- sk_fill, sk_rect, sk_line, sk_text, sk_text_clip, sk_textw, sk_baseline
- Builds as libskykit.a, consumed by Rigel and future shell components

Rigel (rigel/):
- Native C/Xlib file manager with SkyKit
- Sidebar with 8 bookmarks (Home, Desktop, Documents, Downloads, etc.)
- Editable path bar (Ctrl+L or '/' to activate, Enter to navigate)
- Sortable columns: Name / Size / Modified (click header to sort/reverse)
- Colored type badges: DIR, IMG, SND, VID, ZIP, SRC, PDF, TXT, EXE
- Double-click to open dirs or files (xdg-open)
- Keyboard: ↑↓ select, Enter open, Backspace back, Ctrl+H toggle hidden
- Right-click menu updated: "File Manager" now launches rigel

Text centering fix (deskbar + wm):
- Root cause: draw_str added glyph-specific ext.height/ext.y offset, and
  all callers used hardcoded (H-10)/2 magic numbers instead of font metrics
- Fix: draw_str now takes y as a direct baseline; str_baseline() computes
  (strip_y + strip_h + ascent - descent) / 2 for consistent centering
- WM titlebar: same fix using font_title->ascent/descent
- Affects: "Orion ▾" button, clock, date, app list, flyout menu items,
  letter icons, and all window titlebars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-23 15:40:03 -04:00
1 parent e3bc761f25
commit 1340bf012c
11 files changed
+1113 -18

No files matched your search

+3
View File
@@ -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
+11 -2
View File
@@ -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/
+14 -11
View File
@@ -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);
}
}
+4
View File
@@ -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..."
+21
View File
@@ -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
+832
View File
@@ -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 <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <fontconfig/fontconfig.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#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;
}
+22
View File
@@ -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
+113
View File
@@ -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);
}
+91
View File
@@ -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 <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <fontconfig/fontconfig.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* ── 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);
+1 -4
View File
@@ -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));
+1 -1
View File
@@ -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 &" },