-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of navigation container #5500
base: develop
Are you sure you want to change the base?
Conversation
// Objects can be any CanvasObject, and only the most recent one will be visible. | ||
// | ||
// Since: 2.6 | ||
type Navigation struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way this is written it can only be constructed via NewNavigation
and not easily extended, or constructed by a struct literal instead, as has currently been a requirement for all builtin Fyne widgets. Most of this struct's contents may be able to move to the renderer instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Have to look from that angle again and compare to other widgets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs more thought than I expected to make sure everything is initialized correctly, but I started making some parts public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it working by adding a setup method and a bunch of checks.
"fyne.io/fyne/v2/widget" | ||
) | ||
|
||
// Navigation container is used to provide your application with a control bar and an area for content objects. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the idea is for this to be the final API, it should probably be called StackNavigation, to distinguish it from other navigation schemes, such as a browser-like queue of past pages that can be navigated back and forward. Unless the idea is to make this single component support multiple navigation schemes in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But couldn't you build a browser back/forward with this too? It could be extended to have forward navigation without needing Stack vs not-stack navigation.
Conceptually it feels like the browser is a stack as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that really the best option? Do we know that all the other navigators will have the same API?
With more and more types in the container package that actually aren't simple containers but technically widgets, I wonder if a "navigation" package would make sense? Tabs would fit in quite well there as well, I'd think. Just an idea to bring up before we commit to anything :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that tabs fit a mental model of navigation - you're not "going" anywhere...
Container has diversified but the general concept of "you put other widgets in there" mostly holds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really think you could build a browser-like navigation with this component without just forking it and adding a different API - the browser navigation model is only a bit "stack-like", but it's really more of a list with a pointer that can advance back and forward, and when you push a new page, it deletes the whole "tail" of the history after the current pointer position. (ie back, back, back, new page would remove the last 3 items in the old history)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now considering to change the logic to support an optional "Next" button.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that mean the naming works now? If it needs a more explicit name then OK but I'm not convinced that "Stack" or "Simple" add anything meaningful to a developer's understanding of what it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the naming works if we plan to add (at least eventually) the forward navigation as well. I wonder if users may also confuse this with a "routing" system though, which would be more complex still.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The forward navigation is now in the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with this now. If a routing system is to be added that would not be presented as a navigation container in this sense - more likely it would have to be a parser of path through a UI which then populates the various containers / tabs along the way to that content.
Maybe that's overly complex but I'm happy that routing is not a widget/container concern so there is no name conflict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A very useful addition. I agree with the comments from Drew but I also added some tips/suggestions inline.
container/navigation.go
Outdated
nav.button.OnTapped = func() { | ||
nav.Pop() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nav.button.OnTapped = func() { | |
nav.Pop() | |
} | |
nav.button.OnTapped = nav.Pop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Different function signature, forgot to explicitly ignore the return value. Still raising the API question, should Pop() return the object?
container/navigation.go
Outdated
// | ||
// Since: 2.6 | ||
func (nav *Navigation) Push(obj fyne.CanvasObject) { | ||
nav.PushWithTitle(obj, nav.titles[len(nav.titles)-1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will crash if there are 0 titles? Or can that not happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't happen, but it actually points at something related I was wondering: can the stack be empty or is there always at least one element? Or should it be configurable?
FYI: This likely closes #1166. |
What is the status of this? We have just over 2 days to get any new API in for 2.6. I'm happy with this change but given the history of the feature I don't feel it would be appropriate for me to accept it. |
The comment by Drew about it only being possible to use this when created using the constructor is still a valid one in my opinion and it hasn't been resolved. |
Agree - though I think Navigation.stack may be the last one left that needs work for its zero value to be valid/not crash |
Thanks, sounds like a good summary - @sdassow is this something you can resolve before Friday? |
If it helps, my implementation at https://github.com/Jacalz/rymdport/blob/rewrite/internal/ui/components/stack.go does not have that issue. Feel free to use it as inspiration if it helps. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm so sorry for the late review. I really like the approach of the global title and the root object being handled like they are now; it is a big improvement over my navigation container. The support for moving forward to an object that already is added is nice. I think we also want a method to go back without popping?
However, I am a bit sceptical to there being a lot of complexity and manual setup required for a developer to use this (especially when using the struct). It does not quite work like how many of our other APIs work in this regard; it feels to me like internal details are exposed as exported fields in the main struct. I left a few comments inline to try and explain more thoroughly what I mean. I propose handling our internal widgets inside the renderer (by not using a simple renderer), slimming down the custom navigation objects into creating our own buttons internally and letting the developer set OnNext
and OnBack
functions if they want a different behaviour than default.
container/navigation.go
Outdated
Title string | ||
Back NavigationObject | ||
Next NavigationObject | ||
Label *widget.Label |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems quite inconsistent with other APIs we have and "Label" is not a descriptive name for where this label ends up. Can we not use a string field for what this text is intended to do and let the internals be managed in our renderer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, string
is what I would expect here too
container/navigation.go
Outdated
// NavigationObject allows using any object implementing the `fyne.Disableable`, | ||
// `fyne.CanvasObject`, and `fyne.Tappable` interfaces to be used as Back/Next | ||
// navigation control. | ||
// | ||
// Since: 2.6 | ||
type NavigationObject interface { | ||
fyne.CanvasObject | ||
fyne.Disableable | ||
fyne.Tappable | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like indicated above, I'm sceptical about this interface and the custom solution for changing the backwards and next buttons. I'd prefer to just have an OnBack
and an OnNext
function to keep things simple. I'd rather begin without this and we can add this later if there is enough demand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point - but is slightly ironic because the "forward nagivation" was added in response to it not being sufficient to be called Navigation
without it - so we might need a little discussion to agree the scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually maybe I misunderstood - with or without forward navigation this interface should not be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't really like it either - now gone.
container/navigation.go
Outdated
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adopted this approach, thanks!
container/navigation.go
Outdated
if nav.Label != nil { | ||
control.Objects = append(control.Objects, nav.Label) | ||
} | ||
return widget.NewSimpleRenderer(NewBorder(control, nil, nil, nil, &nav.stack)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the comments above, I think we basically can't use the simple renderer. There needs to be an actual custom renderer with the internal widgets and properties reachable so we can have the methods on that renderer change the internal widgets when values on our widget is changed. You can use https://github.com/Jacalz/rymdport/blob/4707bc88f31670e2de0293f914f0894e30b78755/internal/ui/components/stack.go#L79 for inspiration.
container/navigation.go
Outdated
Back NavigationObject | ||
Next NavigationObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this likely adds more complexity over having the CreateRenderer just create buttons internally and let the developer wire up functions for what should happen on next and back. Just doing OnBack func()
and OnNext func()
will likely suffice. There are two options for that approach:
- Always show backwards and forwards buttons and use a sensible approach for selecting what they do when no functions are set (just moving backwards and forwards).
- Don't show buttons at all when functions are not set.
Label *widget.Label | ||
|
||
level int | ||
stack fyne.Container |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I do like this approach, it might make more sense (and be more consistent) with just a []fyne.CanvasObject
slice and have the container handled in the renderer. See comment further down.
container/navigation.go
Outdated
return objs[nav.level] | ||
} | ||
|
||
// Forward shows the next object in the again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a word missing here at the end.
container/navigation.go
Outdated
|
||
Root fyne.CanvasObject | ||
Title string | ||
Back NavigationObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of exporting these objects either - a standard back and forward buttons should be sufficient
// Forward shows the next object in the again. | ||
// | ||
// Since: 2.6 | ||
func (nav *Navigation) Forward() fyne.CanvasObject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we could do with a matching Back
as well?
Perhaps indicating in Push/Pop being about manipulating history and Back/Forward about traversing it?
…nderer, adjust tests, and add missing word in documentation
Description:
This add a basic navigation container type with back button and title. Sponsored by Fyne Labs.
How it looks like:
Screen.Recording.2025-02-04.at.17.37.28.mov
Example code:
Checklist:
Where applicable: