Skip to content

Layouts

mike-ward edited this page May 17, 2026 · 1 revision

Layouts

The layout system is go-gui's flex model. Every container is a Row or Column at its core; the specialised containers (Splitter, DockLayout, etc.) layer behaviour on top. Understanding sizing modes is the key to productive layout work.


Sizing modes

All containers accept a Sizing field that controls how width and height are determined. The pattern is Width×Height:

Mode Width Height
FillFill Grow to fill parent Grow to fill parent
FillFit Grow to fill parent Shrink to fit children
FitFill Shrink to fit children Grow to fill parent
FitFit Shrink to fit children Shrink to fit children
FixedFixed Use explicit Width/Height Use explicit Width/Height
FixedFit Use explicit Width Shrink to fit children
FillFixed Grow to fill parent Use explicit Height

FillFill makes a container take all available space. FitFit makes it hug its content. FixedFixed pins both dimensions explicitly — useful for the root container at window size:

gui.Column(gui.ContainerCfg{
    Width:  float32(ww),
    Height: float32(wh),
    Sizing: gui.FixedFixed,
})

Row and Column

gui.Row and gui.Column are the fundamental containers. They arrange children horizontally or vertically with configurable alignment and spacing.

gui.Row(gui.ContainerCfg{
    Sizing:  gui.FillFit,
    Spacing: gui.SomeF(8),
    VAlign:  gui.VAlignMiddle,
    Padding: gui.SomeP(4, 8, 4, 8), // top, right, bottom, left
    Content: []gui.View{
        icon,
        label,
    },
})

gui.Column uses the same ContainerCfg; only the axis changes.

Alignment constants:

  • HAlign: HAlignLeft, HAlignCenter, HAlignRight
  • VAlign: VAlignTop, VAlignMiddle, VAlignBottom

Wrap

gui.Wrap flows children left-to-right and wraps to a new row when the available width is exhausted — the same model as CSS flex-wrap: wrap.

gui.Wrap(gui.ContainerCfg{
    Sizing:  gui.FillFit,
    Spacing: gui.SomeF(8),
    Content: tagViews,  // []gui.View — wraps automatically
})

Useful for tag clouds, icon grids, and responsive button groups.


Canvas

gui.Canvas places children at absolute coordinates. Each child positions itself via X/Y on its Shape. Use this when precise pixel placement is required.

gui.Canvas(gui.ContainerCfg{
    Width:  400,
    Height: 300,
    Sizing: gui.FixedFixed,
    Content: []gui.View{
        labelAt(50, 40, "Origin marker"),
        iconAt(200, 150, gui.IconStar),
    },
})

For procedural drawing within a fixed area, see DrawCanvas in Display Widgets.


ExpandPanel

gui.ExpandPanel(gui.ExpandPanelCfg{...}) is a collapsible accordion section. The Head view is always visible; Content reveals when Open is true.

gui.ExpandPanel(gui.ExpandPanelCfg{
    ID:   "details",
    Open: app.DetailsOpen,
    Head: gui.Text(gui.TextCfg{Text: "Advanced options", TextStyle: t.B3}),
    Content: gui.Column(gui.ContainerCfg{
        Sizing:  gui.FillFit,
        Padding: gui.SomeP(8, 0, 8, 0),
        Content: []gui.View{
            optionRows...,
        },
    }),
    OnToggle: func(w *gui.Window) {
        gui.State[App](w).DetailsOpen = !gui.State[App](w).DetailsOpen
    },
})

Stack multiple ExpandPanel widgets in a Column to build a full accordion.


Splitter

gui.Splitter(gui.SplitterCfg{...}) divides space between two panes with a draggable handle. The split can be horizontal (top/bottom) or vertical (left/right).

