core/docking/host.go
Functions
func ActivePanelIndex
func (h *Host) ActivePanelIndex(zone Zone) int {
if int(zone) >= zoneCount || h.zones[zone].isEmpty() {
return -1
}
return h.zones[zone].activeIdx
}
func BottomRatio
BottomRatio sets the size ratio of the bottom zone (0.0 to 1.0).
Default is 0.2 (20% of available height).
func BottomRatio(r float32) HostOption {
return func(c *hostConfig) { c.bottomRatio = clampRatio(r) }
}
func CenterContent
CenterContent sets the main content widget displayed in the center zone.
func CenterContent(w widget.Widget) HostOption {
return func(c *hostConfig) { c.centerContent = w }
}
func Children
Children returns all visible content widgets (zone panels + center).
func (h *Host) Children() []widget.Widget {
var children []widget.Widget
// Center content.
if h.cfg.centerContent != nil {
children = append(children, h.cfg.centerContent)
}
// Active panel content from each zone.
for z := Zone(0); z < zoneCount; z++ {
if z == Center {
continue
}
panel := h.zones[z].activePanel()
if panel != nil && panel.Content() != nil {
children = append(children, panel.Content())
}
}
if len(children) == 0 {
return nil
}
return children
}
func ColorSchemeOpt
ColorSchemeOpt sets the theme-derived color scheme for the dock host.
func ColorSchemeOpt(cs ZoneColorScheme) HostOption {
return func(c *hostConfig) { c.colorScheme = cs }
}
func Dock
Dock adds a panel to the specified zone.
If the zone already has panels, the new panel becomes an additional tab.
The new panel becomes the active tab in its zone.
func (h *Host) Dock(panel *Panel, zone Zone) {
if panel == nil {
return
}
if int(zone) >= zoneCount {
return
}
// Remove from any existing zone first.
h.undockFromAll(panel)
h.zones[zone].addPanel(panel)
// ADR-028: parent chain for upward dirty propagation.
if content := panel.Content(); content != nil {
type parentSetter interface{ SetParent(widget.Widget) }
if ps, ok := content.(parentSetter); ok {
ps.SetParent(h)
}
}
}
func Draw
Draw renders the dock layout to the canvas.
func (h *Host) Draw(ctx widget.Context, canvas widget.Canvas) {
bounds := h.Bounds()
if bounds.IsEmpty() {
return
}
// Draw center content first (background).
if h.cfg.centerContent != nil {
h.cfg.centerContent.Draw(ctx, canvas)
}
// Draw edge zones on top of center.
for z := Zone(0); z < zoneCount; z++ {
if z == Center || h.zones[z].isEmpty() {
continue
}
h.drawZone(ctx, canvas, z)
}
}
func Event
Event handles input events for the dock host.
func (h *Host) Event(ctx widget.Context, e event.Event) bool {
// Check edge zone tab bars for click events.
if consumed := h.handleZoneTabEvents(ctx, e); consumed {
return true
}
// Forward to active panel content in edge zones.
for z := Zone(0); z < zoneCount; z++ {
if z == Center {
continue
}
panel := h.zones[z].activePanel()
if panel != nil && panel.Content() != nil {
if panel.Content().Event(ctx, e) {
return true
}
}
}
// Forward to center content.
if h.cfg.centerContent != nil {
if h.cfg.centerContent.Event(ctx, e) {
return true
}
}
return false
}
func Layout
Layout calculates zone sizes and positions all children.
func (h *Host) Layout(ctx widget.Context, constraints geometry.Constraints) geometry.Size {
totalSize := constraints.Constrain(geometry.Sz(constraints.MaxWidth, constraints.MaxHeight))
if totalSize.Width <= 0 || totalSize.Height <= 0 {
totalSize = geometry.Sz(defaultHostWidth, defaultHostHeight)
}
origin := h.Bounds().Min
rects := h.computeZoneRects(totalSize, origin)
// Layout zone contents.
for z := Zone(0); z < zoneCount; z++ {
h.zones[z].bounds = rects[z]
h.layoutZone(ctx, z, rects[z])
}
// Layout center content.
if h.cfg.centerContent != nil {
centerRect := rects[Center]
cc := geometry.Tight(centerRect.Size())
h.cfg.centerContent.Layout(ctx, cc)
if setter, ok := h.cfg.centerContent.(interface{ SetBounds(geometry.Rect) }); ok {
setter.SetBounds(centerRect)
}
}
return totalSize
}
func LeftRatio
LeftRatio sets the size ratio of the left zone (0.0 to 1.0).
Default is 0.2 (20% of available width).
func LeftRatio(r float32) HostOption {
return func(c *hostConfig) { c.leftRatio = clampRatio(r) }
}
func MovePanel
MovePanel moves a panel from its current zone to a new zone.
Returns true if the panel was found and moved.
func (h *Host) MovePanel(panel *Panel, targetZone Zone) bool {
if panel == nil || int(targetZone) >= zoneCount {
return false
}
// Find current zone.
found := false
for z := Zone(0); z < zoneCount; z++ {
if h.zones[z].containsPanel(panel) {
found = true
break
}
}
if !found {
return false
}
h.undockFromAll(panel)
h.zones[targetZone].addPanel(panel)
return true
}
func NewHost
NewHost creates a new dock host with the given options.
The returned widget is visible and enabled by default.
func NewHost(opts ...HostOption) *Host {
h := &Host{
painter: DefaultPainter{},
}
h.SetVisible(true)
h.SetEnabled(true)
h.cfg.leftRatio = defaultEdgeRatio
h.cfg.rightRatio = defaultEdgeRatio
h.cfg.topRatio = defaultEdgeRatio
h.cfg.bottomRatio = defaultEdgeRatio
for _, opt := range opts {
opt(&h.cfg)
}
if h.cfg.painter != nil {
h.painter = h.cfg.painter
}
// ADR-028: parent chain for upward dirty propagation.
// Flutter: RenderObject.adoptChild sets parent on each child.
if h.cfg.centerContent != nil {
type parentSetter interface{ SetParent(widget.Widget) }
if ps, ok := h.cfg.centerContent.(parentSetter); ok {
ps.SetParent(h)
}
}
return h
}
func OnPanelClose
OnPanelClose sets the callback invoked when a panel's close button is clicked.
The callback receives the panel and the zone it belonged to.
func OnPanelClose(fn func(panel *Panel, zone Zone)) HostOption {
return func(c *hostConfig) { c.onPanelClose = fn }
}
func PainterOpt
PainterOpt sets the painter used to render zone borders and tab headers.
If not set, [DefaultPainter] is used.
func PainterOpt(p Painter) HostOption {
return func(c *hostConfig) { c.painter = p }
}
func PanelCount
PanelCount returns the number of panels in the given zone.
func (h *Host) PanelCount(zone Zone) int {
if int(zone) >= zoneCount {
return 0
}
return len(h.zones[zone].panels)
}
func PanelZone
PanelZone returns the zone a panel is docked to, and whether it was found.
func (h *Host) PanelZone(panel *Panel) (Zone, bool) {
if panel == nil {
return 0, false
}
for z := Zone(0); z < zoneCount; z++ {
if h.zones[z].containsPanel(panel) {
return z, true
}
}
return 0, false
}
func RightRatio
RightRatio sets the size ratio of the right zone (0.0 to 1.0).
Default is 0.2 (20% of available width).
func RightRatio(r float32) HostOption {
return func(c *hostConfig) { c.rightRatio = clampRatio(r) }
}
func SetActivePanelIndex
SetActivePanelIndex sets the active tab index for the given zone.
Does nothing if the index is out of range.
func (h *Host) SetActivePanelIndex(zone Zone, idx int) {
if int(zone) >= zoneCount {
return
}
g := &h.zones[zone]
if idx < 0 || idx >= len(g.panels) {
return
}
g.activeIdx = idx
}
func TopRatio
TopRatio sets the size ratio of the top zone (0.0 to 1.0).
Default is 0.2 (20% of available height).
func TopRatio(r float32) HostOption {
return func(c *hostConfig) { c.topRatio = clampRatio(r) }
}
func Undock
Undock removes a panel from its current zone.
Returns true if the panel was found and removed.
func (h *Host) Undock(panel *Panel) bool {
if panel == nil {
return false
}
removed := h.undockFromAll(panel)
// ADR-028: clear parent on removal.
if removed {
if content := panel.Content(); content != nil {
type parentSetter interface{ SetParent(widget.Widget) }
if ps, ok := content.(parentSetter); ok {
ps.SetParent(nil)
}
}
}
return removed
}
Structs
type Host struct
Host is the root container managing the dock layout.
It arranges docked panels into five zones (Left, Right, Top, Bottom, Center)
and renders tab headers for zones with multiple panels.
A host is created with [NewHost] using functional options:
host := docking.NewHost(
docking.CenterContent(mainEditor),
docking.LeftRatio(0.25),
)
type Host struct {
widget.WidgetBase
cfg hostConfig
painter Painter
// Zone groups, indexed by Zone constant.
zones [zoneCount]group
// Tab interaction state per zone.
tabStates [zoneCount][]ZoneTabState
}
ActivePanelIndex returns the active tab index for the given zone.
Returns -1 if the zone is empty.