From 82f06cb6668032d0be99f22d01ba6a77fee62e58 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sun, 5 Jan 2025 12:58:46 +0100 Subject: [PATCH] Implement a stack navigator --- internal/ui/components/stack.go | 145 ++++++++++++++++++++++++++------ internal/ui/recv.go | 18 +++- internal/ui/send.go | 30 ++++++- internal/ui/setup.go | 11 ++- 4 files changed, 167 insertions(+), 37 deletions(-) diff --git a/internal/ui/components/stack.go b/internal/ui/components/stack.go index e2fdfce2..d64c1de1 100644 --- a/internal/ui/components/stack.go +++ b/internal/ui/components/stack.go @@ -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) } diff --git a/internal/ui/recv.go b/internal/ui/recv.go index c6b21940..56c2c07a 100644 --- a/internal/ui/recv.go +++ b/internal/ui/recv.go @@ -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)) diff --git a/internal/ui/send.go b/internal/ui/send.go index b2795b8e..26bceb0c 100644 --- a/internal/ui/send.go +++ b/internal/ui/send.go @@ -1,6 +1,8 @@ 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" @@ -8,16 +10,36 @@ import ( "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) diff --git a/internal/ui/setup.go b/internal/ui/setup.go index 9943a10c..65c64374 100644 --- a/internal/ui/setup.go +++ b/internal/ui/setup.go @@ -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" @@ -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 }