VLayout in a scroll or container view

Anyone got a functioning example? Doesn’t seem to work.

(
var view = ScrollView();
view.canvas = View().layout_(VLayout(
	*(
		100.collect {
			View().fixedSize_(200@(rand(20, 400)))
			.background_(Color.rand)
		}
	)
).spacing_(0));
view.front;
)

What about this?

1 Like

Thanks. Hmm…

// works
View(nil, Rect(100, 1000, 300, 400)).background_(Color.red).layout_(VLayout(
	*(
		100.collect {
			TextField().string_("Super")
		}
	)
)).front;

// fails
(
var view = ScrollView();
View(view, Rect(0, 0, 300, 400)).background_(Color.red).layout_(VLayout(
	*(
		100.collect {
			TextField().string_("Super")
		}
	)
));
view.front;
)

Also fails, and really seems like it shouldn’t? But perhaps a LineLoyouts only work on top level views or within other LineLayouts?

(
var w = Window(bounds:Rect(100,100,300,300));
View(w, Rect(0, 0, 300, 400)).background_(Color.red).layout_(VLayout(
	*(
		100.collect {
			TextField().string_("Super")
		}
	)
));
w.front;
)

I’m not totally sure about the mechanics of this, but I’m pretty sure Layouts will not work (well…) if you don’t allow them to attach children to parents, or if you mix parentage between layouts and directly passing into the constructor.

Maybe this gives a clue as to why:

(
    var w =  Window(bounds:Rect(100,100,300,300));
    var v = View(w, bounds:Rect(0, 0, 50, 50)).background_(Color.red);
    v.layout_(VLayout(
        *(
            100.collect {
                TextField().string_("Super").fixedSize_(100@30)
            }
        )
    ));
    v.bounds = Rect(0, 0, v.sizeHint.width, v.sizeHint.height);
    w.front;
)

This basically works as one would expect. But, remove the v.bounds line and you end up with a tiny, squashed view where nothing is laid out correctly. This sort of makes sense - the VLayout can suggest a size based on the children, but there’s nothing setting/changing the size of v unless it is either inside another Layout, or it’s being set manually (my v.bounds = line).

If you remove v.bounds = but hard-code the view size to sizeHint (basically: the size of the layed-out children), you get the expected result:

View(w, bounds:Rect(0, 0, 118.0, 3612.0))

From there, it’s easy to see what’s going on… Just change the hard coded size by shrinking the height:

View(w, bounds:Rect(0, 0, 118.0, 3612.0 - 500))

The result is that all the children are squashed because they are constrained by the size of their parent - they look a little wonky because they’re smaller than their preferred size - this is in some sense a mildly invalid state. It’s clearer, then, that in your original example it’s just a case of all of the children being so radically squashed that all you can see is glitch.

The moral of the story here is probably: always use layouts, never use direct parentage, unless one is prepared to / wants to explicitly manage the size of items that are not part of a layout. I make a lot of fairly sophisticated UI systems, and I don’t think I’ve ever needed the parent constructor argument in any of them?

One thing to note is that, until you call .front on a View, you can freely modify, swap parentage, etc., with basically zero cost - it’s all more or less a data structure at that point. So, you can assemble components separately, and then combine them together later by e.g. adding them to a layout. The layout also handles parentage, and can even swap parents if you e.g. add something to a different layout after it’s created. If you want to attach a component to a parent window, doing parent.layout.add(View()) is much preferable to View(parent).

Here’s a more extended example of flexibly creating views, assembling them, and dynamically re-parenting:

(
    var view_a, view_b;
    
    view_a = View().layout_(VLayout(
        *30.collect {
            View()
                .fixedSize_(80@6)
                .background_(Color.rand)
        }
    ).spacing_(2));
    
    view_b = View().layout_(GridLayout.columns(
        *30.collect {
            View()
                .fixedSize_(40@20)
                .background_(Color.rand)
        }
        .clump(4)
    ).spacing_(2));
    
    3.do {
        |i|
        var top, h_layout, v_layout, swap_button;
        
        swap_button = Button()
            .states_([["horizontal"], ["vertical"]])
            .action_({
                |v|
                if (v.value == 0) {
                    h_layout.add(view_a, align:\center);
                    h_layout.add(view_b, align:\center);
                } {
                    v_layout.add(view_a, align:\center);
                    v_layout.add(view_b, align:\center);
                }
            });
        
        top = View(bounds:Rect(500 * i))
            .fixedWidth_(500)
            .layout_(VLayout(
                [ swap_button, stretch: 0 ],
                [
                    VLayout(
                        h_layout = HLayout(),
                        v_layout = VLayout()
                    ).spacing_(0).margins_(0),
                    stretch: 1
                ]
            )).front;
    };
)

Thanks for this @scztt. I think maybe some small doc improvements might help the next person to face this. I’ll take a look…

@scztt PR here: LineLayout related doc improvements by muellmusik · Pull Request #6273 · supercollider/supercollider · GitHub

I took the liberty of assigning you since you’re in the loop and this should be uncontroversial. Thanks for your help with this.