core/radio/group.go

Functions Structs

Functions

func Children

Children returns the items as a slice of widget.Widget.

func (g *Group) Children() []widget.Widget {
	g.mu.RLock()
	defer g.mu.RUnlock()

	children := make([]widget.Widget, len(g.items))
	for i, it := range g.items {
		children[i] = it
	}
	return children
}

func Draw

Draw renders all items to the canvas.

func (g *Group) Draw(ctx widget.Context, canvas widget.Canvas) {
	// Copy items under lock, then draw without holding the lock.
	// Item.Draw calls g.isSelected which acquires RLock — holding
	// RLock here would deadlock if a Lock waiter exists (RWMutex
	// is not reentrant in Go).
	g.mu.RLock()
	items := make([]*Item, len(g.items))
	copy(items, g.items)
	g.mu.RUnlock()

	// Items are positioned in local coordinates (0,0)-based.
	// Push the group's position so items render at the correct screen location.
	canvas.PushTransform(g.Bounds().Min)
	for _, it := range items {
		widget.StampScreenOrigin(it, canvas)
		it.Draw(ctx, canvas)
	}
	canvas.PopTransform()
}

func Event

Event handles an input event by delegating to the appropriate item.

func (g *Group) Event(ctx widget.Context, e event.Event) bool {
	// Copy items under lock, then dispatch without holding the lock.
	// Item event handlers may call back into Group (selectValue),
	// which requires a write lock — holding RLock here would deadlock.
	g.mu.RLock()
	items := make([]*Item, len(g.items))
	copy(items, g.items)
	g.mu.RUnlock()

	// For mouse events, translate to Group-local coordinates and hit-test.
	// This mirrors PushTransform(g.Bounds().Min) used in Draw.
	// MouseEnter/MouseLeave are about the Group container, not items —
	// items get their own Enter/Leave from updateHover. Do NOT forward
	// these to children, otherwise items set Pointer cursor on the
	// entire container area.
	if me, ok := e.(*event.MouseEvent); ok {
		if me.MouseType == event.MouseEnter || me.MouseType == event.MouseLeave {
			return false
		}
		local := *me
		local.Position = me.Position.Sub(g.Bounds().Min)
		for _, it := range items {
			if it.Bounds().Contains(local.Position) && it.Event(ctx, &local) {
				return true
			}
		}
		return false
	}

	for _, it := range items {
		if it.Event(ctx, e) {
			return true
		}
	}
	return false
}

func IsFocusable

IsFocusable reports whether the group itself can receive focus.

The group delegates focus to its items, so it always returns false.

func (g *Group) IsFocusable() bool {
	return false
}

func ItemAt

ItemAt returns the item at the given index.

Panics if index is out of range.

func (g *Group) ItemAt(index int) *Item {
	g.mu.RLock()
	defer g.mu.RUnlock()
	return g.items[index]
}

func ItemCount

ItemCount returns the number of items in the group.

func (g *Group) ItemCount() int {
	g.mu.RLock()
	defer g.mu.RUnlock()
	return len(g.items)
}

func Layout

Layout calculates the group's preferred size by laying out all items.

func (g *Group) Layout(ctx widget.Context, constraints geometry.Constraints) geometry.Size {
	g.mu.RLock()
	defer g.mu.RUnlock()

	if len(g.items) == 0 {
		return geometry.Size{}
	}

	var preferred geometry.Size
	if g.cfg.direction == Vertical {
		preferred = g.layoutVertical(ctx, constraints)
	} else {
		preferred = g.layoutHorizontal(ctx, constraints)
	}

	return constraints.Constrain(preferred)
}

func Mount

Mount creates signal bindings for push-based invalidation.

Implements [widget.Lifecycle].

func (g *Group) Mount(ctx widget.Context) {
	sched := ctx.Scheduler()
	if sched == nil {
		return
	}
	if g.cfg.selectedSignal != nil {
		b := state.BindToScheduler(g.cfg.selectedSignal, g, sched)
		g.AddBinding(b)
	}
	if g.cfg.readonlyDisabledSig != nil {
		b := state.BindToScheduler(g.cfg.readonlyDisabledSig, g, sched)
		g.AddBinding(b)
	} else if g.cfg.disabledSignal != nil {
		b := state.BindToScheduler(g.cfg.disabledSignal, g, sched)
		g.AddBinding(b)
	}
}

func NewGroup

NewGroup creates a new radio Group with the given options.

 

The returned group is visible, enabled, and lays out items vertically

by default. Use options to configure items, selected value, direction,

and change handler.

func NewGroup(opts ...GroupOption) *Group {
	g := &Group{
		selected: -1,
	}
	g.SetVisible(true)
	g.SetEnabled(true)

	for _, opt := range opts {
		opt(&g.cfg)
	}

	// Determine the painter.
	painter := Painter(DefaultPainter{})
	if g.cfg.painter != nil {
		painter = g.cfg.painter
	}

	// Create items from definitions.
	for _, def := range g.cfg.items {
		it := newItem(def, g, painter)
		g.items = append(g.items, it)
	}

	// Apply initial selection.
	// Use ResolvedSelected to support signal-based initial values.
	initialSelected := g.cfg.ResolvedSelected()
	if initialSelected != "" {
		for i, it := range g.items {
			if it.value == initialSelected {
				g.selected = i
				break
			}
		}
	}

	// ADR-028: parent chain for upward dirty propagation.
	// Flutter: RenderObject.adoptChild sets parent on each child.
	for _, it := range g.items {
		it.SetParent(g)
	}

	return g
}

func Select

Select programmatically selects the item with the given value.

If no item matches the value, the selection is cleared.

func (g *Group) Select(value string) {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.setSelectedLocked(value)
}

func Selected

Selected returns the value of the currently selected item.

When a [SelectedSignal] is bound, the signal value is returned directly.

Returns an empty string if no item is selected.

func (g *Group) Selected() string {
	g.mu.RLock()
	defer g.mu.RUnlock()
	if g.cfg.selectedSignal != nil {
		return g.cfg.selectedSignal.Get()
	}
	if g.selected < 0 || g.selected >= len(g.items) {
		return ""
	}
	return g.items[g.selected].value
}

func Unmount

Unmount is called when the radio group is removed from the widget tree.

Implements [widget.Lifecycle].

func (g *Group) Unmount() {
	// Bindings are cleaned up automatically by WidgetBase.CleanupBindings().
}

Structs

type Group struct

Group is a container widget that holds a set of mutually-exclusive radio items.

 

Exactly one item may be selected at a time. Create a group with [NewGroup]:

 

rg := radio.NewGroup(

radio.Items(

radio.ItemDef{Value: "s", Label: "Small"},

radio.ItemDef{Value: "m", Label: "Medium"},

),

radio.Selected("m"),

radio.OnChange(func(v string) { fmt.Println("selected:", v) }),

)

type Group struct {
	widget.WidgetBase
	mu		sync.RWMutex
	cfg		groupConfig
	items		[]*Item
	selected	int	// index of selected item, -1 = none
}