-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[RFC] Blinking a TextInput
cursor using periodic animation
#560
Comments
Very nice! I have one question. Will this supposed solution make it harder to do incremental drawing, or is it something that can be added later? Some of the widgets I'm creating can get pretty complex, and I imagine text is really expensive too. We can create a separate RFC for this too if you want. |
Incremental rendering / persistent widget tree is such a huge change that everything is likely to change anyway, but I think this is a step in the right direction. Definitely want to develop incremental rendering in a separate RFC. I have lots of ideas 😂 but I'll keep them out of this RFC. |
I implemented this offline to see if there were any gotchas I missed. The only thing ive had to add to this RFC is the addition of a new synthetic event, Emitting a new event like this is the easiest way to drive the update cycle, and keeps the simple adage (widgets are only updated in response to messages + events) true. The alternative (making another entry point into the update cycle) seems suboptimal, as it increases complexity. |
If you would like to test the prototype and give feedback, update your iced = { git = "https://github.com/twitchyliquid64/iced", branch = "text_input" }
iced_native = { git = "https://github.com/twitchyliquid64/iced", branch = "text_input" }
iced_graphics = { git = "https://github.com/twitchyliquid64/iced", branch = "text_input" }
# etc for all `iced` crates You can see documentation for the updated trait here: https://iced-animations-rfc.tomdnetto.net/iced_native/widget/trait.Widget.html |
Introduction
As discussed in #89, users expect a blinking cursor to signify the current insert position, of a focused text input.
However, implementing this is more complicated than simply updating the
TextInput
widget, because currently iced widgets can only update in response to user events, or application messages (via the Subscription system).This document aims to walk through this problem space and propose a minimal solution to enable a blinking
TextInput
.Guide-level explanation
Background on current architecture
TextInput
widgetCurrently, the cursor is always shown (ie: it does not blink) for a focused text input. No cursor is shown for a
TextInput
which does not have focus.All changes to the text-input's appearance are driven by user-interaction events.
The runtime
Underlying
iced
widgets is an Event Loop, whose role is to bring together all the layers oficed
. Think of this as the 'dispatch' or traffic light of aniced
GUI - it controls the flow. The 'runtime' is composed of a number of separate components (Application
,user_interface
etc), but these details can be omitted for the sake of this explanation.The runtime's job is to perform initial setup and then perform the following operations in sequence:
The widgets
Widgets communicate integrate with the rest of
iced
(the 'runtime') by implementing the Widget trait. This trait defines:width()
,height()
), and resolve its position (layout()
)draw()
)on_event()
)Notably absent from this trait is any means to indicate to the underlying runtime that an update needs to occur at some time in the future: This current design assumes all updates are the result of user interaction or application events.
Whats missing
Hopefully the background section explains enough that these points are clear:
iced
event loop ('runtime') only updates/re-draws its widgets in response to a user or application event. It has no mechanism to redraw itself on its own whim.iced
Widgets cannot communicate to the runtime that an update/re-draw needs to happen at some point in the future - no such interface exists.TextInput
only updates in response to user interaction, and has no concept of time, let alone a sense of 'blink me 500ms after the last interaction'.Generalizing the problem
Even though this RFC is written to support a blinking text-input, the more general case of a widget wanting to animate itself is a common use-case. We can generalize this problem to that of periodic animation - providing the ability for widgets to update their appearance based on the passage of time. More details on different animation use-cases, and ideas around implementing this can be found in #31 / #31 (comment).
Addressing whats missing
1: The event loop
The event loop needs to be able perform an update cycle (ie: handle events if any, update layout if needed, and redraw) when a widget needs it. In the case of a blinking text input cursor, this would be every 500ms, to show/hide the input cursor.
For periodic updates such as these, the behavior of the Wait state needs to be changed. Instead of waiting till the next application event or user interaction, it needs to wait till either the next time a widget needs to be updated, or on the next UI/application event, whichever is first.
For simplicity, we emit a new event
AnimationTick
when an animation tick occurs. This event will drive the update/draw cycle for us.2: The widget interface
Widgets need to be able to communicate their need for a update/draw cycle at some time in the future to the event loop, as ultimately the event loop is responsible for updating that cycle.
This necessitates a change to the interface between widgets and the runtime: the Widget trait.
The exact change to enable this communication is a matter of API design. However, any change which communicates the soonest moment a widget would need to update would work.
3: The
TextInput
widgetThe logic of the widget would need to be updated to:
Reference-level explanation
TL;DR
iced
runtime only performs an update/draw cycle in response to application events and user interaction, but to support periodic animation, we need the runtime to perform a cycle whenever a widget needs it.The numbering of these points aligns to that of the guided explanation in the previous section.
1: Event loop changes
The event loop needs to be changed to track the soonest moment which an update needs to occur, and to perform an update/draw cycle at this moment.
The intermediate state of a user interface (at the transition of a update/draw cycle) is stored in the
State
struct, fromnative/src/program/state.rs
. A new field,next_animation_draw: AnimationState
can be added to track the next draw required by widgets. More on theAnimationState
type later, but just know that this type encapsulates the animation requirements of widgets, and this value can be obtained by calling a method on the root widget.Modifying the behavior of the wait state is surprisingly trivial, thanks to the underlying use of
winit
. Instead of setting the event loops'control_flow
toWait
, we just set it toWaitUntil( <time of soonest update> )
. This changes the behavior to wait until the next event, or the provided time (whichever is sooner).2: Widget trait changes
An update is needed to the Widget trait to communicate the animation requirements of a widget - in our case, that the
TextInput
widget needs to be re-drawn in 500ms.We propose adding a new method to the trait, with a default implementation that just indicate no animation is taking place:
Widgets that need to animate (such as our
TextInput
) can implement this method to indicate an animation is required:Remaining consistent with the stateless and intuitive feel of the widget trait,
next_animation
is called as part of the update loop, to get the latest set of animation requirements.3:
TextInput
changesThe widget needs to keep track of the last time a user interacted with the input. We can do this by adding a new field to an internal state type
Cursor
:And setting its value when the cursor state is updated:
As a corner case, we also need to detect when the input is clicked and gains focus, which we can do by setting
updated_at
duringon_event
when we detect that condition.The
AnimationState
typeWidgets need to symbolize their animation requirements to the runtime. We propose creating a new enum type to represent this:
A new type seems ideal for the following reasons:
AnimationState
can implementstd::cmp::Ord
to provide the soonest animation time throughmin()
. This will greatly simplify implementation because widgets which contain widgets need only return themin()
of their contained widgetsAnimationState
values, in response to anext_animation
call.Option<std::time::Instant>
, but the intent is less obvious then an aptly-named enum.Source
Drawbacks
next_animation()
) increases the runtime complexity of the update loop. Further, even though the method has a default implementation, its one more thing to think about when implementing a widget.iced
, all widgets will be updated & drawn whenever an animation is needed.Alternatives
on_event
andmessages
to signal to the runtime that a redraw is neededMessage
is an associated type, so it will either need to be wrapped somehow or converted into a Tuple. Also, the message-based approach seems harder to reason about than a method that signals current animation requirements.draw()
draw()
and makes it no longer free of side-effectsFuture possibilities
AnimationState
and associated handling in the event loop.Please let me know what you think. Thanks! =D
The text was updated successfully, but these errors were encountered: