Skip to content

Commit

Permalink
Implement a stack navigator
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacalz committed Jan 5, 2025
1 parent 2deb552 commit 82f06cb
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 37 deletions.
145 changes: 118 additions & 27 deletions internal/ui/components/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,143 @@ package components

import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)

var _ fyne.Widget = (*StackNavigator)(nil)

// StackNavigator represents a stack-based navigation manager
type StackNavigator struct {
stack []fyne.CanvasObject
current int
OnBack func()
widget.BaseWidget
stack []fyne.CanvasObject
titles []string
OnBack func()
}

// NewNavigator creates a new Navigator instance.
func NewNavigator(initialPage fyne.CanvasObject) *StackNavigator {
return &StackNavigator{stack: []fyne.CanvasObject{initialPage}}
func NewNavigator(initial fyne.CanvasObject) *StackNavigator {
return &StackNavigator{stack: []fyne.CanvasObject{initial}, titles: []string{""}}
}

// Push adds a new page to the stack and displays it.
func (n *StackNavigator) Push(page fyne.CanvasObject, title string) {
n.stack = append(n.stack, page)
n.titles = append(n.titles, title)
n.Refresh()
}

// Next moves the view to the next view in the stack without adding contents.
// This allows viewes to move forwards through views without recreating each time.
func (n *StackNavigator) Next() {
if n.current == len(n.stack)-1 {
return
// Pop removes the current page and returns to the previous one.
func (n *StackNavigator) Pop() {
if len(n.stack) <= 1 {
return // Prevent popping the last page.
}

n.current++
n.stack[len(n.stack)-1] = nil
n.stack = n.stack[:len(n.stack)-1]
n.titles = n.titles[:len(n.titles)-1]

n.Refresh()
}

// Previous moves the view to the previous view in the stack without removing contents.
// This allows viewes to move backwards through views without recreating each time.
func (n *StackNavigator) Previous() {
if n.current == 0 || len(n.stack) < 1 {
func (n *StackNavigator) MinSize() fyne.Size {
n.ExtendBaseWidget(n)
return n.BaseWidget.MinSize()
}

// CreateRenderer creats the stackNavigatorRenderer.
func (n *StackNavigator) CreateRenderer() fyne.WidgetRenderer {
renderer := &stackNavigatorRenderer{
parent: n,
backButton: widget.Button{
Icon: theme.NavigateBackIcon(),
Text: "Go back",
Importance: widget.LowImportance,
OnTapped: n.OnBack,
},
titleLabel: widget.Label{
Text: n.titles[len(n.titles)-1],
TextStyle: fyne.TextStyle{Bold: true},
Alignment: fyne.TextAlignCenter,
},
}

n.current--
renderer.backButton.Hidden = len(n.stack) == 1
renderer.titleLabel.Hidden = renderer.backButton.Hidden
renderer.separator.Hidden = renderer.backButton.Hidden

renderer.objects = []fyne.CanvasObject{&renderer.backButton, &renderer.titleLabel, &renderer.separator, n.stack[len(n.stack)-1]}
return renderer
}

// Push adds a new page to the stack and displays it.
func (n *StackNavigator) Push(page fyne.CanvasObject) {
n.stack = append(n.stack, page)
n.Next()
var _ fyne.WidgetRenderer = (*stackNavigatorRenderer)(nil)

type stackNavigatorRenderer struct {
parent *StackNavigator
objects []fyne.CanvasObject

backButton widget.Button
titleLabel widget.Label
separator widget.Separator
}

// Pop removes the current page and returns to the previous one.
func (n *StackNavigator) Pop() {
if len(n.stack) <= 1 {
return // Prevent popping the last page
func (r *stackNavigatorRenderer) Destroy() {
}

// Layout is a hook that is called if the widget needs to be laid out.
// This should never call [Refresh].
func (r *stackNavigatorRenderer) Layout(size fyne.Size) {
contentStartsAt := float32(0)
if len(r.parent.stack) > 1 {
r.backButton.Move(fyne.Position{})
buttonSize := r.backButton.MinSize()
r.backButton.Resize(buttonSize)

labelSize := r.titleLabel.MinSize()
r.titleLabel.Move(fyne.NewPos((size.Width-labelSize.Width)/2, 0))
r.titleLabel.Resize(labelSize)

contentStartsAt = buttonSize.Height + theme.Padding()

r.separator.Move(fyne.Position{Y: contentStartsAt})
r.separator.Resize(fyne.NewSize(size.Width, theme.SeparatorThicknessSize()))

}

n.stack[len(n.stack)-1] = nil
n.stack = n.stack[:len(n.stack)-1]
n.Previous()
r.objects[3].Move(fyne.NewPos(0, contentStartsAt))
r.objects[3].Resize(size.SubtractWidthHeight(0, contentStartsAt))
}

// MinSize returns the minimum size of the widget that is rendered by this renderer.
func (r *stackNavigatorRenderer) MinSize() fyne.Size {
minSize := r.objects[3].MinSize()
if len(r.parent.stack) > 1 {
return minSize.AddWidthHeight(0, r.backButton.MinSize().Height+theme.Padding())
}

return minSize
}

// Objects returns all objects that should be drawn.
func (r *stackNavigatorRenderer) Objects() []fyne.CanvasObject {
return r.objects
}

// Refresh is a hook that is called if the widget has updated and needs to be redrawn.
// This might trigger a [Layout].
func (r *stackNavigatorRenderer) Refresh() {
r.titleLabel.Text = r.parent.titles[len(r.parent.titles)-1]
r.titleLabel.Hidden = len(r.parent.stack) == 1
r.titleLabel.Refresh()

r.backButton.Hidden = r.titleLabel.Hidden
r.backButton.Refresh()

r.separator.Hidden = r.titleLabel.Hidden
r.separator.Refresh()

r.objects[3] = r.parent.stack[len(r.parent.stack)-1]

canvas.Refresh(r.parent)
}
18 changes: 15 additions & 3 deletions internal/ui/recv.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
package ui

import (
"github.com/Jacalz/rymdport/v3/internal/ui/components"
"github.com/Jacalz/rymdport/v3/internal/util"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/Jacalz/rymdport/v3/internal/util"
)

func createRecvPage(_ fyne.App, _ fyne.Window) fyne.CanvasObject {
func buildRecvView() fyne.CanvasObject {
return widget.NewLabel("Receiving will be implemented soon...")
}

func createRecvPage(navigator *components.StackNavigator) fyne.CanvasObject {
icon := canvas.NewImageFromResource(theme.DownloadIcon())
icon.FillMode = canvas.ImageFillContain
icon.SetMinSize(fyne.NewSquareSize(200))

description := &widget.Label{Text: "Enter a code below to start receiving data.", Alignment: fyne.TextAlignCenter}

recvView := buildRecvView()
code := &widget.Entry{PlaceHolder: "Code from sender", Validator: util.CodeValidator}
start := &widget.Button{Text: "Start Receive", Icon: theme.DownloadIcon(), Importance: widget.HighImportance}
start := &widget.Button{
Text: "Start Receive",
Icon: theme.DownloadIcon(),
Importance: widget.HighImportance,
OnTapped: func() { navigator.Push(recvView, "Receiving Data") },
}

content := container.NewVBox(icon, description, &widget.Separator{}, code, &widget.Separator{}, container.NewCenter(start))

Expand Down
30 changes: 26 additions & 4 deletions internal/ui/send.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
package ui

import (
"github.com/Jacalz/rymdport/v3/internal/ui/components"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)

func createSendPage(_ fyne.App, _ fyne.Window) fyne.CanvasObject {
func buildSendView() fyne.CanvasObject {
return widget.NewLabel("Sending will be implemented soon...")
}

func createSendPage(navigator *components.StackNavigator) fyne.CanvasObject {
icon := canvas.NewImageFromResource(theme.UploadIcon())
icon.FillMode = canvas.ImageFillContain
icon.SetMinSize(fyne.NewSquareSize(200))

description := &widget.Label{Text: "Select data type below or drop files here.", Alignment: fyne.TextAlignCenter}

file := &widget.Button{Icon: theme.FileTextIcon(), Text: "Send File", Importance: widget.HighImportance}
folder := &widget.Button{Icon: theme.FolderIcon(), Text: "Send Folder", Importance: widget.HighImportance}
text := &widget.Button{Icon: theme.DocumentIcon(), Text: "Send Text", Importance: widget.HighImportance}
sendView := buildSendView()
file := &widget.Button{
Icon: theme.FileTextIcon(),
Text: "Send File",
Importance: widget.HighImportance,
OnTapped: func() { navigator.Push(sendView, "Sending File") },
}
folder := &widget.Button{
Icon: theme.FolderIcon(),
Text: "Send Folder",
Importance: widget.HighImportance,
OnTapped: func() { navigator.Push(sendView, "Sending Folder") },
}
text := &widget.Button{
Icon: theme.DocumentIcon(),
Text: "Send Text",
Importance: widget.HighImportance,
OnTapped: func() { navigator.Push(sendView, "Sending Text") },
}

buttons := container.NewCenter(container.NewHBox(file, &widget.Separator{}, folder, &widget.Separator{}, text))
content := container.NewVBox(icon, description, &widget.Separator{}, buttons)
Expand Down
11 changes: 8 additions & 3 deletions internal/ui/setup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ui

import (
"github.com/Jacalz/rymdport/v3/internal/ui/components"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
Expand All @@ -16,13 +18,16 @@ func Create(a fyne.App, w fyne.Window) fyne.CanvasObject {
widget.ShowPopUpMenuAtRelativePosition(menu, w.Canvas(), offset, dropdown)
}

navigator := &components.StackNavigator{}
navigator.OnBack = navigator.Pop
tabs := &container.AppTabs{
Items: []*container.TabItem{
{Text: "Send", Icon: theme.UploadIcon(), Content: createSendPage(a, w)},
{Text: "Receive", Icon: theme.DownloadIcon(), Content: createRecvPage(a, w)},
{Text: "Send", Icon: theme.UploadIcon(), Content: createSendPage(navigator)},
{Text: "Receive", Icon: theme.DownloadIcon(), Content: createRecvPage(navigator)},
},
}

upperRightCorner := container.NewBorder(container.NewBorder(nil, nil, nil, dropdown), nil, nil, nil)
return container.NewStack(tabs, upperRightCorner)
navigator.Push(container.NewStack(tabs, upperRightCorner), "")
return navigator
}

0 comments on commit 82f06cb

Please sign in to comment.