Repository: fyne-io/fyne
File: container/navigation.go (Go)

Code before:
	fyne.CanvasObject
	fyne.Disableable
	fyne.Tappable
}

// NewNavigation creates a new navigation container with a given root object.
//
// Since: 2.6
func NewNavigation(root fyne.CanvasObject) *Navigation {
	return NewNavigationWithTitle(root, "")
}

// NewNavigationWithTitle creates a new navigation container with a given root object and a default title.
//
// Since: 2.6
func NewNavigationWithTitle(root fyne.CanvasObject, s string) *Navigation {
	nav := &Navigation{
		Root:  root,
		Title: s,
		Label: widget.NewLabelWithStyle(s, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
	}
	nav.setup()
	nav.Back = widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() { _ = nav.Pop() })
	nav.Back.Disable()
	nav.Next = widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() { _ = nav.Forward() })
	nav.Next.Disable()

	return nav
}

// Push puts the given object on top of the navigation stack and hides the object below.
//
// Since: 2.6
func (nav *Navigation) Push(obj fyne.CanvasObject) {
	s := nav.Title
	if nav.level > 0 {
		s = nav.titles[nav.level-1]
	}
	nav.PushWithTitle(obj, s)
}

// PushWithTitle puts the given CanvasObject on top, hides the object below, and uses the given title as label text.
//
// Since: 2.6
func (nav *Navigation) PushWithTitle(obj fyne.CanvasObject, s string) {
	objs := nav.stack.Objects[:nav.level]
	if len(objs) > 0 {
		objs[len(objs)-1].Hide()
	}
	nav.stack.Objects = append(objs, obj)
	if len(nav.stack.Objects) > 0 {

Review comment (suggestion):
This doesn't quite solve the review comment about making sure that the type should be usable from the struct. All fields or exported and part of the struct so sure, you can use the struct but there is a lot of setup required. Have a look at how https://github.com/Jacalz/rymdport/blob/rewrite/internal/ui/components/stack.go has all of the widgets internal in the renderer and handles the setup of them in the `CreateRenderer` call. That is more consistent with out other APIs and avoid the developer having to do a lot of setup to get the API to work.

Basically, let the renderer contain the internal widgets and do most of the setup in the `CreateRenderer` call. If the data inside the Navigation struct changes after a refresh, the methods on our custom renderer change the contents of our internal widgets to reflect the change.

Code after:
// NewNavigationWithTitle creates a new navigation container with a given root object and a default title.
//
// Since: 2.7
func NewNavigationWithTitle(root fyne.CanvasObject, s string) *Navigation {
	var nav *Navigation
	nav = &Navigation{
		Root:      root,
		Title:     s,
		OnBack:    func() { _ = nav.Back() },
		OnForward: func() { _ = nav.Forward() },
	}
	return nav
}

// Push puts the given object on top of the navigation stack and hides the object below.
//
// Since: 2.7
func (nav *Navigation) Push(obj fyne.CanvasObject) {
	nav.PushWithTitle(obj, nav.Title)
}

// PushWithTitle puts the given CanvasObject on top, hides the object below, and uses the given title as label text.
//
// Since: 2.7
func (nav *Navigation) PushWithTitle(obj fyne.CanvasObject, s string) {
	obj.Show()
	objs := nav.stack.Objects[:nav.level]
	if len(objs) > 0 {
		objs[len(objs)-1].Hide()
	}
	nav.stack.Objects = append(objs, obj)
	nav.titles = append(nav.titles[:nav.level], s)
	nav.level++
	nav.Refresh()
}

// Back returns the top level CanvasObject, adjusts the title accordingly, and disabled the back button
// when no more objects are left to go back to.
//
// Since: 2.7
func (nav *Navigation) Back() fyne.CanvasObject {
	if nav.level == 0 || nav.level == 1 && nav.Root != nil {
		return nil
	}

	objs := nav.stack.Objects
	objs[nav.level-1].Hide()
	if nav.level > 1 {
		objs[nav.level-2].Show()
	}