Using GtkListBox with AdwTabView

Thanks to Gio.ListModel it is surprisingly simple to use a Gtk.ListBox as a vertical list of tabs for an Adw.TabView.

The benefit over using a simple Gtk.Stack is that Adw.TabView supports standard shortcuts for switching view.

Here is a GJS example that should translate well to other languages. You can try this directly in Workbench.

// This snippet goes into Workbench UI Blueprint

using Gtk 4.0;
using Adw 1;

Box {
  Gtk.ScrolledWindow {
    width-request: 200;
    Gtk.ListBox list_box {
      selection-mode: browse;
    }
  }
   
  Gtk.Separator {}
  
  Adw.TabView tab_view {}
}
// This snippet goes into Workbench Code JavaScript

import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw?version=1";

import GObject from "gi://GObject";

Gtk.init();

const tab_view = workbench.builder.get_object("tab_view");
const list_box = workbench.builder.get_object("list_box");

// Create a binding between the Gtk.ListBox and the Adw.TabView

list_box.bind_model(
  tab_view.pages,
  // This function will be called for every new Adw.TabPage
  (tab_page) => {
    return buildTabRow(tab_page);
  },
);

list_box.connect("row-selected", (self, row) => {
  tab_view.set_selected_page(row.tab_page);
});

tab_view.connect("notify::selected-page", (self, page) => {
  list_box.select_row(tab_view.selected_page.list_box_row);
});

// Add some Adw.TabPage and let the binding do the rest

for (let i = 1; i < 11; i++) {
  const title = `hello ${i}`;
  const tab_page = tab_view.append(
    new Adw.StatusPage({
      title,
      hexpand: true,
    }),
  );
  tab_page.title = title;
}

// You probably should use a custom widget
// but this will do it for the purpose of this example

function buildTabRow(tab_page) {
  const list_box_row = new Gtk.ListBoxRow({
    selectable: true,
  });
  list_box_row.tab_page = tab_page;
  const label = new Gtk.Label();
  list_box_row.set_child(label);

  tab_page.list_box_row = list_box_row;

  tab_page.bind_property(
    "title",
    label,
    "label",
    GObject.BindingFlags.SYNC_CREATE,
  );

  return list_box_row;
}