Beneath Odysseus’s headerbar is a tabbar, and together they make up the entirety of it’s omnipresent chrome (other pieces show and hide as needed). Today I’ll be discussing how this tabbar works.
elementary styles these to fit together, though they’ve started experimenting with associating the tabbar with it’s content instead of the headerbar.
There are two layers to this: Gtk & elementary’s libgranite.
elementary starts by defining their own UI layout for tab labels, consisting of from left-to-right a:
- Close GtkButton (in a GtkRevealer)
The display of these may be adjusted to enter or leave a “pinned” mode, and there’s a right-click menu to trigger signals up to the containing GtkNotebook. Most of which can be triggered by variations of mouse-click.
Other events are also handled.
I reckon Odysseus might be the app which makes the fullest use of this.
Next they define a class which manages a list of the most recently closed tabs, but only a textual serialization of them, and renders them to a GtkMenu.
Then the Granite.DynamicNotebook wraps those as well as a Gtk.Notebook, adding some buttons to either side and a right-click menu. Leaving me (the embedding app) to implement handlers for the tabs’ menu options, etc appropriate to my Tab subclass.
It also enforces certain ordering/sizing constraints, and interprets more input events.
The GtkNotebook (GtkDynamicNotebook was a typo) has a vastly different CSS Gadget hierarchy to it’s widget hierarchy.
It’s CSS gadgets include:
- “stack” - holds the selected content
- “tabs” - within the header
- “arrows” - optional, within tabs for scrolling
- & one for each tab onscreen.
And it has extensive logic for sizing (based on Granite’s input), rendering, and especially event handling. In part because gadgets lack many of the same abstractions.
The state tracked by the GtkNotebook to offer this UI consists primarily of:
- A linked-list of all tabs.
- The scrolled-to tab
- The “current” tab
- The “detached”/dragged tab
Rearranging the tabs are done using the window manager’s drag & drop gesture, but only between windows in the same application. And if that gesture “fails” it’ll tell me to create a new window.
Though I’m failing where the logic is to shift tabs around the one being dragged.