Correct way to implement fit-to-width widget?

I needed some fit-to-width widget so GtkPicture inside a AdwPreferenceRow doesn’t get crushed into a dot when I resize the window vertically.

So I wrote this code (gtk-rs, Rust), and it works well.

use glib::Object;
use gtk::glib;

glib::wrapper! {
    pub struct FitToWidthWidget(ObjectSubclass<imp::FitToWidthWidget>)
        @extends gtk::Widget,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}

impl FitToWidthWidget {
    pub fn new() -> Self {
        Object::builder().build()
    }
}

mod imp {
    use adw::subclass::prelude::*;
    use gtk::prelude::*;
    use gtk::{SizeRequestMode, glib};

    #[derive(Default)]
    pub struct FitToWidthWidget;

    #[glib::object_subclass]
    impl ObjectSubclass for FitToWidthWidget {
        const NAME: &'static str = "SceFitToWidthWidget";
        type Type = super::FitToWidthWidget;
        type ParentType = gtk::Widget;
    }

    impl WidgetImpl for FitToWidthWidget {
        fn request_mode(&self) -> SizeRequestMode {
            SizeRequestMode::WidthForHeight
        }

        fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
            let child = self.obj().first_child().unwrap();
            let (min, nat, min_baseline, nat_baseline) = child.measure(orientation, for_size);
            if orientation == gtk::Orientation::Vertical {
                // return natural size as minimum size
                return (nat, nat, min_baseline, nat_baseline);
            }

            (min, nat, min_baseline, nat_baseline)
        }

        fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
            let child = self.obj().first_child().unwrap();
            child.allocate(width, height, baseline, None);
        }
    }

    impl ObjectImpl for FitToWidthWidget {
        fn dispose(&self) {
            let child = self.obj().first_child().unwrap();
            child.unparent();
        }
    }
}

But I got these warnings on Windows (G_ENABLE_DEBUG is turned off on my Linux distro)

(program.exe:73640): Gtk-WARNING **: 01:11:28.263: Widget reports min height of 195 for width of 467, but min width of 0 for height of 191
(program.exe:73640): Gtk-WARNING **: 01:11:28.263: Widget reports min height of 219 for width of 491, but min width of 24 for height of 215
(program.exe:73640): Gtk-WARNING **: 01:11:28.264: Widget reports min height of 224 for width of 495, but min width of 28 for height of 220

(program.exe:73640): Gtk-WARNING **: 01:11:28.226: Widget reports min width of 0 for height of 195, but min height of 204 for width of 488
(program.exe:73640): Gtk-WARNING **: 01:11:28.226: Widget reports min width of 24 for height of 219, but min height of 228 for width of 512
(program.exe:73640): Gtk-WARNING **: 01:11:28.226: Widget reports min width of 28 for height of 224, but min height of 233 for width of 516

It appears that they are from this:

and the code was the introduced at this change with the following commit message:
sizerequestcache: Warn on contradictory measurements (3e7ec4f9) · Commits · GNOME / gtk · GitLab

When committing the measurement for a widget to the cache, check if the
measurement contradicts a previous measurement in the opposite
orientation, and print a warning. Unfortunately, we don’t have a
reference to the widget itself at this point in the code, so we can’t
name the specific widget; nevertheless it should be easy to break on the
wanring and inspect the stack trace to see which measurement has
triggered the warning.

I get the idea. When measuring the minimum size (say, height) for width, comparing with the minimum width for height makes sense.

But I don’t get how the log message’s dimensions are contradictory.

(program.exe:73640): Gtk-WARNING **: 01:11:28.263: Widget reports min height of 195 for width of 467, but min width of 0 for height of 191
(program.exe:73640): Gtk-WARNING **: 01:11:28.263: Widget reports min height of 219 for width of 491, but min width of 24 for height of 215
(program.exe:73640): Gtk-WARNING **: 01:11:28.264: Widget reports min height of 224 for width of 495, but min width of 28 for height of 220

Why is it contradictory to return a larger min height (than for height) for a larger for width (than min width)?
Would it be more contradictory to return a smaller min height for a larger for width and vice versa?

