JBrowser Source Docs
You are reading the documentation for JBrowser 1.6.2. The newest release is 2.0.3. Go to the latest documentation

User interface overview

Every pixel JBrowser draws is a Qt widget, most of them custom-painted in paintEvent with QPainter rather than styled with style sheets. That is what gives JBrowser its Windows 11 look over a translucent backdrop.

The main window#

MainWindow (ui/window.py):

MainWindow (QMainWindow, translucent, custom frame)
└── RootWidget                      paints the backdrop wash (colour tint or incognito black)
    ├── Sidebar                     favourites, spaces, the space's cards, tools
    └── right column
        ├── TitleBar                navigation, the address pill, tools, window buttons
        ├── FavoritesBar            the bookmarks bar (Ctrl+Shift+B)
        └── SpaceStack              one Canvas per space, sliding between them
            └── Canvas → WebCard    one card per tab: header, web view, info bars, find bar
            └── Gallery             the overlay grid of every card (Ctrl+Shift+G)
    overlays: LazyToolbar · HotkeySheet · ToastManager · Onboarding

The constructor also creates the BrowserController, installs WindowHooks as ctx.hooks, registers every command and installs the shortcuts, and connects the lifecycle signals to the cards.

The window frame#

The window has no native title bar but keeps every native behaviour. NativeFrame in platform/win.py handles WM_NCCALCSIZE (the whole window is client area), WM_NCHITTEST (resize edges, the caption, and HTMAXBUTTON over the maximise button so Windows 11 shows the Snap Layouts flyout), rounded corners and the drop shadow. apply_backdrop() asks DWM for Mica, Mica Alt or Acrylic (DWMWA_SYSTEMBACKDROP_TYPE); translucent pixels in the window then show the backdrop.

Changed in 1.6.2 One owner for each window's look. DWM draws the material light or dark according to the window's DWMWA_USE_IMMERSIVE_DARK_MODE, and more than one party sets it: JBrowser, and Qt, which re-applies its own idea of light/dark to every window frame when the application palette or colour scheme changes (QWindowsWindow::windowEvent, ApplicationPaletteChange; Qt judges by the colour scheme and the palette at that moment). 1.6.0 reacted to that by reading the attribute back, rebuilding the material and refreshing the frame on activation, and the main window switched JBrowser between its translucent and solid look whenever a DWM call failed. On some PCs those reactions fed each other: pale windows, a stale rectangle over the title bar, a caption that stopped dragging (a frame refresh during activation interrupts a drag), black or white flashes and a frozen app (a restyle loop). ui/backdrop.py replaces all of it:

  • Backdrop(window, kind) owns one top-level window: "window" (frameless windows with a material: the main window and ChromeWindows), "popup" (the Archive and every QMenu: Acrylic over the whole window) or "frame" (dialogs with a native title bar: the mode and the caption colours). It applies the material and the mode when they change, and refreshes the frame once per native window so WM_NCCALCSIZE takes over.
  • After anything that can disturb the mode (palette changes, activation, window state changes, WinIdChange, and Windows settings broadcasts that NativeFrame reports through on_system_change), it sets the mode again, coalesced into one deferred call. Setting the same value changes nothing on screen, so nothing can loop. It never reads the state back, never rebuilds the material and never restyles the app.
  • Theme.translucent depends only on the setting and win.backdrop_supported() (Windows 11, or Windows 10 with pywinstyles), never on a single DWM call.
  • Theme.backdrop_layers(): translucent windows paint JBrowser's own base colour (backdrop_base, about 55–60 % opaque) and then the tint over the backdrop, so a window stays dark or light whatever DWM draws behind it.
  • Theme.refresh() applies the new palette before telling Qt the colour scheme (_request_scheme()), so Qt's re-application finds the matching palette.
  • caption_hit() does the caption hit test for both window kinds and never raises: an exception there used to make the window procedure fall back to "client area", and the window stopped dragging.
  • Menu actions (menu_action()) run after the menu has closed, outside QMenu.exec()'s event loop.

The window's event filter#

MainWindow.eventFilter() sees events for the whole application. It implements the behaviours that cut across widgets: Alt + mouse wheel pans the canvas from anywhere, pointing at the left edge peeks the hidden sidebar, mouse back/forward buttons navigate the card under the pointer, and clicking inside a card focuses it. Alt + drag anywhere on a card moves it. It also gives menus and tooltips native rounded corners.

How widgets talk#

  • Read from ctx.state, Tab models and ctx.settings, and connect to their signals.
  • Act through ui (the BrowserController) or ctx.commands.run(id). Never call another widget.
  • Draw with the theme's tokens (theme().c("text2"), theme().surface("card")), never hard-coded colours, and repaint on theme().changed.
  • Animate with motion() (motion).

Overlays#

Full-window modal layers derive from Overlay in ui/widgets.py: a scrim over the window, a panel from panel_rect(), Escape and clicks outside close it, focus returns where it was. The Lazy Toolbar and the shortcut sheet are overlays. Transient messages use ToastManager.show(text, glyph, duration_ms).

Reusable widgets#

ui/widgets.py holds the custom-painted controls: IconButton (with badge and progress ring), ToggleSwitch, Spinner, ProgressLine, ElidedLabel, InfoBarWidget, Toast, helpers such as menu_action(), and the colour-tint picker TintPicker. Icons are glyphs from the Segoe Fluent Icons font (ui/icons.py): icon("settings") returns a QIcon whose colour follows the theme, and draw_glyph() paints one directly.

Accessibility#

  • Custom widgets set accessible names and descriptions (setAccessibleName), and keyboard focus is always visible.
  • Everything is reachable from the keyboard; the command registry makes every action searchable.
  • Fluid animations off removes every animation; on first run it follows the Windows "Animation effects" setting.

Theming, the canvas and cards, the sidebar, the Lazy Toolbar, the Gallery.