~eliasnaur/gio

9 6

Layout help

Details
Message ID
<20191206150150.GA18147@larrymbp14.local>
DKIM signature
missing
Download raw message
I'd like some help on layout.

I'd like an editor widget at the bottom of the window, that starts at
one line when it's empty, and grows in height up to 25% of the window.

I'd like a list to fill the remaining space.  The list is
"ScrollToEnd: true", in case that matters.  So it's similar to a TTY
in that respect: you add stuff to it, which goes in at the bottom, and
everything above it scrolls up.

I've tried two Flex children with 75% and 25%, but that gives the
editor widget at the bottom 25% of the space even when it's empty.

I tried something, I forget exactly what, and got the editor widget to
start at one line and then expand, but it expanded up to the whole
window.

I tried setting the editor's gtx.Constraints.Height.Min = 0, but that
gave me an editor of height 0 that didn't grow at all.

I've tried various combinations of Flex and Rigid children, in various
orders of definition and in the call to Layout, but the first two
attempts I mentioned are the closest I've gotten.

Suggestions?  Thanks!

-- L
Raffaele Sena
Details
Message ID
<CANKfucYiDWapWCSKRsOGARMageETmFtOcuLQMD8L1N+p2xM+ug@mail.gmail.com>
In-Reply-To
<20191206150150.GA18147@larrymbp14.local> (view parent)
DKIM signature
pass
Download raw message
Since Gio re-render at every frame you should be able to change
percentages/constraint dynamically (i.e. start for example at 90%/10%
and adjust when the editor widget expands.

On Fri, Dec 6, 2019 at 7:02 AM Larry Clapp <larry@theclapp.org> wrote:
>
> I'd like some help on layout.
>
> I'd like an editor widget at the bottom of the window, that starts at
> one line when it's empty, and grows in height up to 25% of the window.
>
> I'd like a list to fill the remaining space.  The list is
> "ScrollToEnd: true", in case that matters.  So it's similar to a TTY
> in that respect: you add stuff to it, which goes in at the bottom, and
> everything above it scrolls up.
>
> I've tried two Flex children with 75% and 25%, but that gives the
> editor widget at the bottom 25% of the space even when it's empty.
>
> I tried something, I forget exactly what, and got the editor widget to
> start at one line and then expand, but it expanded up to the whole
> window.
>
> I tried setting the editor's gtx.Constraints.Height.Min = 0, but that
> gave me an editor of height 0 that didn't grow at all.
>
> I've tried various combinations of Flex and Rigid children, in various
> orders of definition and in the call to Layout, but the first two
> attempts I mentioned are the closest I've gotten.
>
> Suggestions?  Thanks!
>
> -- L
Details
Message ID
<BYYHETLXZ1A3.DSEI5ZLV94G1@toolbox>
In-Reply-To
<20191206150150.GA18147@larrymbp14.local> (view parent)
DKIM signature
pass
Download raw message
On Fri Dec 6, 2019 at 10:01 AM Larry Clapp wrote:
> I'd like some help on layout.
> 
> I'd like an editor widget at the bottom of the window, that starts at
> one line when it's empty, and grows in height up to 25% of the window.
> 
> I'd like a list to fill the remaining space.  The list is
> "ScrollToEnd: true", in case that matters.  So it's similar to a TTY
> in that respect: you add stuff to it, which goes in at the bottom, and
> everything above it scrolls up.
> 
> I've tried two Flex children with 75% and 25%, but that gives the
> editor widget at the bottom 25% of the space even when it's empty.
> 
> I tried something, I forget exactly what, and got the editor widget to
> start at one line and then expand, but it expanded up to the whole
> window.
> 
> I tried setting the editor's gtx.Constraints.Height.Min = 0, but that
> gave me an editor of height 0 that didn't grow at all.
> 
> I've tried various combinations of Flex and Rigid children, in various
> orders of definition and in the call to Layout, but the first two
> attempts I mentioned are the closest I've gotten.
> 

The trick is to realize that the order of Rigid/Flex calls and the order
of the FlexChildren in Flex.Layout don't have to match. In your example,
you should Rigid the widget with the highest priority first:

	// Available total height.
	height := gtx.Constraints.Height.Max

	f := layout.Flex{Axis: Vertical}
	
	// The bottom Editor gets first dips on the available height.
	c0 := f.Rigid(gtx, func() {
		// But only up to 25% of the total height.
		gtx.Constraints.Height.Max = int(.25*height)
		bottomEdit.Layout(gtx)
	})

	// Then the List is allowed to take up the rest of the space:
	c1 := f.Rigid(gtx, func() {
		list.Layout(gtx, ...)
	})

	// Note how c0 and c1 are swapped relative to their Rigid
	// order:
	f.Layout(gtx, c1, c0)

The above snippet is not tested, so let me know if it didn't match
your needs.

-- elias
Gregory Pomerantz
Details
Message ID
<2dbca6fd-8031-e105-dacd-5390d4df5c85@wow.st>
In-Reply-To
<BYYHETLXZ1A3.DSEI5ZLV94G1@toolbox> (view parent)
DKIM signature
pass
Download raw message
On 12/6/19 11:38 AM, Elias Naur wrote:
> The trick is to realize that the order of Rigid/Flex calls and the order
> of the FlexChildren in Flex.Layout don't have to match. In your example,
> you should Rigid the widget with the highest priority first:

I was just working on updating the heart rate monitor app to add a 
stopwatch widget and improve the layout (including adding new layouts 
for horizontal screens). A common pattern seems to be to have a Flex 
with a number of Rigid children and one or more Flex children taking up 
remaining space (basically pushing any other items to the edge of the 
layout area but still leaving enough space for them to get drawn).

Could it be possible to always automatically treat Flex children as a 
lower priority than RIgid children within the same layout.Flex layout? 
That might simplify these common cases a bit, i.e. could we say:

     f.Layout(gtx,
         f.Rigid(gtx, widget1),
         f.Flex(gtx, 0.5, spacer1),
         f.Rigid(gtx, widget2),
         f.Flex(gtx, 0.5, spacer2),
         f.Rigid(gtx, widget3),
     )

and have the flex's Layout function automatically compute the sizes of 
widget2 and widget3 before allocating the remaining space proportionally 
amongst the two spacers?
Details
Message ID
<BYYQ7SS82TCW.2FIL8JHCO3U7T@toolbox>
In-Reply-To
<2dbca6fd-8031-e105-dacd-5390d4df5c85@wow.st> (view parent)
DKIM signature
pass
Download raw message
On Fri Dec 6, 2019 at 12:34 PM Gregory Pomerantz wrote:
> On 12/6/19 11:38 AM, Elias Naur wrote:
> > The trick is to realize that the order of Rigid/Flex calls and the order
> > of the FlexChildren in Flex.Layout don't have to match. In your example,
> > you should Rigid the widget with the highest priority first:
> 
> I was just working on updating the heart rate monitor app to add a 
> stopwatch widget and improve the layout (including adding new layouts 
> for horizontal screens). A common pattern seems to be to have a Flex 
> with a number of Rigid children and one or more Flex children taking up 
> remaining space (basically pushing any other items to the edge of the 
> layout area but still leaving enough space for them to get drawn).
> 
> Could it be possible to always automatically treat Flex children as a 
> lower priority than RIgid children within the same layout.Flex layout? 
> That might simplify these common cases a bit, i.e. could we say:
> 
>      f.Layout(gtx,
>          f.Rigid(gtx, widget1),
>          f.Flex(gtx, 0.5, spacer1),
>          f.Rigid(gtx, widget2),
>          f.Flex(gtx, 0.5, spacer2),
>          f.Rigid(gtx, widget3),
>      )
> 
> and have the flex's Layout function automatically compute the sizes of 
> widget2 and widget3 before allocating the remaining space proportionally 
> amongst the two spacers?

That's a good idea, but in your example spacer1 would be called before widget2
and widget3, so spacer1's 0.5 weight would only take widget1 into account.

Unfortunately, the improved variant

	f.Layout(gtx,
		layout.FlexChild{Widget: widget1},
		layout.FlexChild{Widget: widget1, Weigh:0.5},
		...
	)

leads to escaping of the widgets functions.

Keep trying. I'd really like to simplify Flex and Stack to just a single Layout
call, just like List.

A crazy variant is to steal from layout.Format
(https://godoc.org/eliasnaur.com/giox/layout), say

	flex.Layout(gtx, "r,f(0.5),r,f(0.5),r", w1, s1, w2, s2, w3)

which doesn't lead to escaping widgets.

WDYT?

-- elias
Details
Message ID
<CAE_4BPAdzRxFinPRf5Vhw4Qdqj2hoTieRU8EwAT02LNp4jweZQ@mail.gmail.com>
In-Reply-To
<BYYHETLXZ1A3.DSEI5ZLV94G1@toolbox> (view parent)
DKIM signature
missing
Download raw message
This worked perfectly almost as-is.  (The only thing missing was
"float32(height)", i.e. "int(.25 * float32(height))".  Instead I used
"height / 4". :)  Thanks!

I did try doing the Rigid calls in this order before, but it didn't
occur to me to change the constraints in the first call.  I assumed
that I should be able to achieve what I wanted with a Flex and a
Rigid, and that monkeying around with Constraints was overkill and/or
shouldn't be necessary and/or bad style.  But apparently I was wrong!
:)

Thanks again.

Gregory Pomerantz:
You have three "real" widgets and two "spacers", with the two spacers
splitting the space left over after the three real widgets.  (I
think?)  How is that different from Flex{Spacing: layout.SpaceBetween}
with just the three "real" widgets?  "SpaceBetween distributes space
evenly between children, leaving no space at the start and end."  (I
haven't tried this but it *seems* like it should do what you want.)

— L


On Fri, Dec 6, 2019 at 11:38 AM Elias Naur <mail@eliasnaur.com> wrote:
>
> On Fri Dec 6, 2019 at 10:01 AM Larry Clapp wrote:
> > I'd like some help on layout.
> >
> > I'd like an editor widget at the bottom of the window, that starts at
> > one line when it's empty, and grows in height up to 25% of the window.
> >
> > I'd like a list to fill the remaining space.  The list is
> > "ScrollToEnd: true", in case that matters.  So it's similar to a TTY
> > in that respect: you add stuff to it, which goes in at the bottom, and
> > everything above it scrolls up.
> >
> > I've tried two Flex children with 75% and 25%, but that gives the
> > editor widget at the bottom 25% of the space even when it's empty.
> >
> > I tried something, I forget exactly what, and got the editor widget to
> > start at one line and then expand, but it expanded up to the whole
> > window.
> >
> > I tried setting the editor's gtx.Constraints.Height.Min = 0, but that
> > gave me an editor of height 0 that didn't grow at all.
> >
> > I've tried various combinations of Flex and Rigid children, in various
> > orders of definition and in the call to Layout, but the first two
> > attempts I mentioned are the closest I've gotten.
> >
>
> The trick is to realize that the order of Rigid/Flex calls and the order
> of the FlexChildren in Flex.Layout don't have to match. In your example,
> you should Rigid the widget with the highest priority first:
>
>         // Available total height.
>         height := gtx.Constraints.Height.Max
>
>         f := layout.Flex{Axis: Vertical}
>
>         // The bottom Editor gets first dips on the available height.
>         c0 := f.Rigid(gtx, func() {
>                 // But only up to 25% of the total height.
>                 gtx.Constraints.Height.Max = int(.25*height)
>                 bottomEdit.Layout(gtx)
>         })
>
>         // Then the List is allowed to take up the rest of the space:
>         c1 := f.Rigid(gtx, func() {
>                 list.Layout(gtx, ...)
>         })
>
>         // Note how c0 and c1 are swapped relative to their Rigid
>         // order:
>         f.Layout(gtx, c1, c0)
>
> The above snippet is not tested, so let me know if it didn't match
> your needs.
>
> -- elias
Gregory Pomerantz
Details
Message ID
<f07d2d87-1aff-98c6-e3d5-b890b5e41023@wow.st>
In-Reply-To
<CAE_4BPAdzRxFinPRf5Vhw4Qdqj2hoTieRU8EwAT02LNp4jweZQ@mail.gmail.com> (view parent)
DKIM signature
pass
Download raw message
On 12/9/19 10:18 AM, Larry Clapp wrote:
> Gregory Pomerantz:
> You have three "real" widgets and two "spacers", with the two spacers
> splitting the space left over after the three real widgets.  (I
> think?)  How is that different from Flex{Spacing: layout.SpaceBetween}
> with just the three "real" widgets?  "SpaceBetween distributes space
> evenly between children, leaving no space at the start and end."  (I
> haven't tried this but it *seems* like it should do what you want.)

Yes, that works for empty spacers, my use of that term was confusing -- 
I had in mind a layout where certain elements are fixed size and others 
expand to fill space. I'm not sure this is possible to do in a 
garbage-free way without re-ordering the declaration of your widgets -- 
ideally we could pass function pointers from flex.Rigid and flex.Flex 
directly into flex.Layout, but that requires storing the function 
pointer somewhere in the FlexChild or the Flex itself, which prevents 
inlining and therefore risks escaping the Widget to the heap. The go 
escape analysis seems clever enough to figure this out for simple 
examples but gets tripped up at some point when things get more complicated.
Gregory Pomerantz
Details
Message ID
<d47f6d49-4213-259b-0294-7014cccf1010@wow.st>
In-Reply-To
<f07d2d87-1aff-98c6-e3d5-b890b5e41023@wow.st> (view parent)
DKIM signature
pass
Download raw message
On 12/9/19 11:19 AM, Gregory Pomerantz wrote:
> On 12/9/19 10:18 AM, Larry Clapp wrote:
>> Gregory Pomerantz:
>> You have three "real" widgets and two "spacers", with the two spacers
>> splitting the space left over after the three real widgets.  (I
>> think?)  How is that different from Flex{Spacing: layout.SpaceBetween}
>> with just the three "real" widgets?  "SpaceBetween distributes space
>> evenly between children, leaving no space at the start and end."  (I
>> haven't tried this but it *seems* like it should do what you want.)
>
> Yes, that works for empty spacers, my use of that term was confusing 
> -- I had in mind a layout where certain elements are fixed size and 
> others expand to fill space. I'm not sure this is possible to do in a 
> garbage-free way without re-ordering the declaration of your widgets 
> -- ideally we could pass function pointers from flex.Rigid and 
> flex.Flex directly into flex.Layout, but that requires storing the 
> function pointer somewhere in the FlexChild or the Flex itself, which 
> prevents inlining and therefore risks escaping the Widget to the heap. 
> The go escape analysis seems clever enough to figure this out for 
> simple examples but gets tripped up at some point when things get more 
> complicated.

It turns out it is not the function pointers that are the tricky bit, 
but the variadic Layout function for layout.Flex. I attached a patch 
which does what I want without any additional garbage, the use of 
sync.Pool may be considered cheating but I can't see any other way to 
avoid leaking the slice of FlexChildren that is passed as a parameter to 
Flex.Layout(). Can you see any obvious reasons why this is broken?

Note that Flex.Flex() and Flex.Rigid() no longer need the gtx parameter 
(and don't need any reference to the enclosing Flex) but I kept them for 
now as it makes the patch API-compatible with the existing design. With 
the attached patch we can do this:

     f := layout.Flex{Axis: layout.Vertical}
     gtx.Constraints.Height.Min = gtx.Constraints.Height.Max
     f.Layout(gtx,
         f.Rigid(gtx, func() { topBar.Layout(gtx) }),
         f.Flex(gtx, 1.0, func() { editor.Layout(gtx, ed) }),
         f.Rigid(gtx, func() { footer.Layout(gtx) }),
     )

and the editor will fill the entire space between the topBar and the 
footer without the footer being pushed off the bottom.

If you want you can also now skip the calls to Rigid() and Flex() and 
write FlexChild{weight, widget} directly.
Details
Message ID
<BZ306B3LHALP.PPUH6PL4BHDF@toolbox>
In-Reply-To
<d47f6d49-4213-259b-0294-7014cccf1010@wow.st> (view parent)
DKIM signature
pass
Download raw message
On Mon Dec 9, 2019 at 11:19 PM Gregory Pomerantz wrote:
> On 12/9/19 11:19 AM, Gregory Pomerantz wrote:
> > On 12/9/19 10:18 AM, Larry Clapp wrote:
> It turns out it is not the function pointers that are the tricky bit, 
> but the variadic Layout function for layout.Flex. I attached a patch 
> which does what I want without any additional garbage, the use of 
> sync.Pool may be considered cheating but I can't see any other way to 
> avoid leaking the slice of FlexChildren that is passed as a parameter to 
> Flex.Layout(). Can you see any obvious reasons why this is broken?
> 
> Note that Flex.Flex() and Flex.Rigid() no longer need the gtx parameter 
> (and don't need any reference to the enclosing Flex) but I kept them for 
> now as it makes the patch API-compatible with the existing design. With 
> the attached patch we can do this:
> 
>      f := layout.Flex{Axis: layout.Vertical}
>      gtx.Constraints.Height.Min = gtx.Constraints.Height.Max
>      f.Layout(gtx,
>          f.Rigid(gtx, func() { topBar.Layout(gtx) }),
>          f.Flex(gtx, 1.0, func() { editor.Layout(gtx, ed) }),
>          f.Rigid(gtx, func() { footer.Layout(gtx) }),
>      )
> 
> and the editor will fill the entire space between the topBar and the 
> footer without the footer being pushed off the bottom.
> 
> If you want you can also now skip the calls to Rigid() and Flex() and 
> write FlexChild{weight, widget} directly.
> 
> 

I didn't like the dependency on sync.Pool, but your patch made me take a
closer look. It turns out that my initial analysis was wrong (it wasn't
the variadic argument either). See

	https://gioui.org/commit/06217c532056c0bb3c5a808b403bdca108f84e74

for the fiddly details.

The good news is that the above commit resolves the problematic heap escape,
and the following commits implement the much nicer API for Flex and Stack.

See

	https://gioui.org/commit/7814da47a0ffdf00462fbd0095af5e3d23757ef2
	https://gioui.org/commit/f60a5c7ac342fe67638ae94fa332dfc743bc62a9

for details.

-- elias
Details
Message ID
<1ef4569d1c1eae21337ae19e4806340f@evereska.org>
In-Reply-To
<BZ306B3LHALP.PPUH6PL4BHDF@toolbox> (view parent)
DKIM signature
pass
Download raw message
> The good news is that the above commit resolves the problematic heap escape,
> and the following commits implement the much nicer API for Flex and Stack.
> 
> See
> 
> https://gioui.org/commit/7814da47a0ffdf00462fbd0095af5e3d23757ef2
> https://gioui.org/commit/f60a5c7ac342fe67638ae94fa332dfc743bc62a9
> 
> for details.
> 
> -- elias

I updated my code, it was quite easy and it is a simpler way to do it.

I had to call Rigid in an non visual order so when I would call the Layout it would have computed size correctly.
Now it makes sense, you can declare in visual order, and still get the correct size.

Nice!