-
-
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
Stateless widgets #1284
Merged
Merged
Stateless widgets #1284
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The idea here is to expose a set of "virtual widgets" that can be used with a `Virtual` widget and its `virtual::State`. A virtual widget is a widget that does not contain any state, but instead is a "virtual" representation of the "real" widget. The real widgets are stored in the `virtual::State`. Every time a new virtual widget tree is created during `view`, it is compared to the previous one and "real" widgets are added / removed to the `virtual::State`. Effectively, this removes the need to keep track of local widget state in the application state and allows `view` to take an immutable reference to `self`. To summarize, using this crate should allow users to remove `State` structs in their application state. Eventually, the strategy used here may be adopted generally and, as a result, all of the widgets in `iced_native` would be replaced!
... as well as a very naive diffing strategy!
`virtual` is a reserved keyword in Rust 😬
Besides exposing the `iced_pure` crate, enabling the `pure` feature also provides pure versions of both the `Application` and `Sandbox` traits! :tada:
`button("Hello")` is easier to write and read than `Button::new("Hello")`.
... and reuse it in `iced_pure`!
The `Widget` trait in `iced_pure` needed to change a bit to make the implementation of `Element::map` possible. Specifically, the `children` method has been split into `diff` and `children_state`.
Instead, we can define the type aliases just once in the root crate!
... and fix collisions with the new `helpers`
As it is useful to make the `Message` completely free in many implementations.
This helper should be unnecessary in the future.
I'm wondering what the scrollable example would look like, where the |
@alex13sh Querying / modifying internal widget state is not possible in this iteration. But there are 2 main ideas to satisfy this use case:
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
This PR introduces a pure, stateless version of every widget in
iced
alongside a newPure
widget to embed them in an impure, stateful application.The Elm Architecture, purity, and continuity
As you may know, applications made with
iced
use The Elm Architecture.In a nutshell, this architecture defines the initial state of the application, a way to
view
it, and a way toupdate
it after a user interaction. Theupdate
logic is called after a meaningful user interaction, which in turn updates the state of the application. Then, theview
logic is executed to redisplay the application.Since
view
logic is only run after anupdate
, all of the mutations to the application state must only happen in theupdate
logic. If the application state changes anywhere else, theview
logic will not be rerun and, therefore, the previously generatedview
may stay outdated.However, the
Application
trait iniced
definesview
as:As a consequence, the application state can be mutated in
view
logic. Theview
logic iniced
is impure.This impurity is necessary because
iced
puts the burden of widget continuity on its users. In other words, it's up to you to provideiced
with the internal state of each widget every timeview
is called.If we take a look at the classic
counter
example:We can see how we need to keep track of the
button::State
of eachButton
in ourCounter
state and provide a mutable reference to the widgets in ourview
logic. The widgets produced byview
are stateful.While this approach forces users to keep track of widget state and causes impurity, I originally chose it because it allows
iced
to directly consume the widget tree produced byview
. Since there is no internal state decoupled fromview
maintained by the runtime,iced
does not need to compare (e.g. reconcile) widget trees in order to ensure continuity.Stateless widgets
As the library matures, the need for some kind of persistent widget data (see #553) between
view
calls becomes more apparent (e.g. incremental rendering, animations, accessibility, etc.).If we are going to end up having persistent widget data anyways... There is no reason to have impure, stateful widgets anymore!
And so I started exploring and ended up creating a new subcrate called
iced_pure
, which introduces a completely stateless implementation for every widget iniced
.After the changes in this PR, we can now write a pure
counter
example:Notice how we no longer need to keep track of the
button::State
! The widgets iniced_pure
do not take any mutable application state inview
. They are stateless widgets. As a consequence, we do not need mutable access toself
inview
anymore.view
becomes pure.All of the widgets in
iced_native
,iced_graphics
, andiced_lazy
have apure
counterpart! In order to use any of them, you will need to enable the newpure
feature. For instance:Once the
pure
feature is enabled, you can find the new widgets in thepure
module.For the optional widgets (e.g.
Canvas
,Image
, etc.) you will still need to enable the specific widget feature, alongside thepure
feature.Additionally, I have introduced function helpers to allow you to use widgets with less verbosity:
Let me know if you have any ideas to further reduce the boilerplate in
view
code.Purifying applications
The
pure
module also offers pure versions of theApplication
andSandbox
traits, so you can get started with pureiced
applications right away.The following examples have a new
pure
version and showcase how to use the newpure
module:component
game_of_life
pane_grid
pick_list
todos
tour
However, if you already have an existing
iced
application, you do not need to switch completely to the new traits in order to benefit from thepure
module. Instead, you can leverage the newPure
widget to includepure
widgets in your impureApplication
.For instance, let's say we want to use our pure
Counter
in an impure application:Pure
acts as a bridge between pure and impure widgets. It is completely opt-in and can be used to slowly migrate your application to the new architecture.The purification of your application may trigger a bunch of important refactors, since it's far easier to keep your data decoupled from the GUI state with stateless widgets. For this reason, I recommend starting small in the most nested views of your application and slowly expand the purity upwards.
In the long run, the plan is to make
pure
the default API and deprecate the impure widgets altogether.Other changes
This PR also contains a bunch of smaller changes worth mentioning:
glow_canvas
andglow_qr_code
features have been removed. Now you can simply enablecanvas
orqr_code
together withglow
.The genericMessage
in thecanvas::Program
trait has been turned into an associated type.iced_wgpu
,iced_glow
, andiced_graphics
no longer re-export aliases of all the widgets. This caused a lot of unnecessary duplication. If you need widgets that are renderer-agnostic, you can depend oniced_native
directly.iced_native
(layout
,update
,draw
, etc.) has been isolated into functions in its respective widget module.&'static str
implementsInto<Element>
by turning it intopure::Text
. This is very useful to avoid typingText::new
everywhere!And I think that's all! This is maybe the biggest user-facing change since the inception of the library. Give it a shot and let me know what you think!