Layout and Positioning

Layout

While View handles the rendering of a component, the actual positioning of the views is delegated to the Layouter interface. Each view can specify it’s layouter in the return value of the view’s Build() call. If no layouter is given, all of its children will be positioned to size of its parent view.

Constraints

Package constraint implements a constraint-based layout system.

func (v *View) Build(ctx view.Context) view.Model {
    // Create a new constraint system.
    l := &constraint.Layouter{}

    // Solves for the position of v, given the constraints on s. The result is a 400x100 frame.
    l.Solve(func(s *constraint.Solver) {
        s.Width(400)
        s.Width(200) // If two constraints conflict, the later one is ignored.
        s.Height(100)
    })

    // Adds a child view and solves for its position relative to v. The result is a 5x10 frame pinned to the lower right corner of v.
    child1 := basicview.New()
    guide1 := l.Add(child1, func(s *constraint.Solver) {
        s.Width(5) // Left(), Top(), CenterX()... methods support constraining to floats.
        s.Height(10)
        s.TopEqual(l.Bottom()) // LeftEqual(), TopLess(), CenterXGreater()... methods support constraining to anchors.
        s.LeftEqual(l.Right())
    })

    // Anchors can be manipulated outside of the solver function.
    verticalCenter := l.CenterX().Add(10)

    // Adds a child view that is twice as large as child1 and 10 points above the center v.
    child2 := basicview.New()
    _ = l.Add(child1, func(s *constraint.Solver) {
        s.WidthEqual(guide1.Width().Mul(2)) // Anchors can be added to and multiplied by constants.
        s.HeightEqual(guide1.Height().Mul(2))
        s.CenterXEqual(l.CenterX())
        s.CenterYEqual(verticalCenter.Add(10))
    })

    // Recalulates the constraints for child1.
    guide1.Solve(func(s *constraint.Solver) {
        s.Width(40)
        s.Height(30)
        s.TopEqual(l.Bottom()) // The top and left position must be respecified, even though only the width and height have been updated.
        s.LeftEqual(l.Right())
    })

    // Solvers do not run simultaneously! Child2 is still 10x20 since at the time it was added Child1 was 5x10.

    return view.Model{
        Views:    l.Views(),
        Layouter: l,
    }
}

If a child view is unconstrained in x or y, it will try to move as close to the center of the parent as possible. If the view is unconstrained in width or height, it will try to match the minGuide as close as possible.

Layouter

Understanding the details of this is not too important for most day to day development. For the most part you will be using predefined layouters such as the one provided by the constraint and table package.

Layout occurs in a separate pass after the view hierarchy has been built. Like Build(), each layouter is only responsible for returning its own frame and the frame of its direct descendents. To determine the correct sizing for a child, the layouter will call Context.LayoutChild() passing a minimum and maximum size. The child will return a desired size within the min and max, which the parent can then position. Here is an example Layout function that centers its children within itself.

func (l *Layouter) Layout(ctx layout.Context) (layout.Guide, []layout.Guide) {
    // Specify that the view wants to be the minSize given by its parent.
    g := layout.Guide{
        Frame: layout.Rt(0, 0, ctx.MinSize().X, ctx.MinSize().Y),
    }

    // Iterate over all child ids.
    gs := []layout.Guide{}
    for i := 0; i< ctx.ChildCount(); i++ {

        // Get the desired size of the children. In this case we let the children be any size.
        child := ctx.LayoutChild(idx, layout.Pt(0, 0), layout.Pt(math.Inf(1), math.Inf(1)))

        // Position the children to be centered in the view.
        child.Frame = child.Frame.Add(layout.Pt(g.CenterX()-child.Width()/2, g.CenterY()-child.Height()/2))
        child.ZIndex = i
        gs = append(gs, child)
    }

    // Return the view's size, and the frames of its children.
    return g, gs
}

Layouters also implement the comm.Notifier interface. This allows layouts to update without rebuilding the view. It is light-weight and useful for animations.