Skip to content
Randall C. O'Reilly edited this page Jan 25, 2024 · 1 revision

Layout Tips

  • Layout is designed to be driven "bottom-up" by the actual sizes of the basic widget elements, typically driven by text elements, so typically no additional sizing hints are needed.

  • Use the horizontal-align property to align widget elements within a vertical layout (specifying their relative horizontal alignments, "left", "center" or "right"), and vice-versa for vertical-align within horizontal layout ("top", "middle", "bottom"). If you want an element in a horizontal layout to show up on the right, you need to insert a gi.Stretch element, which will insert just the right amount of space to push everything after it over to the right side. Likewise, adding Stretch elements on either side will center the element. The same logic applies for aligning elements vertically within a vertical layout. This is the same logic for setting margin: auto to achieve horizontal centering in CSS / HTML: https://www.w3schools.com/css/css_align.asp

  • the text-align and text-vertical-align properties apply to text elements, typically a gi.Label or the Text within a button, relative to their containing element. For a gi.Label, the containing element is the gi.Label itself! Thus, for this property to have an effect, you must explicitly specify a size, because otherwise the label is sized based on the text, and thus, for single-line text, this property has no effect. For multi-line text, text-align is always useful for aligning the lines.

  • Use min-width width max-width and min-height, height, max-height styles when the default content-based sizing is not sufficient, as follows:

  • min- ensures that the size is at least some basic size -- the default min- size of all widgets is 2 pixels. Set a larger value for min- size if the element's concrete content is not sufficiently constraining to drive the correct minimum size. Generally using a font-based unit for this will ensure proper scaling as a function of font and DPI sizes, e.g., units.NewEm(N)

  • width or height define a specific "preferred" size -- generally not needed, except in the critical case of variable-sized elements that may exceed a reasonable size when driven strictly based on content (e.g., StructTableView, SliceView etc). Setting a smallish but reasonable preferred size prevents these elements from driving the size of the outer elements containing them to some large unreasonable size, and forces scrollbars to associated directly with these inner elements instead of the outer elements. In addition, it typically works best to then give these elements a "stretch" max value (see below), so they can take up all the space that is actually available to them. There is a convenience function for setting both min and pref in one call, which is typically sensible for these cases:

	sg.SetMinPrefHeight(units.NewEm(10))
	sg.SetMinPrefWidth(units.NewEm(10))
  • max- defines a maximum size -- also generally not needed (i.e., leave at the default 0 value, which means ignore), and indeed should be avoided, unless there is a good reason. There is an important special case: -1 (or any negative number) means stretch the size of this element to take up any extra space available. There is also a special function for this:
	sg.SetMaxStretch() // for both width and height, or..
	sg.SetMaxStretchHeight()
	sg.SetMaxStretchWidth()
  • Use a Frame instead of a plain Layout for anything that is designed to scroll -- it is important to repaint the background after scrolling, otherwise you'll see a big mess of old and new rendering. You can set border-width to 0 to get rid of the lines around frames. Also, a plain Layout does not render itself at all, so setting a border-width etc will have no effect -- you must use a Frame for layouts that render.

  • Set the overflow property to "hidden" (= gist.OverflowHidden) to prevent a layout from using scrollbars to grow in size. This is rarely needed but sometimes things are just a bit off in sizing and you don't want scrollbars to be triggered under any circumstances.

Layout implementation logic

There are two main layout passes:

1. Size2D: DepthFirst downward pass, each node first calls
g.Layout.Reset(), then sets their LayoutSize according to their own
intrinsic size parameters, and/or those of its children if it is a Layout

2. Layout2D: MeFirst downward pass (each node calls on its children at
appropriate point) with relevant parent BBox that the children are
constrained to render within -- they then intersect this BBox with their
own BBox (from BBox2D) -- typically just call Layout2DBase for default
behavior -- and add parent position to AllocPos. Layout does all its
sizing and positioning of children in this pass, based on the Size2D data
gathered bottom-up and constraints applied top-down from higher levels.
Typically only a single iteration is required but multiple are supported
(needed for word-wrapped text or flow layouts).

The Layout2D stack passes back up a bool return value indicating if an additional iteration is required (and it also receives an iteration counter, passed throughout the stack). Re-layout is triggered by word-wrapped text or flow layouts, and is otherwise not needed.

The key layout data is in LayoutData:

  • Size is set from the min, max and "pref" width and height style properties

  • AllocSize represents the final allocated size, but also is used during Size2D for terminal nodes to specify their computed actual size needs -- the Layout2D process then puts this value into Size.Min (as Max relative to any style-set value). Thus the WidgetBase Size2DFromWH sets this AllocSize value as an input to the layout process.

  • For any nodes that trigger a re-layout iteration, they should just stick with the AllocSize computed from last time, but layouts will typically need to recompute.