Add WidgetPath and use it to fix widget focus. #811
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Background
I started looking into the widget id system after @sjoshid ran into some trouble with checking for descendants and talked about it on zulip.
The current way is to check a bloom filter, which gives false positives. Now that on its own isn't necessarily a problem, but that fact is easily forgotten, as indeed evidenced in druid code itself.
The
has_focus
methods for example use the bloom filter and thus can returntrue
even if the widget nor its descendants actually have focus. This is not documented forhas_focus
.This leads into a chain reaction, because now we get to
TextBox
which useshas_focus
thinking it's safe and proceeds to paint its border and caret even when it's not actually focused. This issue could be solved perhaps with better documentation forhas_focus
and fixing internal widgets likeTextBox
to properly useis_focused
instead.However the issue runs deeper because the druid inner event passing system uses
has_focus
to determine whether to pass down key press events. This means that widgets that aren't actually focused are getting key press events. Widgets likeTextBox
have nois_focused
guards before handling key press events, so this means that if there's a bloom filter collision, multiple textboxes will handle the key press inputs at once even though only one of them is focused. In theory this issue could also be solved by better documentation informing people that widgets can arbitrarily receive key press events.That's a lot of documentation and random behavior though. Behavior that won't manifest under normal testing, but will manifest later for some customers using the final application and experiencing unexpected behavior. Behavior that the developer can't reproduce.
I think druid should have reliable and reproducible behavior whenever possible. It leads to easier debugging and fewer surprises. Thus I think that
has_focus
should always be correct, with no false positives.The changes in this PR
has_focus
no longer returns false positives and can be relied on.contains
tomay_contain
so that its behavior is signaled even to people who don't have the documentation at hand, e.g. people glancing at changes on GitHub.request_focus
. The cycle is still only between registered-for-focus widgets.WidgetId
documentation by adding info about how there's not necessarily a one-id to one-widget relationship.The reliability of
has_focus
is achieved by introducing a new structWidgetPath
. It's basically aVec<WidgetId>
that describes the path that needs to be taken in the widget tree to reach the target. Its implementation uses a tiny bit of unsafe to guarantee fast code (even in debug builds!) by being less general than a fullVec
thanks toWidgetId
.I know the reason a bloom filter is used for tracking children in
BaseState
is because the exponential nature ofBaseState
would cause memory exhaustion. Thus this PR doesn't replace the bloom filter completely. Instead it is right now used only for focus purposes. Although it can become useful for other future cases too.Potential improvements
There is the question of the focus cycle (
focus_chain
) though. Right nowfocus_chain
lives inBaseState
and I just did a naive update there by replacingWidgetId
withWidgetPath
. This isn't too bad, but could be possibly better. I just don't know all the goals of the system.Do widgets need to be able to change their position in the tree without re-registering for focus? Because if not, then this PR could perhaps be updated in a way that
focus_chain
would revert back to only knowing the leafWidgetId
, but there would be an additional payload generating theWidgetPath
as a result ofregister_for_focus
and then thatWidgetPath
would be stored in the window state as aHashMap
. When afocus_chain
widget needs to get focus, that goes through the window anyway, so the window could just do a lookup for theWidgetPath
there. Then there would be no need to always store partialWidgetPath
s inBaseState
.