From f296ee31ad7e21cdc1ffb9bda3cba7db15069eea Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Mar 2026 02:44:07 +0000 Subject: [PATCH 1/3] Replace FABs with single dropdown menu button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the 4 floating action buttons from the titlebar and replace with a single compact dropdown trigger (▾) that opens a combined menu showing all categories (File, Edit, Search, View) with section headers. Reduce titlebar height from 40px to 30px so tabs and title move up. https://claude.ai/code/session_013uWpT9xBhNcN2PtRU4XotZ --- prose_code.h | 9 ++-- render.c | 146 ++++++++++++++++++++++++++++----------------------- wndproc.c | 142 ++++++++++++++++++++++--------------------------- 3 files changed, 145 insertions(+), 152 deletions(-) diff --git a/prose_code.h b/prose_code.h index 2b52b98..eaaee01 100644 --- a/prose_code.h +++ b/prose_code.h @@ -111,7 +111,7 @@ typedef struct { #define CLR_SEARCH_HL (g_theme.search_hl) /* ── Layout constants ── */ -#define TITLEBAR_H 40 +#define TITLEBAR_H 30 #define TABBAR_H 38 #define MENUBAR_H 0 #define STATUSBAR_H 30 @@ -333,11 +333,10 @@ typedef struct { wchar_t autosave_dir[MAX_PATH]; unsigned int next_autosave_id; - int menu_open; - int menu_hover_item; - int menu_bar_widths[MENU_COUNT]; + int menu_open; /* -1 = closed, 0 = combined dropdown open */ + int menu_hover_item; /* flat index across all menus */ - int fab_hover; + int dropdown_hover; /* 1 if hovering the dropdown trigger button */ int spellcheck_enabled; diff --git a/render.c b/render.c index 764adc0..78141a6 100644 --- a/render.c +++ b/render.c @@ -25,44 +25,31 @@ void render_titlebar(HDC hdc) { int th = DPI(TITLEBAR_H); fill_rect(hdc, 0, 0, g_editor.client_w, th, CLR_BG_DARK); - SelectObject(hdc, g_editor.font_title); SetBkMode(hdc, TRANSPARENT); - static const wchar_t *fab_icons[MENU_COUNT] = { - L"\x2630", L"\x270E", L"\x2315", L"\x2699", - }; - - int fab_size = DPI(28); - int fab_pad = DPI(6); - int fab_start_x = DPI(8); - int fab_y = (th - fab_size) / 2; - - SelectObject(hdc, g_editor.font_ui_small); - for (int i = 0; i < MENU_COUNT; i++) { - int fx = fab_start_x + i * (fab_size + fab_pad); - - COLORREF fab_bg; - if (g_editor.menu_open == i) { - fab_bg = CLR_ACCENT; - } else if (g_editor.fab_hover == i) { - fab_bg = g_theme.is_dark ? RGB(50, 50, 62) : RGB(220, 220, 225); - } else { - fab_bg = g_theme.is_dark ? RGB(38, 38, 46) : RGB(235, 235, 238); - } - fill_rounded_rect(hdc, fx, fab_y, fab_size, fab_size, fab_size / 2, fab_bg); - - COLORREF icon_clr = (g_editor.menu_open == i) ? - (g_theme.is_dark ? RGB(255, 255, 255) : RGB(255, 255, 255)) : CLR_SUBTEXT; - SetTextColor(hdc, icon_clr); - RECT fr = { fx, fab_y, fx + fab_size, fab_y + fab_size }; - DrawTextW(hdc, fab_icons[i], 1, &fr, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + /* Dropdown trigger button (small down arrow) */ + int btn_size = DPI(22); + int btn_x = DPI(8); + int btn_y = (th - btn_size) / 2; - g_editor.menu_bar_widths[i] = fab_size + fab_pad; + COLORREF btn_bg; + if (g_editor.menu_open >= 0) { + btn_bg = CLR_ACCENT; + } else if (g_editor.dropdown_hover) { + btn_bg = g_theme.is_dark ? RGB(50, 50, 62) : RGB(220, 220, 225); + } else { + btn_bg = g_theme.is_dark ? RGB(38, 38, 46) : RGB(235, 235, 238); } + fill_rounded_rect(hdc, btn_x, btn_y, btn_size, btn_size, DPI(4), btn_bg); - int fab_total_w = MENU_COUNT * (fab_size + fab_pad); - int title_x = fab_start_x + fab_total_w + DPI(12); + SelectObject(hdc, g_editor.font_ui_small); + COLORREF icon_clr = (g_editor.menu_open >= 0) ? RGB(255, 255, 255) : CLR_SUBTEXT; + RECT br = { btn_x, btn_y, btn_x + btn_size, btn_y + btn_size }; + SetTextColor(hdc, icon_clr); + DrawTextW(hdc, L"\x25BE", 1, &br, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + /* Document title */ + int title_x = btn_x + btn_size + DPI(10); SelectObject(hdc, g_editor.font_title); Document *doc = current_doc(); if (doc) { @@ -71,6 +58,7 @@ void render_titlebar(HDC hdc) { draw_text(hdc, title_x, (th - DPI(16)) / 2, title, (int)wcslen(title), CLR_SUBTEXT); } + /* Window control buttons */ int bw = DPI(46), bh = th; int x = g_editor.client_w - bw * 3; @@ -952,31 +940,35 @@ void render_menubar(HDC hdc) { } void render_menu_dropdown(HDC hdc) { - if (g_editor.menu_open < 0 || g_editor.menu_open >= MENU_COUNT) return; + if (g_editor.menu_open < 0) return; - const MenuDef *menu = &g_menus[g_editor.menu_open]; int item_h = DPI(26); int sep_h = DPI(9); + int header_h = DPI(24); int dropdown_w = DPI(260); int pad_x = DPI(12); - int fab_size = DPI(28); - int fab_pad = DPI(6); - int fab_start_x = DPI(8); - - int dropdown_x = fab_start_x + g_editor.menu_open * (fab_size + fab_pad); + int dropdown_x = DPI(8); int dropdown_y = DPI(TITLEBAR_H); + /* Calculate total height across all menus */ int total_h = DPI(4); - for (int i = 0; i < menu->item_count; i++) { - total_h += (menu->items[i].id == MENU_ID_SEP) ? sep_h : item_h; + for (int m = 0; m < MENU_COUNT; m++) { + if (m > 0) total_h += sep_h; /* separator between categories */ + total_h += header_h; /* category header */ + for (int i = 0; i < g_menus[m].item_count; i++) { + total_h += (g_menus[m].items[i].id == MENU_ID_SEP) ? sep_h : item_h; + } } total_h += DPI(4); + /* Shadow */ fill_rounded_rect(hdc, dropdown_x + DPI(2), dropdown_y + DPI(2), dropdown_w, total_h, DPI(8), RGB(0, 0, 0)); + /* Background */ fill_rounded_rect(hdc, dropdown_x, dropdown_y, dropdown_w, total_h, DPI(8), g_theme.is_dark ? RGB(34, 34, 42) : RGB(248, 248, 248)); + /* Border */ HPEN pen = CreatePen(PS_SOLID, 1, CLR_SURFACE1); HBRUSH hollow = (HBRUSH)GetStockObject(HOLLOW_BRUSH); HPEN old_pen = (HPEN)SelectObject(hdc, pen); @@ -991,40 +983,60 @@ void render_menu_dropdown(HDC hdc) { SetBkMode(hdc, TRANSPARENT); int cy = dropdown_y + DPI(4); - for (int i = 0; i < menu->item_count; i++) { - const MenuItem *item = &menu->items[i]; + int flat_idx = 0; + + for (int m = 0; m < MENU_COUNT; m++) { + const MenuDef *menu = &g_menus[m]; - if (item->id == MENU_ID_SEP) { + /* Category separator (between groups, not before the first) */ + if (m > 0) { int line_y = cy + sep_h / 2; fill_rect(hdc, dropdown_x + pad_x, line_y, dropdown_w - pad_x * 2, 1, CLR_SURFACE0); cy += sep_h; - continue; } - if (g_editor.menu_hover_item == i) { - fill_rounded_rect(hdc, dropdown_x + DPI(4), cy, dropdown_w - DPI(8), item_h, - DPI(4), CLR_ACCENT); - SetTextColor(hdc, g_theme.is_dark ? RGB(255, 255, 255) : RGB(255, 255, 255)); - } + /* Category header */ + SelectObject(hdc, g_editor.font_title); + draw_text(hdc, dropdown_x + pad_x, cy + (header_h - DPI(12)) / 2, + menu->label, (int)wcslen(menu->label), CLR_OVERLAY0); + SelectObject(hdc, g_editor.font_ui_small); + cy += header_h; + + /* Menu items */ + for (int i = 0; i < menu->item_count; i++) { + const MenuItem *item = &menu->items[i]; + + if (item->id == MENU_ID_SEP) { + int line_y = cy + sep_h / 2; + fill_rect(hdc, dropdown_x + pad_x, line_y, dropdown_w - pad_x * 2, 1, CLR_SURFACE0); + cy += sep_h; + continue; + } - COLORREF label_clr = (g_editor.menu_hover_item == i) - ? (g_theme.is_dark ? RGB(255, 255, 255) : RGB(255, 255, 255)) - : CLR_TEXT; - draw_text(hdc, dropdown_x + pad_x, cy + (item_h - DPI(12)) / 2, - item->label, (int)wcslen(item->label), label_clr); - - if (item->shortcut) { - SIZE ssz; - GetTextExtentPoint32W(hdc, item->shortcut, (int)wcslen(item->shortcut), &ssz); - COLORREF sc_clr = (g_editor.menu_hover_item == i) - ? (g_theme.is_dark ? RGB(205, 210, 230) : RGB(220, 230, 255)) - : CLR_OVERLAY0; - draw_text(hdc, dropdown_x + dropdown_w - pad_x - ssz.cx, - cy + (item_h - DPI(12)) / 2, - item->shortcut, (int)wcslen(item->shortcut), sc_clr); - } + if (g_editor.menu_hover_item == flat_idx) { + fill_rounded_rect(hdc, dropdown_x + DPI(4), cy, dropdown_w - DPI(8), item_h, + DPI(4), CLR_ACCENT); + } - cy += item_h; + COLORREF label_clr = (g_editor.menu_hover_item == flat_idx) + ? RGB(255, 255, 255) : CLR_TEXT; + draw_text(hdc, dropdown_x + pad_x, cy + (item_h - DPI(12)) / 2, + item->label, (int)wcslen(item->label), label_clr); + + if (item->shortcut) { + SIZE ssz; + GetTextExtentPoint32W(hdc, item->shortcut, (int)wcslen(item->shortcut), &ssz); + COLORREF sc_clr = (g_editor.menu_hover_item == flat_idx) + ? (g_theme.is_dark ? RGB(205, 210, 230) : RGB(220, 230, 255)) + : CLR_OVERLAY0; + draw_text(hdc, dropdown_x + dropdown_w - pad_x - ssz.cx, + cy + (item_h - DPI(12)) / 2, + item->shortcut, (int)wcslen(item->shortcut), sc_clr); + } + + flat_idx++; + cy += item_h; + } } } diff --git a/wndproc.c b/wndproc.c index 1c2497a..647a17b 100644 --- a/wndproc.c +++ b/wndproc.c @@ -227,7 +227,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { g_editor.font_size = FONT_SIZE_DEFAULT; g_editor.menu_open = -1; g_editor.menu_hover_item = -1; - g_editor.fab_hover = -1; + g_editor.dropdown_hover = 0; g_editor.spellcheck_enabled = 1; /* spell check on by default */ g_editor.font_main = CreateFontW( @@ -616,29 +616,21 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } - /* Check FAB clicks (LEFT SIDE of titlebar) */ - int fab_size = DPI(28); - int fab_pad = DPI(6); - int fab_start_x = DPI(8); - int fab_y = (DPI(TITLEBAR_H) - fab_size) / 2; - int fab_total_w = MENU_COUNT * (fab_size + fab_pad); - - if (mx >= fab_start_x && mx < fab_start_x + fab_total_w && - my >= fab_y && my < fab_y + fab_size) { - for (int i = 0; i < MENU_COUNT; i++) { - int fx = fab_start_x + i * (fab_size + fab_pad); - if (mx >= fx && mx < fx + fab_size) { - /* Toggle this menu open/closed */ - if (g_editor.menu_open == i) { - g_editor.menu_open = -1; - } else { - g_editor.menu_open = i; - g_editor.menu_hover_item = -1; - } - InvalidateRect(hwnd, NULL, FALSE); - return 0; - } + /* Check dropdown trigger button click */ + int btn_size = DPI(22); + int btn_x = DPI(8); + int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; + + if (mx >= btn_x && mx < btn_x + btn_size && + my >= btn_y && my < btn_y + btn_size) { + if (g_editor.menu_open >= 0) { + g_editor.menu_open = -1; + } else { + g_editor.menu_open = 0; + g_editor.menu_hover_item = -1; } + InvalidateRect(hwnd, NULL, FALSE); + return 0; } /* Drag to move (anywhere else in titlebar) */ @@ -649,40 +641,43 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } - /* Menu dropdown click — MUST be checked before tab bar since + /* Combined dropdown click — MUST be checked before tab bar since * the dropdown overlaps the tab bar's y-range */ if (g_editor.menu_open >= 0) { - const MenuDef *menu = &g_menus[g_editor.menu_open]; int item_h = DPI(26); int sep_h = DPI(9); + int header_h = DPI(24); int dropdown_w = DPI(260); - - /* FAB-based dropdown position (LEFT SIDE) */ - int fab_size = DPI(28); - int fab_pad = DPI(6); - int fab_start_x = DPI(8); - int dropdown_x = fab_start_x + g_editor.menu_open * (fab_size + fab_pad); + int dropdown_x = DPI(8); int dropdown_y = DPI(TITLEBAR_H); /* Calculate total dropdown height */ int total_dd_h = DPI(4); - for (int i = 0; i < menu->item_count; i++) - total_dd_h += (menu->items[i].id == MENU_ID_SEP) ? sep_h : item_h; + for (int m = 0; m < MENU_COUNT; m++) { + if (m > 0) total_dd_h += sep_h; + total_dd_h += header_h; + for (int i = 0; i < g_menus[m].item_count; i++) + total_dd_h += (g_menus[m].items[i].id == MENU_ID_SEP) ? sep_h : item_h; + } total_dd_h += DPI(4); if (mx >= dropdown_x && mx < dropdown_x + dropdown_w && my >= dropdown_y && my < dropdown_y + total_dd_h) { int cy = dropdown_y + DPI(4); - for (int i = 0; i < menu->item_count; i++) { - int h = (menu->items[i].id == MENU_ID_SEP) ? sep_h : item_h; - if (menu->items[i].id != MENU_ID_SEP && - my >= cy && my < cy + h) { - menu_execute(menu->items[i].id); - return 0; + for (int m = 0; m < MENU_COUNT; m++) { + if (m > 0) cy += sep_h; + cy += header_h; /* skip category header */ + for (int i = 0; i < g_menus[m].item_count; i++) { + int h = (g_menus[m].items[i].id == MENU_ID_SEP) ? sep_h : item_h; + if (g_menus[m].items[i].id != MENU_ID_SEP && + my >= cy && my < cy + h) { + menu_execute(g_menus[m].items[i].id); + return 0; + } + cy += h; } - cy += h; } - /* Clicked inside dropdown but on nothing (e.g. separator) */ + /* Clicked inside dropdown but on nothing (header/separator) */ return 0; } /* Clicked outside dropdown — close it */ @@ -863,61 +858,48 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { int mx = GET_X_LPARAM(lParam); int my = GET_Y_LPARAM(lParam); - /* FAB hover tracking in titlebar (LEFT SIDE) */ - int old_fab_hover = g_editor.fab_hover; - g_editor.fab_hover = -1; + /* Dropdown trigger button hover tracking */ + int old_dropdown_hover = g_editor.dropdown_hover; + g_editor.dropdown_hover = 0; if (my < DPI(TITLEBAR_H)) { - int fab_size = DPI(28); - int fab_pad = DPI(6); - int fab_start_x = DPI(8); - int fab_total_w = MENU_COUNT * (fab_size + fab_pad); - int fab_y = (DPI(TITLEBAR_H) - fab_size) / 2; - - if (mx >= fab_start_x && mx < fab_start_x + fab_total_w && - my >= fab_y && my < fab_y + fab_size) { - for (int i = 0; i < MENU_COUNT; i++) { - int fx = fab_start_x + i * (fab_size + fab_pad); - if (mx >= fx && mx < fx + fab_size) { - g_editor.fab_hover = i; - /* If a menu is open and we hover a different FAB, switch to it */ - if (g_editor.menu_open >= 0 && g_editor.menu_open != i) { - g_editor.menu_open = i; - g_editor.menu_hover_item = -1; - } - break; - } - } + int btn_size = DPI(22); + int btn_x = DPI(8); + int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; + if (mx >= btn_x && mx < btn_x + btn_size && + my >= btn_y && my < btn_y + btn_size) { + g_editor.dropdown_hover = 1; } } - if (old_fab_hover != g_editor.fab_hover) + if (old_dropdown_hover != g_editor.dropdown_hover) InvalidateRect(hwnd, NULL, FALSE); - /* Menu dropdown hover tracking */ + /* Combined dropdown hover tracking */ if (g_editor.menu_open >= 0) { - const MenuDef *menu = &g_menus[g_editor.menu_open]; int item_h = DPI(26); int sep_h = DPI(9); + int header_h = DPI(24); int dropdown_w = DPI(260); - - /* FAB-based dropdown position (LEFT SIDE) */ - int fab_size = DPI(28); - int fab_pad = DPI(6); - int fab_start_x = DPI(8); - int dropdown_x = fab_start_x + g_editor.menu_open * (fab_size + fab_pad); + int dropdown_x = DPI(8); int dropdown_y = DPI(TITLEBAR_H); int old_hover = g_editor.menu_hover_item; g_editor.menu_hover_item = -1; if (mx >= dropdown_x && mx < dropdown_x + dropdown_w) { int cy = dropdown_y + DPI(4); - for (int i = 0; i < menu->item_count; i++) { - int h = (menu->items[i].id == MENU_ID_SEP) ? sep_h : item_h; - if (menu->items[i].id != MENU_ID_SEP && - my >= cy && my < cy + h) { - g_editor.menu_hover_item = i; - break; + int flat_idx = 0; + for (int m = 0; m < MENU_COUNT; m++) { + if (m > 0) cy += sep_h; + cy += header_h; + for (int i = 0; i < g_menus[m].item_count; i++) { + int h = (g_menus[m].items[i].id == MENU_ID_SEP) ? sep_h : item_h; + if (g_menus[m].items[i].id != MENU_ID_SEP) { + if (my >= cy && my < cy + h) { + g_editor.menu_hover_item = flat_idx; + } + flat_idx++; + } + cy += h; } - cy += h; } } if (old_hover != g_editor.menu_hover_item) From d930db5cc5153bbcb3bf4f45eaa2d1648ca618ef Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Mar 2026 02:49:47 +0000 Subject: [PATCH 2/3] Merge titlebar and tabbar into a single row Set TABBAR_H to 0 and increase TITLEBAR_H to 36. Tabs are now rendered inline in the titlebar after the dropdown button, with tab click handling moved into the titlebar region. The separate render_tabbar() is now a no-op. This puts the dropdown icon, tabs, and window controls all on one line, eliminating the second row. https://claude.ai/code/session_013uWpT9xBhNcN2PtRU4XotZ --- prose_code.h | 4 +-- render.c | 75 ++++++++++++++++++++++++---------------------------- wndproc.c | 71 +++++++++++++++++++++++++------------------------ 3 files changed, 73 insertions(+), 77 deletions(-) diff --git a/prose_code.h b/prose_code.h index eaaee01..f155b99 100644 --- a/prose_code.h +++ b/prose_code.h @@ -111,8 +111,8 @@ typedef struct { #define CLR_SEARCH_HL (g_theme.search_hl) /* ── Layout constants ── */ -#define TITLEBAR_H 30 -#define TABBAR_H 38 +#define TITLEBAR_H 36 +#define TABBAR_H 0 #define MENUBAR_H 0 #define STATUSBAR_H 30 #define GUTTER_PAD 16 diff --git a/render.c b/render.c index 78141a6..7abdedb 100644 --- a/render.c +++ b/render.c @@ -29,7 +29,7 @@ void render_titlebar(HDC hdc) { /* Dropdown trigger button (small down arrow) */ int btn_size = DPI(22); - int btn_x = DPI(8); + int btn_x = DPI(6); int btn_y = (th - btn_size) / 2; COLORREF btn_bg; @@ -48,14 +48,37 @@ void render_titlebar(HDC hdc) { SetTextColor(hdc, icon_clr); DrawTextW(hdc, L"\x25BE", 1, &br, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - /* Document title */ - int title_x = btn_x + btn_size + DPI(10); - SelectObject(hdc, g_editor.font_title); - Document *doc = current_doc(); - if (doc) { - wchar_t title[256]; - swprintf(title, 256, L"%ls%ls", doc->title, doc->modified ? L" \x2022" : L""); - draw_text(hdc, title_x, (th - DPI(16)) / 2, title, (int)wcslen(title), CLR_SUBTEXT); + /* Tabs (inline, right after dropdown button) */ + int tx = btn_x + btn_size + DPI(8); + int right_limit = g_editor.client_w - DPI(46) * 3 - DPI(8); + SelectObject(hdc, g_editor.font_ui_small); + + for (int i = 0; i < g_editor.tab_count; i++) { + Document *doc = g_editor.tabs[i]; + wchar_t label[128]; + swprintf(label, 128, L"%ls%ls", doc->title, doc->modified ? L" \x2022" : L""); + int tw = (int)wcslen(label) * DPI(8) + DPI(TAB_PAD) * 2; + if (tw < DPI(TAB_MIN_W)) tw = DPI(TAB_MIN_W); + if (tw > DPI(TAB_MAX_W)) tw = DPI(TAB_MAX_W); + + if (tx + tw > right_limit) break; + + if (i == g_editor.active_tab) { + fill_rounded_rect(hdc, tx, DPI(5), tw, th - DPI(5), DPI(10), CLR_TAB_ACTIVE); + fill_rounded_rect(hdc, tx + DPI(12), th - DPI(3), tw - DPI(24), DPI(2), DPI(1), CLR_ACCENT); + draw_text(hdc, tx + DPI(TAB_PAD), (th - DPI(12)) / 2, label, (int)wcslen(label), CLR_TEXT); + } else { + draw_text(hdc, tx + DPI(TAB_PAD), (th - DPI(12)) / 2, label, (int)wcslen(label), CLR_OVERLAY0); + } + + draw_text(hdc, tx + tw - DPI(20), (th - DPI(12)) / 2, L"\x00D7", 1, CLR_OVERLAY0); + + tx += tw + DPI(4); + } + + /* New tab (+) button */ + if (tx + DPI(30) <= right_limit) { + draw_text(hdc, tx + DPI(8), (th - DPI(12)) / 2, L"+", 1, CLR_OVERLAY0); } /* Window control buttons */ @@ -86,38 +109,8 @@ void render_titlebar(HDC hdc) { } void render_tabbar(HDC hdc) { - int y = DPI(TITLEBAR_H + MENUBAR_H); - int tbh = DPI(TABBAR_H); - fill_rect(hdc, 0, y, g_editor.client_w, tbh, CLR_BG_DARK); - - SelectObject(hdc, g_editor.font_ui_small); - SetBkMode(hdc, TRANSPARENT); - - int x = DPI(8); - for (int i = 0; i < g_editor.tab_count; i++) { - Document *doc = g_editor.tabs[i]; - wchar_t label[128]; - swprintf(label, 128, L"%ls%ls", doc->title, doc->modified ? L" \x2022" : L""); - int tw = (int)wcslen(label) * DPI(8) + DPI(TAB_PAD) * 2; - if (tw < DPI(TAB_MIN_W)) tw = DPI(TAB_MIN_W); - if (tw > DPI(TAB_MAX_W)) tw = DPI(TAB_MAX_W); - - if (i == g_editor.active_tab) { - fill_rounded_rect(hdc, x, y + DPI(5), tw, tbh - DPI(5), DPI(10), CLR_TAB_ACTIVE); - fill_rounded_rect(hdc, x + DPI(12), y + tbh - DPI(3), tw - DPI(24), DPI(2), DPI(1), CLR_ACCENT); - draw_text(hdc, x + DPI(TAB_PAD), y + (tbh - DPI(12)) / 2, label, (int)wcslen(label), CLR_TEXT); - } else { - draw_text(hdc, x + DPI(TAB_PAD), y + (tbh - DPI(12)) / 2, label, (int)wcslen(label), CLR_OVERLAY0); - } - - draw_text(hdc, x + tw - DPI(20), y + (tbh - DPI(12)) / 2, L"\x00D7", 1, CLR_OVERLAY0); - - x += tw + DPI(4); - } - - draw_text(hdc, x + DPI(8), y + (tbh - DPI(12)) / 2, L"+", 1, CLR_OVERLAY0); - - fill_rect(hdc, 0, y + tbh - 1, g_editor.client_w, 1, CLR_SURFACE0); + /* Tabs are now rendered inline in the titlebar */ + (void)hdc; } void render_statusbar(HDC hdc) { diff --git a/wndproc.c b/wndproc.c index 647a17b..b197cc9 100644 --- a/wndproc.c +++ b/wndproc.c @@ -618,7 +618,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { /* Check dropdown trigger button click */ int btn_size = DPI(22); - int btn_x = DPI(8); + int btn_x = DPI(6); int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; if (mx >= btn_x && mx < btn_x + btn_size && @@ -633,6 +633,39 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } + /* Tab clicks (inline in titlebar) */ + { + int tx = btn_x + btn_size + DPI(8); + int right_limit = g_editor.client_w - DPI(46) * 3 - DPI(8); + for (int i = 0; i < g_editor.tab_count; i++) { + wchar_t label[128]; + swprintf(label, 128, L"%ls%ls", g_editor.tabs[i]->title, g_editor.tabs[i]->modified ? L" \x2022" : L""); + int tw = (int)wcslen(label) * DPI(8) + DPI(TAB_PAD) * 2; + if (tw < DPI(TAB_MIN_W)) tw = DPI(TAB_MIN_W); + if (tw > DPI(TAB_MAX_W)) tw = DPI(TAB_MAX_W); + + if (tx + tw > right_limit) break; + + if (mx >= tx && mx < tx + tw) { + if (mx >= tx + tw - DPI(24)) { + close_tab(i); + } else { + g_editor.active_tab = i; + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + tx += tw + DPI(4); + } + /* New tab button */ + if (tx + DPI(30) <= right_limit && + mx >= tx + DPI(4) && mx < tx + DPI(30)) { + new_tab(); + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + } + /* Drag to move (anywhere else in titlebar) */ g_editor.titlebar_dragging = 1; g_editor.drag_start.x = mx; @@ -641,8 +674,8 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } - /* Combined dropdown click — MUST be checked before tab bar since - * the dropdown overlaps the tab bar's y-range */ + /* Combined dropdown click — MUST be checked before editor area since + * the dropdown overlaps the editor's y-range */ if (g_editor.menu_open >= 0) { int item_h = DPI(26); int sep_h = DPI(9); @@ -687,36 +720,6 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { /* Fall through to handle the click on whatever was underneath */ } - /* Tab bar */ - if (my >= DPI(TITLEBAR_H + MENUBAR_H) && my < DPI(TITLEBAR_H + MENUBAR_H + TABBAR_H)) { - int tx = DPI(8); - for (int i = 0; i < g_editor.tab_count; i++) { - wchar_t label[128]; - swprintf(label, 128, L"%ls%ls", g_editor.tabs[i]->title, g_editor.tabs[i]->modified ? L" \x2022" : L""); - int tw = (int)wcslen(label) * DPI(8) + DPI(TAB_PAD) * 2; - if (tw < DPI(TAB_MIN_W)) tw = DPI(TAB_MIN_W); - if (tw > DPI(TAB_MAX_W)) tw = DPI(TAB_MAX_W); - - if (mx >= tx && mx < tx + tw) { - /* Check close button */ - if (mx >= tx + tw - DPI(24)) { - close_tab(i); - } else { - g_editor.active_tab = i; - } - InvalidateRect(hwnd, NULL, FALSE); - return 0; - } - tx += tw + DPI(4); - } - /* New tab button */ - if (mx >= tx + DPI(4) && mx < tx + DPI(30)) { - new_tab(); - InvalidateRect(hwnd, NULL, FALSE); - } - return 0; - } - /* Search bar close button */ if (g_editor.search.active) { int bar_h = g_editor.search.replace_active ? DPI(72) : DPI(40); @@ -863,7 +866,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { g_editor.dropdown_hover = 0; if (my < DPI(TITLEBAR_H)) { int btn_size = DPI(22); - int btn_x = DPI(8); + int btn_x = DPI(6); int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; if (mx >= btn_x && mx < btn_x + btn_size && my >= btn_y && my < btn_y + btn_size) { From c079c8db3d4add18d64dea30ff9ba757965d3d0c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Mar 2026 02:53:58 +0000 Subject: [PATCH 3/3] Move dropdown arrow after tabs/+, enlarge + and x icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder titlebar: tabs start from left edge, then (+) new tab button, then (▾) dropdown trigger. The + and × (close tab) icons now use font_ui instead of font_ui_small for better visibility. Dropdown button position is computed dynamically and stored in dropdown_btn_x so wndproc hit-testing stays in sync. The dropdown menu panel now appears below the trigger button wherever it ends up. https://claude.ai/code/session_013uWpT9xBhNcN2PtRU4XotZ --- prose_code.h | 1 + render.c | 60 +++++++++++++++++++++++++++++----------------------- wndproc.c | 46 +++++++++++++++++++++------------------- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/prose_code.h b/prose_code.h index f155b99..8df04c8 100644 --- a/prose_code.h +++ b/prose_code.h @@ -337,6 +337,7 @@ typedef struct { int menu_hover_item; /* flat index across all menus */ int dropdown_hover; /* 1 if hovering the dropdown trigger button */ + int dropdown_btn_x; /* computed x position of dropdown button */ int spellcheck_enabled; diff --git a/render.c b/render.c index 7abdedb..2883df4 100644 --- a/render.c +++ b/render.c @@ -27,29 +27,8 @@ void render_titlebar(HDC hdc) { SetBkMode(hdc, TRANSPARENT); - /* Dropdown trigger button (small down arrow) */ - int btn_size = DPI(22); - int btn_x = DPI(6); - int btn_y = (th - btn_size) / 2; - - COLORREF btn_bg; - if (g_editor.menu_open >= 0) { - btn_bg = CLR_ACCENT; - } else if (g_editor.dropdown_hover) { - btn_bg = g_theme.is_dark ? RGB(50, 50, 62) : RGB(220, 220, 225); - } else { - btn_bg = g_theme.is_dark ? RGB(38, 38, 46) : RGB(235, 235, 238); - } - fill_rounded_rect(hdc, btn_x, btn_y, btn_size, btn_size, DPI(4), btn_bg); - - SelectObject(hdc, g_editor.font_ui_small); - COLORREF icon_clr = (g_editor.menu_open >= 0) ? RGB(255, 255, 255) : CLR_SUBTEXT; - RECT br = { btn_x, btn_y, btn_x + btn_size, btn_y + btn_size }; - SetTextColor(hdc, icon_clr); - DrawTextW(hdc, L"\x25BE", 1, &br, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - /* Tabs (inline, right after dropdown button) */ - int tx = btn_x + btn_size + DPI(8); + /* Tabs (starting from left edge) */ + int tx = DPI(6); int right_limit = g_editor.client_w - DPI(46) * 3 - DPI(8); SelectObject(hdc, g_editor.font_ui_small); @@ -71,15 +50,42 @@ void render_titlebar(HDC hdc) { draw_text(hdc, tx + DPI(TAB_PAD), (th - DPI(12)) / 2, label, (int)wcslen(label), CLR_OVERLAY0); } - draw_text(hdc, tx + tw - DPI(20), (th - DPI(12)) / 2, L"\x00D7", 1, CLR_OVERLAY0); + /* Tab close (×) button — use main font for larger size */ + SelectObject(hdc, g_editor.font_ui); + draw_text(hdc, tx + tw - DPI(22), (th - DPI(16)) / 2, L"\x00D7", 1, CLR_OVERLAY0); + SelectObject(hdc, g_editor.font_ui_small); tx += tw + DPI(4); } - /* New tab (+) button */ + /* New tab (+) button — use main font for larger size */ if (tx + DPI(30) <= right_limit) { - draw_text(hdc, tx + DPI(8), (th - DPI(12)) / 2, L"+", 1, CLR_OVERLAY0); + SelectObject(hdc, g_editor.font_ui); + draw_text(hdc, tx + DPI(6), (th - DPI(16)) / 2, L"+", 1, CLR_OVERLAY0); + SelectObject(hdc, g_editor.font_ui_small); + } + + /* Dropdown trigger button (▾) — after (+) button */ + int btn_size = DPI(22); + int btn_x = tx + DPI(32); + int btn_y = (th - btn_size) / 2; + g_editor.dropdown_btn_x = btn_x; + + COLORREF btn_bg; + if (g_editor.menu_open >= 0) { + btn_bg = CLR_ACCENT; + } else if (g_editor.dropdown_hover) { + btn_bg = g_theme.is_dark ? RGB(50, 50, 62) : RGB(220, 220, 225); + } else { + btn_bg = g_theme.is_dark ? RGB(38, 38, 46) : RGB(235, 235, 238); } + fill_rounded_rect(hdc, btn_x, btn_y, btn_size, btn_size, DPI(4), btn_bg); + + SelectObject(hdc, g_editor.font_ui_small); + COLORREF icon_clr = (g_editor.menu_open >= 0) ? RGB(255, 255, 255) : CLR_SUBTEXT; + RECT br = { btn_x, btn_y, btn_x + btn_size, btn_y + btn_size }; + SetTextColor(hdc, icon_clr); + DrawTextW(hdc, L"\x25BE", 1, &br, DT_CENTER | DT_VCENTER | DT_SINGLELINE); /* Window control buttons */ int bw = DPI(46), bh = th; @@ -941,7 +947,7 @@ void render_menu_dropdown(HDC hdc) { int dropdown_w = DPI(260); int pad_x = DPI(12); - int dropdown_x = DPI(8); + int dropdown_x = g_editor.dropdown_btn_x; int dropdown_y = DPI(TITLEBAR_H); /* Calculate total height across all menus */ diff --git a/wndproc.c b/wndproc.c index b197cc9..65ac7d6 100644 --- a/wndproc.c +++ b/wndproc.c @@ -616,26 +616,9 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { return 0; } - /* Check dropdown trigger button click */ - int btn_size = DPI(22); - int btn_x = DPI(6); - int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; - - if (mx >= btn_x && mx < btn_x + btn_size && - my >= btn_y && my < btn_y + btn_size) { - if (g_editor.menu_open >= 0) { - g_editor.menu_open = -1; - } else { - g_editor.menu_open = 0; - g_editor.menu_hover_item = -1; - } - InvalidateRect(hwnd, NULL, FALSE); - return 0; - } - - /* Tab clicks (inline in titlebar) */ + /* Tab clicks (inline in titlebar, starting from left) */ { - int tx = btn_x + btn_size + DPI(8); + int tx = DPI(6); int right_limit = g_editor.client_w - DPI(46) * 3 - DPI(8); for (int i = 0; i < g_editor.tab_count; i++) { wchar_t label[128]; @@ -666,6 +649,25 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { } } + /* Check dropdown trigger button click (after tabs and +) */ + { + int btn_size = DPI(22); + int btn_x = g_editor.dropdown_btn_x; + int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; + + if (mx >= btn_x && mx < btn_x + btn_size && + my >= btn_y && my < btn_y + btn_size) { + if (g_editor.menu_open >= 0) { + g_editor.menu_open = -1; + } else { + g_editor.menu_open = 0; + g_editor.menu_hover_item = -1; + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + } + /* Drag to move (anywhere else in titlebar) */ g_editor.titlebar_dragging = 1; g_editor.drag_start.x = mx; @@ -681,7 +683,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { int sep_h = DPI(9); int header_h = DPI(24); int dropdown_w = DPI(260); - int dropdown_x = DPI(8); + int dropdown_x = g_editor.dropdown_btn_x; int dropdown_y = DPI(TITLEBAR_H); /* Calculate total dropdown height */ @@ -866,7 +868,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { g_editor.dropdown_hover = 0; if (my < DPI(TITLEBAR_H)) { int btn_size = DPI(22); - int btn_x = DPI(6); + int btn_x = g_editor.dropdown_btn_x; int btn_y = (DPI(TITLEBAR_H) - btn_size) / 2; if (mx >= btn_x && mx < btn_x + btn_size && my >= btn_y && my < btn_y + btn_size) { @@ -882,7 +884,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { int sep_h = DPI(9); int header_h = DPI(24); int dropdown_w = DPI(260); - int dropdown_x = DPI(8); + int dropdown_x = g_editor.dropdown_btn_x; int dropdown_y = DPI(TITLEBAR_H); int old_hover = g_editor.menu_hover_item;