5.2 KiB
Tab Bar Avatar — Port from C Implementation
C Implementation Reference (sovereign_browser/src/tab_manager.c)
Layout
The tab bar (GtkNotebook header) has two action widgets:
- Avatar button — far LEFT (
GTK_PACK_START), openssovereign://profilein a new tab - New-tab button — far RIGHT (
GTK_PACK_END), opens a new tab
The Alignment Recipe (the hard-won part)
The avatar must visually align with the hamburger button in the toolbar below it. Both are fixed 28×28 squares with 4px rounded corners:
Widget construction (avatar):
gtk_button_new()withGTK_RELIEF_NORMAL(has border, unlike new-tab'sRELIEF_NONE)- Image:
avatar-default-symbolicatGTK_ICON_SIZE_BUTTON valign = GTK_ALIGN_CENTER,margin_start = 4size_request(28, 28)— fixed square, matches hamburger- Widget name:
avatar-btn
Widget construction (hamburger, already ported):
gtk_menu_button_new(), imageopen-menu-symbolicatGTK_ICON_SIZE_BUTTONsize_request(28, 28), widget namehamburger-btn
CSS (applied at USER priority via add_provider_for_screen):
#avatar-btn, #hamburger-btn {
padding: 0px;
min-width: 28px; min-height: 28px;
border-radius: 4px;
}
#avatar-btn image, #hamburger-btn image {
padding: 0px; margin: 0px;
}
This kills GTK's default button padding so the 28×28 request is honored exactly, and zeroes the image's padding/margin so the icon centers precisely.
Avatar Image Processing (make_fitted_pixbuf)
The downloaded picture is processed to exactly fill the button:
- Scale so the smaller dimension fills 28px (bilinear) — object-fit: cover
- Center-crop to 28×28
- Round the corners with a cairo rounded-rect clip (radius 4, matching the button's border-radius) so the image is clipped to the button's shape
Fetch Flow
tab_manager_set_avatar(pubkey)— called after login AND after the relay bootstrap fetch completes (the kind 0 event may only exist then)- Queries kind 0 from SQLite, extracts
picturefrom content JSON - Falls back to
avatar-default-symbolicicon when: no pubkey, no kind 0, no picture field, or download failure - Background thread downloads (http/https via soup, file:// direct), processes, then
g_idle_addback to the main thread - Multi-window sync: all avatar images (main + auxiliary windows) are tracked in a global list and updated together
Current Rust State
- Main notebook (main.rs:513) has no action widgets at all — no new-tab button, no avatar
- Aux windows (tab_manager.rs:912) have only the new-tab button
- Hamburger button exists (tab_manager.rs:1344) with name
hamburger-btn, 28×28 - Theme CSS (tab_manager.rs:1530) has no avatar/hamburger sizing rules
- No avatar code exists
Implementation Plan
1. src/tab_manager.rs — avatar module
- Global
G_AVATAR_IMAGES: Mutex<Vec<gtk::Image>>(tracked across windows) setup_notebook_action_widgets(notebook)— adds avatar (Start) + new-tab (End); called for main notebook and each aux windowtab_manager_set_avatar(pubkey_hex: Option<&str>):- None/empty → reset all to
avatar-default-symbolic - Query kind 0 via
db::db_query_events(&[0], ...), parsepicturefrom content - Spawn thread: download (reqwest blocking),
make_fitted_pixbuf, thenglib::idle_addto set pixbuf on all tracked images
- None/empty → reset all to
make_fitted_pixbuf(src: &gdk_pixbuf::Pixbuf, size: i32) -> Option<Pixbuf>: cover-scale, center-crop, cairo rounded-corner clip (radius 4)- Avatar click →
tab_manager_open_internal("sovereign://profile")
2. src/tab_manager.rs — theme CSS
Add to tab_manager_apply_theme():
#avatar-btn, #hamburger-btn { padding: 0px; min-width: 28px; min-height: 28px; border-radius: 4px; }
#avatar-btn image, #hamburger-btn image { padding: 0px; margin: 0px; }
3. src/main.rs — wire up main notebook
After notebook creation (line ~517): call tab_manager::setup_notebook_action_widgets(¬ebook)
4. src/tab_manager.rs — wire up aux windows
In tab_manager_new_window(): replace the inline new-tab button with
setup_notebook_action_widgets(¬ebook)
5. Avatar refresh triggers
main.rs/menu.rsdo_login()after successful login:tab_manager_set_avatar(Some(&pubkey_hex))relay_fetch.rsrelay_fetch_thread()after fetch completes: scheduletab_manager_set_avataron the main thread viaglib::idle_add_once(the kind 0 event may only be in the DB after the fetch)
Widget Tree
flowchart TD
Window --> Paned
Paned --> Notebook
Notebook --> HeaderTabs[Notebook header]
HeaderTabs --> AvatarBtn[avatar-btn 28x28 PackType Start]
HeaderTabs --> Tabs[tab labels]
HeaderTabs --> NewTabBtn[new-tab btn PackType End]
Notebook --> TabPage[tab page Box]
TabPage --> Toolbar
Toolbar --> HamburgerBtn[hamburger-btn 28x28]
Toolbar --> UrlEntry[URL entry]
TabPage --> WebView
Verification
cargo build- Launch, sign in as WSB → avatar shows default icon immediately, updates to the profile picture after the relay fetch completes
- Visual check: avatar (tab bar, left) aligns with hamburger (toolbar, left) — same size, same corner radius, same left edge (margin_start 4)
- Click avatar → sovereign://profile opens in a new tab