Skip to content

Commit 2eae952

Browse files
Merge pull request #9 from asystemoffields/claude/fab-to-dropdown-menu-XpiX0
Consolidate tabs and menu into titlebar
2 parents f29ad5d + c079c8d commit 2eae952

3 files changed

Lines changed: 210 additions & 212 deletions

File tree

prose_code.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ typedef struct {
111111
#define CLR_SEARCH_HL (g_theme.search_hl)
112112

113113
/* ── Layout constants ── */
114-
#define TITLEBAR_H 40
115-
#define TABBAR_H 38
114+
#define TITLEBAR_H 36
115+
#define TABBAR_H 0
116116
#define MENUBAR_H 0
117117
#define STATUSBAR_H 30
118118
#define GUTTER_PAD 16
@@ -333,11 +333,11 @@ typedef struct {
333333
wchar_t autosave_dir[MAX_PATH];
334334
unsigned int next_autosave_id;
335335

336-
int menu_open;
337-
int menu_hover_item;
338-
int menu_bar_widths[MENU_COUNT];
336+
int menu_open; /* -1 = closed, 0 = combined dropdown open */
337+
int menu_hover_item; /* flat index across all menus */
339338

340-
int fab_hover;
339+
int dropdown_hover; /* 1 if hovering the dropdown trigger button */
340+
int dropdown_btn_x; /* computed x position of dropdown button */
341341

342342
int spellcheck_enabled;
343343

render.c

Lines changed: 111 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -25,52 +25,69 @@ void render_titlebar(HDC hdc) {
2525
int th = DPI(TITLEBAR_H);
2626
fill_rect(hdc, 0, 0, g_editor.client_w, th, CLR_BG_DARK);
2727

28-
SelectObject(hdc, g_editor.font_title);
2928
SetBkMode(hdc, TRANSPARENT);
3029

31-
static const wchar_t *fab_icons[MENU_COUNT] = {
32-
L"\x2630", L"\x270E", L"\x2315", L"\x2699",
33-
};
30+
/* Tabs (starting from left edge) */
31+
int tx = DPI(6);
32+
int right_limit = g_editor.client_w - DPI(46) * 3 - DPI(8);
33+
SelectObject(hdc, g_editor.font_ui_small);
3434

35-
int fab_size = DPI(28);
36-
int fab_pad = DPI(6);
37-
int fab_start_x = DPI(8);
38-
int fab_y = (th - fab_size) / 2;
35+
for (int i = 0; i < g_editor.tab_count; i++) {
36+
Document *doc = g_editor.tabs[i];
37+
wchar_t label[128];
38+
swprintf(label, 128, L"%ls%ls", doc->title, doc->modified ? L" \x2022" : L"");
39+
int tw = (int)wcslen(label) * DPI(8) + DPI(TAB_PAD) * 2;
40+
if (tw < DPI(TAB_MIN_W)) tw = DPI(TAB_MIN_W);
41+
if (tw > DPI(TAB_MAX_W)) tw = DPI(TAB_MAX_W);
3942

40-
SelectObject(hdc, g_editor.font_ui_small);
41-
for (int i = 0; i < MENU_COUNT; i++) {
42-
int fx = fab_start_x + i * (fab_size + fab_pad);
43-
44-
COLORREF fab_bg;
45-
if (g_editor.menu_open == i) {
46-
fab_bg = CLR_ACCENT;
47-
} else if (g_editor.fab_hover == i) {
48-
fab_bg = g_theme.is_dark ? RGB(50, 50, 62) : RGB(220, 220, 225);
43+
if (tx + tw > right_limit) break;
44+
45+
if (i == g_editor.active_tab) {
46+
fill_rounded_rect(hdc, tx, DPI(5), tw, th - DPI(5), DPI(10), CLR_TAB_ACTIVE);
47+
fill_rounded_rect(hdc, tx + DPI(12), th - DPI(3), tw - DPI(24), DPI(2), DPI(1), CLR_ACCENT);
48+
draw_text(hdc, tx + DPI(TAB_PAD), (th - DPI(12)) / 2, label, (int)wcslen(label), CLR_TEXT);
4949
} else {
50-
fab_bg = g_theme.is_dark ? RGB(38, 38, 46) : RGB(235, 235, 238);
50+
draw_text(hdc, tx + DPI(TAB_PAD), (th - DPI(12)) / 2, label, (int)wcslen(label), CLR_OVERLAY0);
5151
}
52-
fill_rounded_rect(hdc, fx, fab_y, fab_size, fab_size, fab_size / 2, fab_bg);
5352

54-
COLORREF icon_clr = (g_editor.menu_open == i) ?
55-
(g_theme.is_dark ? RGB(255, 255, 255) : RGB(255, 255, 255)) : CLR_SUBTEXT;
56-
SetTextColor(hdc, icon_clr);
57-
RECT fr = { fx, fab_y, fx + fab_size, fab_y + fab_size };
58-
DrawTextW(hdc, fab_icons[i], 1, &fr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
53+
/* Tab close (×) button — use main font for larger size */
54+
SelectObject(hdc, g_editor.font_ui);
55+
draw_text(hdc, tx + tw - DPI(22), (th - DPI(16)) / 2, L"\x00D7", 1, CLR_OVERLAY0);
56+
SelectObject(hdc, g_editor.font_ui_small);
5957

60-
g_editor.menu_bar_widths[i] = fab_size + fab_pad;
58+
tx += tw + DPI(4);
6159
}
6260

63-
int fab_total_w = MENU_COUNT * (fab_size + fab_pad);
64-
int title_x = fab_start_x + fab_total_w + DPI(12);
61+
/* New tab (+) button — use main font for larger size */
62+
if (tx + DPI(30) <= right_limit) {
63+
SelectObject(hdc, g_editor.font_ui);
64+
draw_text(hdc, tx + DPI(6), (th - DPI(16)) / 2, L"+", 1, CLR_OVERLAY0);
65+
SelectObject(hdc, g_editor.font_ui_small);
66+
}
6567

66-
SelectObject(hdc, g_editor.font_title);
67-
Document *doc = current_doc();
68-
if (doc) {
69-
wchar_t title[256];
70-
swprintf(title, 256, L"%ls%ls", doc->title, doc->modified ? L" \x2022" : L"");
71-
draw_text(hdc, title_x, (th - DPI(16)) / 2, title, (int)wcslen(title), CLR_SUBTEXT);
68+
/* Dropdown trigger button (▾) — after (+) button */
69+
int btn_size = DPI(22);
70+
int btn_x = tx + DPI(32);
71+
int btn_y = (th - btn_size) / 2;
72+
g_editor.dropdown_btn_x = btn_x;
73+
74+
COLORREF btn_bg;
75+
if (g_editor.menu_open >= 0) {
76+
btn_bg = CLR_ACCENT;
77+
} else if (g_editor.dropdown_hover) {
78+
btn_bg = g_theme.is_dark ? RGB(50, 50, 62) : RGB(220, 220, 225);
79+
} else {
80+
btn_bg = g_theme.is_dark ? RGB(38, 38, 46) : RGB(235, 235, 238);
7281
}
82+
fill_rounded_rect(hdc, btn_x, btn_y, btn_size, btn_size, DPI(4), btn_bg);
7383

84+
SelectObject(hdc, g_editor.font_ui_small);
85+
COLORREF icon_clr = (g_editor.menu_open >= 0) ? RGB(255, 255, 255) : CLR_SUBTEXT;
86+
RECT br = { btn_x, btn_y, btn_x + btn_size, btn_y + btn_size };
87+
SetTextColor(hdc, icon_clr);
88+
DrawTextW(hdc, L"\x25BE", 1, &br, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
89+
90+
/* Window control buttons */
7491
int bw = DPI(46), bh = th;
7592
int x = g_editor.client_w - bw * 3;
7693

@@ -98,38 +115,8 @@ void render_titlebar(HDC hdc) {
98115
}
99116

100117
void render_tabbar(HDC hdc) {
101-
int y = DPI(TITLEBAR_H + MENUBAR_H);
102-
int tbh = DPI(TABBAR_H);
103-
fill_rect(hdc, 0, y, g_editor.client_w, tbh, CLR_BG_DARK);
104-
105-
SelectObject(hdc, g_editor.font_ui_small);
106-
SetBkMode(hdc, TRANSPARENT);
107-
108-
int x = DPI(8);
109-
for (int i = 0; i < g_editor.tab_count; i++) {
110-
Document *doc = g_editor.tabs[i];
111-
wchar_t label[128];
112-
swprintf(label, 128, L"%ls%ls", doc->title, doc->modified ? L" \x2022" : L"");
113-
int tw = (int)wcslen(label) * DPI(8) + DPI(TAB_PAD) * 2;
114-
if (tw < DPI(TAB_MIN_W)) tw = DPI(TAB_MIN_W);
115-
if (tw > DPI(TAB_MAX_W)) tw = DPI(TAB_MAX_W);
116-
117-
if (i == g_editor.active_tab) {
118-
fill_rounded_rect(hdc, x, y + DPI(5), tw, tbh - DPI(5), DPI(10), CLR_TAB_ACTIVE);
119-
fill_rounded_rect(hdc, x + DPI(12), y + tbh - DPI(3), tw - DPI(24), DPI(2), DPI(1), CLR_ACCENT);
120-
draw_text(hdc, x + DPI(TAB_PAD), y + (tbh - DPI(12)) / 2, label, (int)wcslen(label), CLR_TEXT);
121-
} else {
122-
draw_text(hdc, x + DPI(TAB_PAD), y + (tbh - DPI(12)) / 2, label, (int)wcslen(label), CLR_OVERLAY0);
123-
}
124-
125-
draw_text(hdc, x + tw - DPI(20), y + (tbh - DPI(12)) / 2, L"\x00D7", 1, CLR_OVERLAY0);
126-
127-
x += tw + DPI(4);
128-
}
129-
130-
draw_text(hdc, x + DPI(8), y + (tbh - DPI(12)) / 2, L"+", 1, CLR_OVERLAY0);
131-
132-
fill_rect(hdc, 0, y + tbh - 1, g_editor.client_w, 1, CLR_SURFACE0);
118+
/* Tabs are now rendered inline in the titlebar */
119+
(void)hdc;
133120
}
134121

135122
void render_statusbar(HDC hdc) {
@@ -952,31 +939,35 @@ void render_menubar(HDC hdc) {
952939
}
953940

954941
void render_menu_dropdown(HDC hdc) {
955-
if (g_editor.menu_open < 0 || g_editor.menu_open >= MENU_COUNT) return;
942+
if (g_editor.menu_open < 0) return;
956943

957-
const MenuDef *menu = &g_menus[g_editor.menu_open];
958944
int item_h = DPI(26);
959945
int sep_h = DPI(9);
946+
int header_h = DPI(24);
960947
int dropdown_w = DPI(260);
961948
int pad_x = DPI(12);
962949

963-
int fab_size = DPI(28);
964-
int fab_pad = DPI(6);
965-
int fab_start_x = DPI(8);
966-
967-
int dropdown_x = fab_start_x + g_editor.menu_open * (fab_size + fab_pad);
950+
int dropdown_x = g_editor.dropdown_btn_x;
968951
int dropdown_y = DPI(TITLEBAR_H);
969952

953+
/* Calculate total height across all menus */
970954
int total_h = DPI(4);
971-
for (int i = 0; i < menu->item_count; i++) {
972-
total_h += (menu->items[i].id == MENU_ID_SEP) ? sep_h : item_h;
955+
for (int m = 0; m < MENU_COUNT; m++) {
956+
if (m > 0) total_h += sep_h; /* separator between categories */
957+
total_h += header_h; /* category header */
958+
for (int i = 0; i < g_menus[m].item_count; i++) {
959+
total_h += (g_menus[m].items[i].id == MENU_ID_SEP) ? sep_h : item_h;
960+
}
973961
}
974962
total_h += DPI(4);
975963

964+
/* Shadow */
976965
fill_rounded_rect(hdc, dropdown_x + DPI(2), dropdown_y + DPI(2),
977966
dropdown_w, total_h, DPI(8), RGB(0, 0, 0));
967+
/* Background */
978968
fill_rounded_rect(hdc, dropdown_x, dropdown_y, dropdown_w, total_h, DPI(8),
979969
g_theme.is_dark ? RGB(34, 34, 42) : RGB(248, 248, 248));
970+
/* Border */
980971
HPEN pen = CreatePen(PS_SOLID, 1, CLR_SURFACE1);
981972
HBRUSH hollow = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
982973
HPEN old_pen = (HPEN)SelectObject(hdc, pen);
@@ -991,40 +982,60 @@ void render_menu_dropdown(HDC hdc) {
991982
SetBkMode(hdc, TRANSPARENT);
992983

993984
int cy = dropdown_y + DPI(4);
994-
for (int i = 0; i < menu->item_count; i++) {
995-
const MenuItem *item = &menu->items[i];
985+
int flat_idx = 0;
996986

997-
if (item->id == MENU_ID_SEP) {
987+
for (int m = 0; m < MENU_COUNT; m++) {
988+
const MenuDef *menu = &g_menus[m];
989+
990+
/* Category separator (between groups, not before the first) */
991+
if (m > 0) {
998992
int line_y = cy + sep_h / 2;
999993
fill_rect(hdc, dropdown_x + pad_x, line_y, dropdown_w - pad_x * 2, 1, CLR_SURFACE0);
1000994
cy += sep_h;
1001-
continue;
1002995
}
1003996

1004-
if (g_editor.menu_hover_item == i) {
1005-
fill_rounded_rect(hdc, dropdown_x + DPI(4), cy, dropdown_w - DPI(8), item_h,
1006-
DPI(4), CLR_ACCENT);
1007-
SetTextColor(hdc, g_theme.is_dark ? RGB(255, 255, 255) : RGB(255, 255, 255));
1008-
}
997+
/* Category header */
998+
SelectObject(hdc, g_editor.font_title);
999+
draw_text(hdc, dropdown_x + pad_x, cy + (header_h - DPI(12)) / 2,
1000+
menu->label, (int)wcslen(menu->label), CLR_OVERLAY0);
1001+
SelectObject(hdc, g_editor.font_ui_small);
1002+
cy += header_h;
1003+
1004+
/* Menu items */
1005+
for (int i = 0; i < menu->item_count; i++) {
1006+
const MenuItem *item = &menu->items[i];
1007+
1008+
if (item->id == MENU_ID_SEP) {
1009+
int line_y = cy + sep_h / 2;
1010+
fill_rect(hdc, dropdown_x + pad_x, line_y, dropdown_w - pad_x * 2, 1, CLR_SURFACE0);
1011+
cy += sep_h;
1012+
continue;
1013+
}
10091014

1010-
COLORREF label_clr = (g_editor.menu_hover_item == i)
1011-
? (g_theme.is_dark ? RGB(255, 255, 255) : RGB(255, 255, 255))
1012-
: CLR_TEXT;
1013-
draw_text(hdc, dropdown_x + pad_x, cy + (item_h - DPI(12)) / 2,
1014-
item->label, (int)wcslen(item->label), label_clr);
1015-
1016-
if (item->shortcut) {
1017-
SIZE ssz;
1018-
GetTextExtentPoint32W(hdc, item->shortcut, (int)wcslen(item->shortcut), &ssz);
1019-
COLORREF sc_clr = (g_editor.menu_hover_item == i)
1020-
? (g_theme.is_dark ? RGB(205, 210, 230) : RGB(220, 230, 255))
1021-
: CLR_OVERLAY0;
1022-
draw_text(hdc, dropdown_x + dropdown_w - pad_x - ssz.cx,
1023-
cy + (item_h - DPI(12)) / 2,
1024-
item->shortcut, (int)wcslen(item->shortcut), sc_clr);
1025-
}
1015+
if (g_editor.menu_hover_item == flat_idx) {
1016+
fill_rounded_rect(hdc, dropdown_x + DPI(4), cy, dropdown_w - DPI(8), item_h,
1017+
DPI(4), CLR_ACCENT);
1018+
}
10261019

1027-
cy += item_h;
1020+
COLORREF label_clr = (g_editor.menu_hover_item == flat_idx)
1021+
? RGB(255, 255, 255) : CLR_TEXT;
1022+
draw_text(hdc, dropdown_x + pad_x, cy + (item_h - DPI(12)) / 2,
1023+
item->label, (int)wcslen(item->label), label_clr);
1024+
1025+
if (item->shortcut) {
1026+
SIZE ssz;
1027+
GetTextExtentPoint32W(hdc, item->shortcut, (int)wcslen(item->shortcut), &ssz);
1028+
COLORREF sc_clr = (g_editor.menu_hover_item == flat_idx)
1029+
? (g_theme.is_dark ? RGB(205, 210, 230) : RGB(220, 230, 255))
1030+
: CLR_OVERLAY0;
1031+
draw_text(hdc, dropdown_x + dropdown_w - pad_x - ssz.cx,
1032+
cy + (item_h - DPI(12)) / 2,
1033+
item->shortcut, (int)wcslen(item->shortcut), sc_clr);
1034+
}
1035+
1036+
flat_idx++;
1037+
cy += item_h;
1038+
}
10281039
}
10291040
}
10301041

0 commit comments

Comments
 (0)