From 4854ab85aa4ad396b9d158b04bb0748d81861e9e Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:35:23 -0400 Subject: [PATCH] Redesign launcher: modern dark UI, 4-col grid, pill filters, footer - 640x490 window, larger cards (132x106), 4 columns - Full-width search bar in header with active-state indicator - Category filters as pill chips with gaps - Cards show surface bg + accent top bar on hover - Footer bar with app count and keyboard hints - Fix menu: pavucontrol -> alsamixer in xterm Co-Authored-By: Claude Sonnet 4.6 --- launcher/src/launcher.c | 244 ++++++++++++++++++++++++---------------- wm/src/menu.c | 2 +- 2 files changed, 145 insertions(+), 101 deletions(-) diff --git a/launcher/src/launcher.c b/launcher/src/launcher.c index c0338a3..8ac1822 100644 --- a/launcher/src/launcher.c +++ b/launcher/src/launcher.c @@ -11,51 +11,55 @@ #include /* ── dimensions ─────────────────────────────────────────────────────── */ -#define WIN_W 580 -#define WIN_H 420 -#define HDR_H 50 -#define TAB_H 34 -#define CONTENT_Y (HDR_H + TAB_H) -#define PAD 12 -#define CARD_W 96 -#define CARD_H 84 -#define CARD_GAP 8 -#define COLS 5 -#define ICON_SZ 38 +#define WIN_W 640 +#define WIN_H 490 +#define HDR_H 58 +#define PILLS_H 42 +#define CONTENT_Y (HDR_H + PILLS_H) +#define FOOTER_H 28 +#define CARD_W 132 +#define CARD_H 106 +#define CARD_GAP 10 +#define COLS 4 +#define ICON_SZ 46 +#define PILL_GAP 8 +#define ROWS_VIS 3 #define GX0 ((WIN_W - (COLS * CARD_W + (COLS-1) * CARD_GAP)) / 2) -#define GY0 (CONTENT_Y + PAD) -#define ROWS_VIS 3 /* rows visible before scrolling */ +#define GY0 (CONTENT_Y + 12) #define MAX_APPS 256 /* ── palette ─────────────────────────────────────────────────────────── */ -#define C_BG 0x0E1220 -#define C_HDR 0x090C18 -#define C_BORDER 0x192236 -#define C_ACCENT 0x2258B0 -#define C_TAB_SEL 0x141E30 -#define C_CARD_HOV 0x131C2E -#define C_CARD_BD 0x1E2E48 -#define C_SEARCH 0x131A28 -#define C_SEARCH_BD 0x1C2C44 -#define C_TEXT 0x8AAAC8 -#define C_TEXT_HOV 0xE8F0FF -#define C_TEXT_DIM 0x344A62 -#define C_TAB_TXT 0x486070 -#define C_TAB_TSEL 0xA8C4E8 -#define C_SCROLL_BG 0x0C1020 -#define C_SCROLL_TH 0x1E3258 +#define C_BG 0x111318 +#define C_SURFACE 0x1A1D28 +#define C_SURFACE_HV 0x1F2335 +#define C_BORDER 0x252A42 +#define C_BORDER_HV 0x3A5490 +#define C_ACCENT 0x4B8FF0 +#define C_PILL_SEL 0x19305C +#define C_PILL_BD 0x2A4480 +#define C_SRCH_BG 0x161820 +#define C_SRCH_BD 0x262C48 +#define C_TEXT 0xC0CFF0 +#define C_TEXT_HOV 0xEEF2FF +#define C_TEXT_DIM 0x3A4068 +#define C_TEXT_CAT 0x586090 +#define C_TEXT_CAT_S 0x8AAEE8 +#define C_FOOTER_BG 0x0C0E14 +#define C_FOOTER_TXT 0x353C62 +#define C_SCROLL_BG 0x161820 +#define C_SCROLL_TH 0x2C3460 static const unsigned long ICON_BG[] = { - 0x1A4D88, 0x166C3C, 0x6C3018, - 0x4A1A80, 0x126070, 0x6C1A38, - 0x365218, 0x183460, + 0x1A3A88, 0x0F6040, 0x7A2820, 0x501A80, + 0x106070, 0x6A1A3A, 0x384C18, 0x1A3060, + 0x6A4010, 0x103A60, }; -#define NICON 8 +#define NICON 10 #define FONT_UI "Sans:size=9" -#define FONT_LOGO "Sans:size=11:weight=bold" -#define FONT_ICON "Sans:size=13:weight=bold" +#define FONT_ICON "Sans:size=14:weight=bold" +#define FONT_HINT "Sans:size=8" #define MAX_FLT 64 typedef struct { char name[64]; char cmd[256]; char cat[32]; char icon[64]; } App; @@ -171,14 +175,13 @@ static void load_apps(void) { load_desktop_dir(p); } - /* fallback if no .desktop files (dev environment, macOS, etc.) */ if (NAPP == 0) { static const struct { const char *n, *c, *cat; } FB[] = { - { "Terminal", "xterm &", "System" }, - { "Files", "pcmanfm &", "System" }, - { "Text Editor", "mousepad &", "System" }, - { "System Mon", "xterm -e htop &", "System" }, - { "Display", "arandr &", "Settings" }, + { "Terminal", "xterm &", "System" }, + { "Files", "pcmanfm &", "System" }, + { "Text Editor", "mousepad &", "System" }, + { "System Mon", "xterm -e htop &", "System" }, + { "Display", "arandr &", "Settings" }, }; for (int i = 0; i < (int)(sizeof(FB)/sizeof(FB[0])); i++) { snprintf(APPS[i].name, 64, "%s", FB[i].n); @@ -196,7 +199,7 @@ typedef struct { Display *dpy; int screen; Window root, win; GC gc; Colormap cmap; Visual *visual; - XftFont *font_ui, *font_logo, *font_icon; + XftFont *font_ui, *font_icon, *font_hint; int sel_cat, hov, scroll; char filter[MAX_FLT]; int filter_len; @@ -257,28 +260,28 @@ static int nvis(L *l, int out[MAX_APPS]) { return n; } -static void tab_layout(L *l, int widths[NCAT], int *total) { +static void pill_layout(L *l, int widths[NCAT], int *total) { *total = 0; for (int i = 0; i < NCAT; i++) { XGlyphInfo e; XftTextExtentsUtf8(l->dpy, l->font_ui, (FcChar8*)CATS[i], strlen(CATS[i]), &e); - widths[i] = (int)e.width + 22; + widths[i] = (int)e.width + 24; *total += widths[i]; } + *total += (NCAT - 1) * PILL_GAP; } static int tab_at(L *l, int x, int y) { if (y < HDR_H || y >= CONTENT_Y) return -1; - int w[NCAT], total; tab_layout(l, w, &total); + int w[NCAT], total; pill_layout(l, w, &total); int tx = (WIN_W - total) / 2; for (int i = 0; i < NCAT; i++) { if (x >= tx && x < tx + w[i]) return i; - tx += w[i]; + tx += w[i] + PILL_GAP; } return -1; } -/* card index within visible slice (adjusted for scroll) */ static int card_at_vis(int x, int y) { if (y < CONTENT_Y) return -1; int lx = x - GX0, ly = y - GY0; @@ -328,60 +331,68 @@ found:; static void draw(L *l) { XftDraw *xd = XftDrawCreate(l->dpy, l->win, l->visual, l->cmap); + /* background */ XSetForeground(l->dpy, l->gc, mkc(l, C_BG)); XFillRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W, WIN_H); - /* header */ - XSetForeground(l->dpy, l->gc, mkc(l, C_HDR)); + /* ── header: search bar ─────────────────────────── */ + XSetForeground(l->dpy, l->gc, mkc(l, C_SURFACE)); XFillRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W, HDR_H); - XSetForeground(l->dpy, l->gc, mkc(l, C_ACCENT)); - XFillRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W, 2); - ds(l, xd, l->font_logo, C_ACCENT, 14, 13, "OrionOS"); - - /* search box */ + /* search input box */ { - int sx = 86, sy = 10, sw = WIN_W - 86 - 14, sh = 30; - XSetForeground(l->dpy, l->gc, mkc(l, C_SEARCH)); + int sx = 16, sy = 11, sw = WIN_W - 32, sh = 36; + XSetForeground(l->dpy, l->gc, mkc(l, C_SRCH_BG)); XFillRectangle(l->dpy, l->win, l->gc, sx, sy, sw, sh); - XSetForeground(l->dpy, l->gc, mkc(l, C_SEARCH_BD)); - XDrawRectangle(l->dpy, l->win, l->gc, sx, sy, sw, sh); + XSetForeground(l->dpy, l->gc, mkc(l, + l->filter_len > 0 ? C_BORDER_HV : C_SRCH_BD)); + XDrawRectangle(l->dpy, l->win, l->gc, sx, sy, sw - 1, sh - 1); + + /* accent left bar when active */ + if (l->filter_len > 0) { + XSetForeground(l->dpy, l->gc, mkc(l, C_ACCENT)); + XFillRectangle(l->dpy, l->win, l->gc, sx, sy, 2, sh); + } + if (l->filter_len == 0) { - ds(l, xd, l->font_ui, C_TEXT_DIM, sx + 8, sy + 7, "Search apps..."); + ds(l, xd, l->font_ui, C_TEXT_DIM, sx + 12, sy + 10, "Search applications..."); } else { char buf[MAX_FLT + 2]; - snprintf(buf, sizeof(buf), "%s|", l->filter); - ds(l, xd, l->font_ui, C_TEXT_HOV, sx + 8, sy + 7, buf); + snprintf(buf, sizeof(buf), "%s_", l->filter); + ds(l, xd, l->font_ui, C_TEXT_HOV, sx + 12, sy + 10, buf); } } + /* header bottom separator */ XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER)); - XDrawLine(l->dpy, l->win, l->gc, 0, HDR_H, WIN_W, HDR_H); + XDrawLine(l->dpy, l->win, l->gc, 0, HDR_H - 1, WIN_W, HDR_H - 1); + + /* ── pills row ───────────────────────────────────── */ + int pw[NCAT], ptotal; pill_layout(l, pw, &ptotal); + int px = (WIN_W - ptotal) / 2; + int py = HDR_H + (PILLS_H - 24) / 2; - /* category tabs */ - int tw[NCAT], ttotal; tab_layout(l, tw, &ttotal); - int tx = (WIN_W - ttotal) / 2; for (int i = 0; i < NCAT; i++) { if (i == l->sel_cat) { - XSetForeground(l->dpy, l->gc, mkc(l, C_TAB_SEL)); - XFillRectangle(l->dpy, l->win, l->gc, tx, HDR_H + 1, tw[i], TAB_H - 1); - XSetForeground(l->dpy, l->gc, mkc(l, C_ACCENT)); - XFillRectangle(l->dpy, l->win, l->gc, tx + 5, HDR_H + TAB_H - 2, tw[i] - 10, 2); + XSetForeground(l->dpy, l->gc, mkc(l, C_PILL_SEL)); + XFillRectangle(l->dpy, l->win, l->gc, px, py, pw[i], 24); + XSetForeground(l->dpy, l->gc, mkc(l, C_PILL_BD)); + XDrawRectangle(l->dpy, l->win, l->gc, px, py, pw[i] - 1, 23); + ds_ctr(l, xd, l->font_ui, C_TEXT_CAT_S, px, py, pw[i], 24, CATS[i]); + } else { + ds_ctr(l, xd, l->font_ui, C_TEXT_CAT, px, py, pw[i], 24, CATS[i]); } - ds_ctr(l, xd, l->font_ui, - (i == l->sel_cat) ? C_TAB_TSEL : C_TAB_TXT, - tx, HDR_H, tw[i], TAB_H, CATS[i]); - tx += tw[i]; + px += pw[i] + PILL_GAP; } + /* pills bottom separator */ XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER)); - XDrawLine(l->dpy, l->win, l->gc, 0, CONTENT_Y, WIN_W, CONTENT_Y); + XDrawLine(l->dpy, l->win, l->gc, 0, CONTENT_Y - 1, WIN_W, CONTENT_Y - 1); - /* app grid with scroll */ + /* ── app grid ────────────────────────────────────── */ int vis[MAX_APPS]; int nv = nvis(l, vis); - /* clamp scroll */ int max_scroll = (nv - ROWS_VIS * COLS + COLS - 1) / COLS; if (max_scroll < 0) max_scroll = 0; if (l->scroll > max_scroll) l->scroll = max_scroll; @@ -391,7 +402,8 @@ static void draw(L *l) { if (nv == 0) { ds_ctr(l, xd, l->font_ui, C_TEXT_DIM, - 0, CONTENT_Y, WIN_W, WIN_H - CONTENT_Y, "No apps found"); + 0, CONTENT_Y, WIN_W, WIN_H - CONTENT_Y - FOOTER_H, + "No matching apps"); } else { for (int i = 0; i < ROWS_VIS * COLS; i++) { int vi = vis_start + i; @@ -402,18 +414,29 @@ static void draw(L *l) { int cy = GY0 + row * (CARD_H + CARD_GAP); int hover = (i == l->hov); + /* card background */ if (hover) { - XSetForeground(l->dpy, l->gc, mkc(l, C_CARD_HOV)); + XSetForeground(l->dpy, l->gc, mkc(l, C_SURFACE_HV)); XFillRectangle(l->dpy, l->win, l->gc, cx, cy, CARD_W, CARD_H); - XSetForeground(l->dpy, l->gc, mkc(l, C_CARD_BD)); + XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER_HV)); + XDrawRectangle(l->dpy, l->win, l->gc, cx, cy, CARD_W - 1, CARD_H - 1); + /* accent top bar on hover */ + XSetForeground(l->dpy, l->gc, mkc(l, C_ACCENT)); + XFillRectangle(l->dpy, l->win, l->gc, cx + 1, cy, CARD_W - 2, 2); + } else { + XSetForeground(l->dpy, l->gc, mkc(l, C_SURFACE)); + XFillRectangle(l->dpy, l->win, l->gc, cx, cy, CARD_W, CARD_H); + XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER)); XDrawRectangle(l->dpy, l->win, l->gc, cx, cy, CARD_W - 1, CARD_H - 1); } int app_i = vis[vi]; unsigned int h = 0; for (const char *p = APPS[app_i].name; *p; p++) h = h * 31 + (unsigned char)*p; + + /* icon */ int ix = cx + (CARD_W - ICON_SZ) / 2; - int iy = cy + 10; + int iy = cy + 14; int icon_rendered = 0; #ifdef HAVE_IMLIB2 icon_rendered = render_icon(l, APPS[app_i].icon, ix, iy, ICON_SZ); @@ -421,34 +444,57 @@ static void draw(L *l) { if (!icon_rendered) { XSetForeground(l->dpy, l->gc, mkc(l, ICON_BG[h % NICON])); XFillRectangle(l->dpy, l->win, l->gc, ix, iy, ICON_SZ, ICON_SZ); + /* 1px darker border on icon box */ + XSetForeground(l->dpy, l->gc, mkc(l, 0x000000)); + XDrawRectangle(l->dpy, l->win, l->gc, ix, iy, ICON_SZ - 1, ICON_SZ - 1); char letter[2] = { APPS[app_i].name[0], '\0' }; if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32; - ds_ctr(l, xd, l->font_icon, 0xEAF0FF, ix, iy, ICON_SZ, ICON_SZ, letter); + ds_ctr(l, xd, l->font_icon, 0xF0F4FF, + ix, iy, ICON_SZ, ICON_SZ, letter); } - char lbl[14]; - snprintf(lbl, sizeof(lbl), "%.12s", APPS[app_i].name); + /* label */ + char lbl[16]; + snprintf(lbl, sizeof(lbl), "%.14s", APPS[app_i].name); ds_ctr(l, xd, l->font_ui, hover ? C_TEXT_HOV : C_TEXT, - cx, iy + ICON_SZ + 4, CARD_W, CARD_H - ICON_SZ - 18, lbl); + cx, iy + ICON_SZ + 6, CARD_W, CARD_H - ICON_SZ - 24, lbl); } } /* scrollbar */ if (max_scroll > 0) { - int sb_x = WIN_W - 8, sb_y = CONTENT_Y + 4; - int sb_h = WIN_H - CONTENT_Y - 8; + int sb_x = WIN_W - 6; + int sb_y = CONTENT_Y + 4; + int sb_h = WIN_H - CONTENT_Y - FOOTER_H - 8; XSetForeground(l->dpy, l->gc, mkc(l, C_SCROLL_BG)); - XFillRectangle(l->dpy, l->win, l->gc, sb_x, sb_y, 6, sb_h); + XFillRectangle(l->dpy, l->win, l->gc, sb_x, sb_y, 4, sb_h); int total_rows = (nv + COLS - 1) / COLS; int thumb_h = sb_h * ROWS_VIS / total_rows; - if (thumb_h < 12) thumb_h = 12; + if (thumb_h < 10) thumb_h = 10; int thumb_y = sb_y + (sb_h - thumb_h) * l->scroll / max_scroll; XSetForeground(l->dpy, l->gc, mkc(l, C_SCROLL_TH)); - XFillRectangle(l->dpy, l->win, l->gc, sb_x + 1, thumb_y, 4, thumb_h); + XFillRectangle(l->dpy, l->win, l->gc, sb_x, thumb_y, 4, thumb_h); } - /* outer border */ + /* ── footer ──────────────────────────────────────── */ + int fy = WIN_H - FOOTER_H; + XSetForeground(l->dpy, l->gc, mkc(l, C_FOOTER_BG)); + XFillRectangle(l->dpy, l->win, l->gc, 0, fy, WIN_W, FOOTER_H); + XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER)); + XDrawLine(l->dpy, l->win, l->gc, 0, fy, WIN_W, fy); + + /* app count left, hints right */ + { + char cnt[32]; + snprintf(cnt, sizeof(cnt), "%d apps", nv); + ds(l, xd, l->font_hint, C_FOOTER_TXT, 14, fy + 7, cnt); + ds_ctr(l, xd, l->font_hint, C_FOOTER_TXT, + 0, fy, WIN_W, FOOTER_H, + "Return: launch Esc: close Type: filter"); + } + + /* outer window border */ XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER)); XDrawRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W - 1, WIN_H - 1); @@ -478,7 +524,6 @@ static void on_key(L *l, XKeyEvent *e) { l->filter[--l->filter_len] = '\0'; l->scroll = 0; l->hov = -1; draw(l); return; } - /* page up/down for keyboard scroll */ if (sym == XK_Prior) { l->scroll -= ROWS_VIS; if (l->scroll < 0) l->scroll = 0; draw(l); return; } if (sym == XK_Next) { l->scroll += ROWS_VIS; draw(l); return; } @@ -506,11 +551,11 @@ int main(void) { l.visual = DefaultVisual(l.dpy, l.screen); l.font_ui = XftFontOpenName(l.dpy, l.screen, FONT_UI); - l.font_logo = XftFontOpenName(l.dpy, l.screen, FONT_LOGO); l.font_icon = XftFontOpenName(l.dpy, l.screen, FONT_ICON); + l.font_hint = XftFontOpenName(l.dpy, l.screen, FONT_HINT); if (!l.font_ui) l.font_ui = XftFontOpenName(l.dpy, l.screen, "fixed:size=9"); - if (!l.font_logo) l.font_logo = l.font_ui; if (!l.font_icon) l.font_icon = l.font_ui; + if (!l.font_hint) l.font_hint = l.font_ui; int sw = DisplayWidth(l.dpy, l.screen); int sh = DisplayHeight(l.dpy, l.screen); @@ -540,14 +585,13 @@ int main(void) { if (e.xexpose.count == 0) draw(&l); break; case ButtonPress: + if (e.xbutton.button == Button4) { + l.scroll -= 1; if (l.scroll < 0) l.scroll = 0; draw(&l); break; + } + if (e.xbutton.button == Button5) { + l.scroll += 1; draw(&l); break; + } { - /* scroll wheel */ - if (e.xbutton.button == Button4) { - l.scroll -= 1; if (l.scroll < 0) l.scroll = 0; draw(&l); break; - } - if (e.xbutton.button == Button5) { - l.scroll += 1; draw(&l); break; - } int t = tab_at(&l, e.xbutton.x, e.xbutton.y); if (t >= 0) { l.sel_cat = t; l.scroll = 0; l.hov = -1; draw(&l); diff --git a/wm/src/menu.c b/wm/src/menu.c index 852b7ac..8961cda 100644 --- a/wm/src/menu.c +++ b/wm/src/menu.c @@ -12,7 +12,7 @@ static const MenuItem MENU_ITEMS[] = { { "──────────────────", NULL }, { "Web Browser", "firefox &" }, { "System Monitor", "xterm -T 'System Monitor' -e htop &" }, - { "Volume Control", "pavucontrol &" }, + { "Volume Control", "xterm -e alsamixer &" }, { "Software Updates", "orionpkg &" }, { "Display Settings", "arandr &" }, { "──────────────────", NULL },