gui.Splitter(gui.SplitterCfg{
    ID:                  "editor-preview",
    IDFocus:             20,
    Orientation:         gui.SplitterVertical,
    Sizing:              gui.FillFill,
    Ratio:               gui.SomeF(app.SplitRatio), // 0.0–1.0
    ShowCollapseButtons: true,
    Collapsed:           app.SplitCollapsed,
    OnChange: func(ratio float64, collapsed int, w *gui.Window) {
        a := gui.State[App](w)
        a.SplitRatio = ratio
        a.SplitCollapsed = collapsed
    },
    First: gui.SplitterPaneCfg{
        MinSize: 110,
        Content: []gui.View{editorView},
    },
    Second: gui.SplitterPaneCfg{
        MinSize: 90,
        Content: []gui.View{previewView},
    },
})

The ratio and collapsed state live in your own state struct — go-gui does not own them. Home/End collapse the focused pane when ShowCollapseButtons is true.


Sidebar

w.Sidebar(gui.SidebarCfg{...}) is an animated slide-out side panel. It is typically placed as the first child of a Row, beside the main content area.

gui.Row(gui.ContainerCfg{
    Sizing:  gui.FillFixed,
    Height:  500,
    Padding: gui.NoPadding,
    Content: []gui.View{
        w.Sidebar(gui.SidebarCfg{
            ID:    "nav",
            Open:  app.NavOpen,
            Width: 220,
            Content: []gui.View{navItems},
        }),
        mainContent,
    },
})

Flip app.NavOpen in any OnClick to animate open/close.


OverflowPanel

gui.OverflowPanel(gui.OverflowPanelCfg{...}) clips its children to a fixed area and provides scroll controls for the overflow. Use it when the content list is long but the available space is fixed.

gui.OverflowPanel(gui.OverflowPanelCfg{
    ID:       "log-panel",
    IDScroll: 300,
    Sizing:   gui.FillFixed,
    Height:   240,
    Content:  logLineViews,
})

IDScroll registers the container with the scroll system so the framework tracks its offset. See Focus and Scrolling for programmatic scroll control.


DockLayout

gui.DockLayout(gui.DockLayoutCfg{...}) provides IDE-style dockable panels: areas that can be split, merged, and rearranged by dragging tabs between them at runtime.

The layout tree is a *gui.DockNode stored in your state. The framework calls OnLayoutChange whenever the user rearranges panels so you can persist the new arrangement.

// initial layout: left sidebar + main split (editor on top, terminal below)
func initialLayout() *gui.DockNode {
    return gui.DockSplit("root", gui.DockSplitHorizontal, 0.2,
        gui.DockPanelGroup("left", []string{"explorer"}, "explorer"),
        gui.DockSplit("right", gui.DockSplitVertical, 0.75,
            gui.DockPanelGroup("editor-area", []string{"editor"}, "editor"),
            gui.DockPanelGroup("bottom", []string{"terminal"}, "terminal"),
        ),
    )
}

// in the view function
gui.DockLayout(gui.DockLayoutCfg{
    ID:     "main-dock",
    Root:   app.DockRoot,
    Panels: dockPanels(),
    OnLayoutChange: func(root *gui.DockNode, w *gui.Window) {
        gui.State[App](w).DockRoot = root
    },
    OnPanelSelect: func(groupID, panelID string, w *gui.Window) {
        a := gui.State[App](w)
        a.DockRoot = gui.DockTreeSelectPanel(a.DockRoot, groupID, panelID)
    },
})

dockPanels() returns []gui.DockPanelCfg, each with an ID and a View factory. The view factory is called each frame only for visible panels.


RotatedBox

gui.RotatedBox(gui.RotatedBoxCfg{...}) rotates its single child by a multiple of 90°. QuarterTurns: 1 is 90° clockwise, 2 is 180°, 3 is 270°.

gui.RotatedBox(gui.RotatedBoxCfg{
    QuarterTurns: 1,
    Content: gui.Text(gui.TextCfg{
        Text:      "Rotated label",
        TextStyle: gui.CurrentTheme().N3,
    }),
})

Useful for vertical axis labels in charts or sideways tab strips.

Clone this wiki locally