(program.exe:73640): Gtk-WARNING **: 01:11:28.226: Widget reports min width of 0 for height of 195, but min height of 204 for width of 488
(program.exe:73640): Gtk-WARNING **: 01:11:28.226: Widget reports min width of 24 for height of 219, but min height of 228 for width of 512
(program.exe:73640): Gtk-WARNING **: 01:11:28.226: Widget reports min width of 28 for height of 224, but min height of 233 for width of 516

Similarly, why is it contradictory to return a smaller min width for a smaller for height?

Oh. I changed request_mode to return HeightForWidth, and it does what I want without the warning. I now have to understand what the warning wanted to warn me about.

Hi!

The layout system is tricky, and jumping to implement your own measurements without first thinking over the design is a sure way to get issues like these. Can you describe in some more words, and perhaps screenshots, what it is that you’re trying to achieve?

That is invalid. (Without additional careful handling.)

This is one of the fundamental laws that the measure implementations must follow. When given a larger width (for-size), the widget must report a smaller (or equal) minimum height. In other words, your height requirement cannot grow with available width.

It is fundamentally impossible, the way GTK’s layout system is designed, to have a widget’s minimum height grow when the available width grows.

Things that might actually work for your use case:

Thank you for the explanation! If that’s the requirement, I can understand the log messages.

I’m making this app: GitHub - foriequal0/steam-clip-exporter: Steam clip exporter · GitHub
This is the ui file: steam-clip-exporter/resources/clip_detail.ui at main · foriequal0/steam-clip-exporter · GitHub

The problem was if I shrink the vertical size of the window


It keeps shrink,

until it becomes a dot
(I wanted to put a screenshot here that the image became a dot, but blocked by the new user limitations)

The solutions that you mentioned were also considered, but

  • Pictures should be shrunk since it’s basically a screenshot.
  • I didn’t like to set minimum sizes and wanted GTK to do layout for me since it seemed to require some calculations based on the image sizes (they have various aspect ratios) and parent widget’s width (which can change if I shrink the width of window) or have some padding around the picture if I miscalculated it like the second screenshot.

I should try AspectFrame.

For that use case, I would set some reasonable minimum height (say, 150 pixels), so the screenshot doesn’t get shrunken to a dot indeed.

And I’d also wrap the whole thing into a Gtk.ScrolledWindow (with hscrollbar-policy: never, propagate-natural-height: true), so it remains usable when the window height is small (e.g. on a phone). Then, if you shrink the height of the window, it eventually starts scrolling instead of shrinking the screenshot indefinitely.

In the original scenario without a scrolled window, if what you were trying to implement (fixed-ratio minimum height for a picture) was allowed in GTK, consider what would happen if you did shrink the window height all the way, and then tried to enlarge the window width. You would want the newly available horizontal space to turn into padding around the image, right? But instead, it would force the window height to grow larger.

If I hadn’t been allowed to use a scrolled window or scrollable, I would expect that. But I used AdwClampScrollable (I didn’t know about GtkScrolledWindow, though)

This is what I want:


(This is the current implementation. I wonder whether it is allowed in GTK or not.)

1 → 2: If I shrink the window vertically, the picture will not shrink. It would overflow into scroll instead.
2 → 3: If enlarge the window horizontally, the picture would enlarge vertically and horizontally to fill the newly available horizontal space.

I want the picture to grow both horizontally and vertically to fill newly available horizontal space.

That’s why I wrapped them in AdwClampScrollable. But I didn’t know about `GtkScrolledWindow, so I’ve changed, and it doesn’t seem to change the behavior.

<object class="AdwClampScrollable">
  <child>
    <object class="AdwPreferencesPage">
      <child>

into

<object class="GtkScrolledWindow">
  <property name="propagate-natural-height">true</property>
  <property name="hscrollbar-policy">never</property>
  <child>
    <object class="AdwClampScrollable">
      <child>
        <object class="AdwPreferencesPage">
          <child>

(Widgets with native scrolling support, i.e. those whose classes implement the GtkScrollable interface, are added directly. in here means that I should add GtkScrollable inside the GtkScrolledWindow, right?)