Hey there! What’s the Compose way of sharing focus...
# compose-desktop
m
Hey there! What’s the Compose way of sharing focus between two UI elements? I’m trying to replicate this view which shares focus between an input and a list so that the user can both type a search query and use the arrow keys and return to select results without switching back and forth via ⇥
v
I don't unfortunately have a solution but I am interested if there's a native way to do this
r
Not sure if there's a different idiomatic way in compose, but generally (in other UI frameworks) the idea is to capture up and down arrow key press events in the input and update the selected index of the list accordingly. It's not "shared focus", as only the input is focused, but there's nothing stopping input on one element to impact the state elsewhere in the app (that's kind of the whole point of event handling).
m
Thanks @Ruckus! I was hoping for there to be a composable way where I don’t have to know about the keys that I need to send elsewhere, and where I don’t have to fake focus styling in one part. The problem is that there might be special key handlers and different child components in the list items of the result list, which should be encapsulated there with their own key handlers and based on their own focus state. But neither will work if I have to intercept key events at the top level and if the list doesn’t actually receive focus
r
The problem is that there might be special key handlers...
What do you mean by this? If you're talking about a list view that you create, you could just extract the handlers into functions and call them from both the list or the input depending on who has focus. If you're talking about arbitrary composables, that's a whole different can of worms. A workaround I've used in other UI toolkits is to grab a reference to the target node (e.g. the list) and call it's event handler directly passing in either the event captured from the input, or a new event I constructed for more fine grained control. However, considering UI nodes are functions instead of objects in Compose, I'm not sure what the correct way to go about doing that is.
In both of these cases, I think it's important to point out that there's still only one element with focus. While the idea of multiple focus (each element handling the input it's designed to and ignoring the rest) may sound good at first glance, it has many issues that pile up rapidly. What if multiple elements can handle the same event? Who gets priority? How do you handle consuming events? Or filtering? What about "partial" events (e.g. one element is watching for Ctrl+R, while another is consuming all Ctrl presses)? etc.
m
I have answers to all these questions already, and yes, it’s about arbitrary composition exactly
The difficulties you’re describing regarding more complex event handling aren’t much different from nested key handlers
r
There is one big difference in that nested handlers are structured. They have to go up and down the tree, so handling order can be deterministic and easy to reason about, whereas arbitrary elements are not.
m
Arbitrary elements can be enumerated in DFS order
r
Sure, but what about two siblings. It isn't clear who gets priority.
m
Why? DFS order makes this very clear
Siblings have an order, and the children of the first sibling come before the second sibling on the way down, and vice versa on the way up
r
Siblings have an order
That's my point, the order often isn't as clear as it may seem. The obvious example is whatever order they are defined in the code, but that might not line up with many users expectations, such as what is visible on the screen, or where elements appear on the screen. But all of this is arguably beside the point, as the fundamental idea of having multiple elements focused is problematic, as that kind of defeats the whole point of focus in the first place, and can be fairly easily worked around using the methods I mentioned earlier, without having to answer any of these questions.
(It's also interesting to note there are UI frameworks that do not use trees at all for their UI, though that's not really important here as Compose does. Just kind of fun to think about how things work in those cases.)
m
Aren’t you expecting to be able to both type and move the selection in macOS Spotlight, or the completion in any IDE, or even in your browser address bar?
r
Sure, but that's not multiple focus. In all of those cases, the focus in on the input, and the appropriate functions are called from the input's event handler. There is still only one thing focused at any given time.
m
That’s one way of implementing it, but that doesn’t compose. That’s my whole point. I want to be able to plug any component into a list entry and the component should be able to query the focus API just like any other
r
The same argument applies to input validation, or clicking a button to show or hide parts of the input, or anything on the UI whose interaction modifies anything else on the UI. It's not multiple focus, it's just event handling.
but that doesn’t compose ... I want to be able to plug any component into a list entry and the component should be able to query the focus API just like any other
I don't understand what you mean by "query the focus API just like any other". I think you may be conflating the focus with the selection, but I could be wrong. Can you elaborate?
m
I don’t understand how these examples relate to the point. None of them actually need to be active at the same time
r
None of them actually need to be active at the same time
Yes, that's exactly my point.
m
How does bringing up unrelated examples prove your point?
r
My point is that all of these are essentially the same thing, and your use case doesn't require multiple focus. It's just another example of one elements interaction changing the state of the app that gets represented elsewhere.
m
No use case requires a built-in focus system or even key handlers in any nested position. I can also hard-code all key handlers at the window root and build my own focus system to keep track of what is going on in the window. I think we can agree that this would not be a reasonable way to go about it but it would be possible, right?
It wouldn’t be very easy to maintain this kind of code, most importantly. Everything would be coupled. And in the same way, I’m trying to avoid coupling the two parts of my UI together. I want to be able to plug in a generic list and a generic input without them having to know about each other, and while using the built-in focus system and key handlers
r
That feels like a strawman argument.
m
I’m exaggerating your argument to make it clear that the coupling of the two sides of my UI comes at a cost
Which you keep ignoring
r
Ah, I think I see the issue. I apologize. Let me try to explain.
I've been operating from the assumption that you want something like all the examples you have given, where the UI elements are absolutely coupled (e.g. the search bar of the browser and the list of suggestions are absolutely coupled: the list cannot effectively/meaningfully exist without the input), so I've been offering suggestions based on those kinds of use cases. However, you seem to be asking/suggesting to allow any elements that may have nothing to do with each other whatsoever do all be able to handle input from the user at the same time. Am I understand that correctly now?
m
Yes
r
Is it fair to say that is an entirely separate and unrelated use case from the kinds of things I've been suggesting, or am I still missing something?
m
I believe we can also just stop here and I’ll be happy with knowing that there aren’t any built-in tools for realizing this in Compose
Thanks for your time!
r
Fair enough, but if I may, that suggestion actually sounds exactly like what you were suggesting as an exaggeration of my argument: "I can also hard-code all key handlers at the window root and build my own focus system to keep track of what is going on in the window." As ridiculous as that sounds, you may be on to something. You could create a
MultiFocus
composable that can handle any such use cases inside it and dispatch accordingly. You would need to come up with a good way of representing to the user what elements are and are not in focus, as well as how to add or remove elements (or just assume everything in it is always focused I guess), but it could be cool. Instead of the completion in an IDE (which I think still fits into the tightly coupled world), this would be more akin to multiple cursors in an IDE.
Indeed, thank you too! And sorry again for missing your point entirely most of the time. 🙂
m
No problem 🙂