Files
sovereign_browser_rust/plans/tab-bar-avatar.md
T

5.2 KiB
Raw Blame History

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), opens sovereign://profile in 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() with GTK_RELIEF_NORMAL (has border, unlike new-tab's RELIEF_NONE)
  • Image: avatar-default-symbolic at GTK_ICON_SIZE_BUTTON
  • valign = GTK_ALIGN_CENTER, margin_start = 4
  • size_request(28, 28) — fixed square, matches hamburger
  • Widget name: avatar-btn

Widget construction (hamburger, already ported):

  • gtk_menu_button_new(), image open-menu-symbolic at GTK_ICON_SIZE_BUTTON
  • size_request(28, 28), widget name hamburger-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:

  1. Scale so the smaller dimension fills 28px (bilinear) — object-fit: cover
  2. Center-crop to 28×28
  3. 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 picture from content JSON
  • Falls back to avatar-default-symbolic icon 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_add back 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 window
  • tab_manager_set_avatar(pubkey_hex: Option<&str>):
    • None/empty → reset all to avatar-default-symbolic
    • Query kind 0 via db::db_query_events(&[0], ...), parse picture from content
    • Spawn thread: download (reqwest blocking), make_fitted_pixbuf, then glib::idle_add to set pixbuf on all tracked images
  • 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(&notebook)

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(&notebook)

5. Avatar refresh triggers

  • main.rs / menu.rs do_login() after successful login: tab_manager_set_avatar(Some(&pubkey_hex))
  • relay_fetch.rs relay_fetch_thread() after fetch completes: schedule tab_manager_set_avatar on the main thread via glib::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

  1. cargo build
  2. Launch, sign in as WSB → avatar shows default icon immediately, updates to the profile picture after the relay fetch completes
  3. Visual check: avatar (tab bar, left) aligns with hamburger (toolbar, left) — same size, same corner radius, same left edge (margin_start 4)
  4. Click avatar → sovereign://profile opens in a new tab