How do I align tree labels in a ListView with TreeExpander?

I would like to have a tree-like view with labels left aligned for both branches and leaves. For example, see how “Recipes” is aligned with “Personal”:

Presumably, indent-for-icon expander property should help with this when the expander is present. However, I am getting this:

See how “B” in Branch is shifted to the right as compared to the “L” in “Leaf”

Here is my code:

class ItemNode(GObject.Object):
    def __init__(self, name, children=None):
        super().__init__()
        self.name = name
        self.children = Gio.ListStore(item_type=ItemNode)
        if children:
            for child in children:
                self.children.append(child)

class TopLevelSiblingsWindow(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_title("GTK 4 Top-Level Siblings")
        self.set_default_size(300, 250)

        # 1. Create the sub-children for our branch
        sub_leaf_a = ItemNode("Sub-leaf A")
        sub_leaf_b = ItemNode("Sub-leaf B")
        
        # 2. Create the two primary nodes
        branch = ItemNode("Branch Node (Sibling 1)", children=[sub_leaf_a, sub_leaf_b])
        leaf_1 = ItemNode("Leaf Node (Sibling 2)")

        # 3. Create a base ListStore and append BOTH items directly to it.
        # This forces them to sit side-by-side at the very top level.
        base_model = Gio.ListStore(item_type=ItemNode)
        base_model.append(branch)
        base_model.append(leaf_1)

        # 4. Wrap it in a TreeListModel.
        def create_model_func(item, *user_data):
            if item.children.get_n_items() > 0:
                return item.children
            return None

        # passthrough=False treats base_model as the literal starting layer
        tree_model = Gtk.TreeListModel.new(base_model, passthrough=False, autoexpand=False, create_func=create_model_func)

        # 5. UI Boilerplate (Selection, Factory, and ListView)
        selection_model = Gtk.SingleSelection.new(tree_model)

        factory = Gtk.SignalListItemFactory()
        factory.connect("setup", self.on_setup_item)
        factory.connect("bind", self.on_bind_item)

        list_view = Gtk.ListView(model=selection_model, factory=factory)

        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_child(list_view)
        self.set_child(scrolled_window)

    def on_setup_item(self, factory, list_item):
        expander = Gtk.TreeExpander(indent_for_icon=True)
        label = Gtk.Label(xalign=0)
        expander.set_child(label)
        list_item.set_child(expander)

    def on_bind_item(self, factory, list_item):
        tree_row = list_item.get_item() 
        item_node = tree_row.get_item()
        
        expander = list_item.get_child()
        label = expander.get_child()

        expander.set_list_row(tree_row)
        label.set_text(item_node.name)

Is it a bug or there is another way to align labels?

Hi,

It’s a feature, see theme: Halve indentation of trees (!6553) · Merge requests · GNOME / gtk · GitLab

For the alignment, you can load a small CSS snippet at application startup to restore the original indentation width:

provider = Gtk.CssProvider()
provider.load_from_string("treeexpander indent {-gtk-icon-size: 16px;}")
display = Gdk.Display.get_default()
Gtk.StyleContext.add_provider_for_display(display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)