How to detect if GtkBox overflows

I am creating a custom toolbar widget, similar to GtkToolbar in GTK3. However I am not sure how to detect if the GtkBox (that contains all the toolbar items) overflows, so I can make a button visible that show a popover containing the overflowed items.

Problem I’m coming across is that in the size_allocate vfunc, the width of the button is zero when its not visible obviously. If I try making it visible, its still zero. So if the toolbar box overflows and the button is currently not visible, I don’t know what width to subtract from the total width to allocate for the toolbar widget.

Any tips? Thanks.

Hi. You’re probably asking about GTK 4, so the gtk3 tag is a misnomer, right?

GtkBox can not overflow (modulo bugs), it will request enough size to always contain all of it children. It’s just not designed to be used for your purpose.

To implement something akin to a toolbar that hides items that don’t fit, you want a custom container, not a box. When measured, it should output a small minimum width (just enough to fit the overflow indicator), but a full natural width (so it can fit all the children). When allocated, it should allocate as many children as fit given the provided width, and if that wasn’t all of them, display the overflow indicator.

Here’s a sketch of what the implementation might look like:

class MyToolbar : Gtk.Widget {
    public override void measure (Gtk.Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
        // Set initial values.
        minimum = 0;
        natural = 0;
        minimum_baseline = -1;
        natural_baseline = -1;
        // Don't attempt to distribute horizontal space...
        int child_for_size = (orientation == HORIZONTAL) ? -1 : for_size;
        // Iterate over children.
        for (Gtk.Widget child = get_first_child (); child != null; child = child.get_next_sibling ()) {
            // Skip invisible children.
            if (!child.should_layout ()) {
                continue;
            }
            // Measure the child.
            int child_min, child_nat;
            child.measure (orientation, child_for_size, out child_min, out child_nat, null, null);
            if (orientation == HORIZONTAL) {
                minimum += child_min;
                natural += child_nat;
            } else {
                minimum = int.max (minimum, child_min);
                natural = int.max (natural, child_nat);
            }
        }

        // Our minimum width should allow us to either fit all of our children without overflow, or at least the overflow indicator.
        if (orientation == HORIZONTAL) {
            int overflow_indicator_width = 42; // FIXME
            minimum = int.min (minimum, overflow_indicator_width);
        }
    }

    protected override void size_allocate (int width, int height, int baseline) {
        int offset = 0;
        bool overflow = false;
        // Iterate over children.
        for (Gtk.Widget child = get_first_child (); child != null; child = child.get_next_sibling ()) {
            // Skip invisible children.
            if (!child.should_layout ()) {
                continue;
            }
            // If we already encountered an overflow, hide the remaining children.
            if (overflow) {
                child.set_child_visible (false);
                continue;
            }
            // Measure the child horizontally, for the allocated height.
            int child_width;
            child.measure (HORIZONTAL, height, null, out child_width, null, null);
            // Can we fit this child horizontally?
            // TODO: consider shrinking children down to their minimum widths first.
            if (offset + child_width <= width) {
                // It fits, allocate it.
               Gtk.Allocation allocation;
               if (get_direction () == LTR) {
                   allocation = { offset, 0, child_width, height };
               } else {
                   allocation = { width - offset - child_width, 0, child_width, height };
               }
               child.set_child_visible (true);
               child.allocate_size (allocation, baseline);
               offset += child_width;
            } else {
                // It does not fit, remember that there was an overflow, and hide children starting from this one.
                overflow = true;
                child.set_child_visible (false);
            }
        }

        // If we could not fit all the children, display the overflow indicator/button.
        if (overflow) {
            // ...
        }
    }
}

For a more complete implementation, also implement Gtk.Orientable (so you could have vertical toolbars), and perhaps attempt to shrink children down to their minimum sizes before overflowing (use Gtk.distribute_natural_allocation ()).

Also the sketch above allocates the children in one go, so by the time it detects there being an overflow, it might be already out of space to display the overflow indicator. It should do two passes, one to establish whether an overflow indicator is needed, and another one to actually allocate, already accounting for the space taken up by the indicator, if needed.