-
-
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
Hot reloading of layout #21
Comments
I think this is an essential feature for designing any kind of complex UI. EDIT: I have done a lot of work with Flutter and it's hot reload is amazing. I think nothing that extensive is needed but just reloading the view on the fly would be great. |
It might be worth thinking about clever imgui integration instead, at least in the short run? |
I'm taking a stab at this and I'll upload what I have once I get a bit more working, but I'll try and enumerate what I've learned so far. I think this requires two main components:
Some additional notes:
@hecrj What are your thoughts on an iced layout markup language? I'll happily file an issue with all the details/prototype I have if you're into it. |
https://github.com/tangmi/iced-hotswap-prototype Here's a working proof-of-concept of the following features:
Some caveats:
|
@tangmi Hey! That's really cool! I really like how it keeps I would also probably rename the various Overall this is very, very promising! It could ease development process a lot and help us build view editors! |
It will be very tricky to do this well without an important performance impact and logic errors (i.e. diffing). At least for the time being, I would require the user to define widget state in the application state and provide it in the markup. Then, if the user adds a new widget in the template and the state field doesn't exist, we can fall back to the current naive tracker for hot reloading. However, I would make it a requirement for release builds (i.e. compilation error if the state fields do not exist). |
Glad you like it! Thanks for the feedback! I like the idea of hiding For the markup side, I was imagining two "runtimes":
In both cases, the result would be a function something like |
@tangmi I was about to try something similar, nice work! For my needs, I was thinking of a different, possibly simpler, approach. I'll expand on that here in the hopes that it adds to this discussion. Instead of generating any rust layout code, my priority lies in hot-reloading the styling of existing widgets. The plan was to define all styles and sizes in a single serializable object, I'll call The hot swapping logic would live in an async loop constantly looking for edits to a file, then triggering an iced This approach obviously doesn't allow for generating I'm also skeptical of the utility of generated In my mind, that's like generating HTML, when what I really want is to just hot-reload my CSS. With all that said, maybe what I have in mind isn't the right approach, or representative of most users' needs! I think this is really important work for iced and I'm glad you're tackling it. 🙂 |
@hecrj I'll keep poking at this when I can find time. I think the main takeaway from my investigation is that both a markup language and hotswapping can probably be developed as a library with no* changes in iced needed (great work on library design!). *I think some mechanically consistent declaration of widgets and their state/attributes (not sure what this looks like!) can help simplify the markup language implementation and allow for custom widgets. |
Could you elaborate? What do you mean by this? |
(Be aware this is a half-baked idea) I think I mean some consistent mapping from markup elements to code. E.g. If all widgets provide an "attribute setter" interface (e.g. Button(
content: Text("button"),
on_click: "ButtonClicked",
disabled: false, // <- I know this isn't how `Button` handles disabled currently!
), to let markup_element = todo!("deserialized from file");
let mut button = Button::with_state(widgets_state.get(markup_element.get_id()));
button.set_attribute("content", get_element(markup_element.get("content"));
button.set_attribute("on_click", get_callback(markup_element.get("on_click")));
button.set_attribute("disabled", markup_element.get("disabled")); without any special understanding of ...although I'm unsure how to make this typesafe! The widget might need to have some manifest declaring its attributes and the types of its attributes? Maybe through some code generation (this would require changes in iced)? |
Could you implement a trait for each widget in the meantime, e.g.
then leave it up to the user to implement this trait if they want to use a custom widget with hot reloading? |
I think the attribute would need to take struct Foo {
val: i32,
}
impl Attribute for Foo {
fn set_attribute<T>(&mut self, attribute: String, value: T) {
if attribute == "val" {
self.val = value; // self.val expects `i32`, value is `T`. !!
}
}
} |
Something along these lines? |
I think something like that world work! To support non-num-primitives, I think it'd still need Edit: this assumes we know the type of each attribute upfront... I'm not confident this is knowledge the markup engine would have without some widget manifest... |
I've made some progress on (my version of) this. I ended up going the route of directly placing variables in the attribute setter calls, e.g.:
The stylesheet is just a struct that contains these appearance variables, as well as a separate struct of colors, so the appearances can reference them. I'm still working on the file watching and serde side of things now, but it looks like the fundamentals are proved out. LimitationsThis implementation does not allow dynamic renaming of appearance names, because these are set at compile time. So, once you have set BenefitsThe immediate benefit of this is type checking. The compiler can guarantee that the type of |
@aevyrie If you don't mind, would you please provide some implementation details or show us your code? |
@shujaatak Of course. Please excuse the state of the code, it's very much in flux. I'm not super happy with the verbosity required for a stylesheet - there is the Rust boilerplate of defining the new field in the stylesheet struct, then instantiating a value in the instance of the struct. As far as how the system works, there are This is really just a prototype that evolved as I started using it to build out the application. I'm sure this concept could be fleshed out in a more robust way if there is interest in doing so. |
https://fasterthanli.me/articles/so-you-want-to-live-reload-rust maybe related? We could even hot reload the whole application. End result https://www.youtube.com/watch?v=o1iqV5k6-QU |
@pickfire I think a dynamic library approach would still run into the problem that there is a compile step in the middle of your style/layout tweaking loop. It seems likely to be too slow. |
But compiler could guarantee there is no issue with the data type, say if we use json there is no validation or whatsoever, at least when it compiles it usually works. IIRC there is something to speed this up, I think one can change value in rust code and reflected directly, I saw a crate for this but I forgot which is it. |
@pickfire You are probably thinking of the dymod crate. I've tried the dymod method (expirement here), it has the following issues.
solutions to both these problems are burdensome. problem 1 might require you to turn your appstate into a heavily encapsulated trait object to avoid exposing dependencies to the dymod. Problem 2 could be solved a number of ways, but not without reorganizing Iced's trait structure. reasons to strongly prefer the dymod:
It seems like you should use the dymod method if you want to avoid registering widgets in some kind of interpreter beforehand like Qml. |
Oh hot reloading is so needed. Hope it becomes a reality for iced. |
What about making use of something like https://github.com/rhaiscript/rhai? It seems that it can be integrated with iced quite easily, even better is it allows syntax extensions so in the future it could even be possible to integrate iced specific syntax. Considering making a prototype that integrates with iced. |
Alright, so I played with the idea a little bit and I think it should be possible to integrate iced with Rhai, but it will require changes in iced itself. Problem 1. Rhai requires that types that will be registered implement
I pushed a little "demo" with shot_2021_06_13_13_20.mp4 |
I was able to (hackily) implement Rc wrappers. I have pushed it to the repository, below is a sample with shot_2021_06_13_22_44.mp4Another way this could be implemented without We could also implement |
Avoiding markup is one of the core goals of elm. Both Elm and Rust are less verbose than XML. It is in fact one of the selling arguments of elm. If your only concern is the compile times, have a look at bevy. I think they use dynamic linking (dll) for development, which results in insanely fast compile times, and only use static linking for release builds. Here's a link, scroll down a few paragraphs. Maybe this could be explored with iced too, regardless of hot-reloading? This way, only iced would be pre-compiled once, and only the user code is compiled in every iteration. Using markup/templates removes the dynamic behavior. Approximating dynamic behavior using yet another alien syntax for loops and branching sounds like a lot of effort without much value to me. Furthermore, using a template language adds a completely new barrier. And then it has to be thrown away once you realize that you need actual dynamic behavior, and you'll need to reimplement it in Rust. Using rhai or some other real dynamic language seems much more reasonable to me. However, at that point, it would probably never be favorable to actually use iced with Rust directly. You'd most probably write everything in rhai instead of dealing with transferring objects from one language to another, which further can introduce subtle bugs. Is that the route you want iced to go? If not, rhaiced should perhaps be an independent project. I personally choose to use Rust because of the powerful type system, the package manager, and the performance. At least two of these three selling points are lost when you use a dynamic scripting language. If all you desire is fast compile times, supporting dynamic linking for Rust development might be the way to go. Otherwise, once the user realizes that rhai performance (although neat) can not achieve compiled speed, they can't simply recompile with a different flag, but they will have to rewrite core logic in Rust. And deal with transferring objects across the language border. If Rust is still too verbose compared to rhai, perhaps we should improve the Rust API instead of using a completely different language. I'm very pleased with the current iced design, but with enough love we could probably make it a little more concise. My point is: let's not forget that there's a lot we can improve in Rust. Conciseness and compile speed are issues that can be solved that way. We don't have to add an opinionated secondary language to tackle these issues. I assume most of use actually like Rust, we choose it for a reason, right? |
some sort of markup language for iced would be awesome. to keep the crates lightweight this could be in a sparate crate like: |
A small request - please don't make it obligatory. My takeaway from QML/Qt and Gtk's XML templates was that it made me learn another language to do the same things I could do with the one I already knew. Especially Gtk's XML was hard to accept (also not documented well) and I happen to know XML/XSLT very well. In Qt's case, it split the ecosystem in two, with a significant group of people being angry that Widgets are no longer developed. |
This is an interesting project which would provide hot reloading capabilities without any change to Iced: Unfortunately I cannot make the hot-iced example work, it keeps crashing at every lib reload, I opened an issue with some sample Iced code. |
I realize I'm the first to comment in a while, but I'd like to add my thoughts to the conversation. I believe some sort of external language for creating the view is a good idea. Rust is an amazing language, but many of the things that make it amazing also make it very inconvenient to create UIs with. It excels at creating logic that is dependable, robust, and performant. Contrast this to UIs, where iteration and development speed are important. I suspect part of the reason there hasn't been a clear winner yet in the Rust GUI library space is because many of the language's philosophies do not bode well with GUI app development. Having the view logic built in a separate language can help mitigate this. The update logic should still be written in Rust since this is where it would be great, but the view field where no major calculations should be done can spare the maximum performance. While I agree with the point in another comment that markup languages should not be forced or mandatory, I believe there is some merit in having an external crate providing this functionality to Iced. Many of the cases where markup languages split a community (cough QML cough) were because they were presented as the only alternative, and other parts of the library suffered because of it. If this functionality is provided as a separate crate and development on the regular Iced library continued as normal, it would give users a choice on what to do based on the requirements and tradeoffs of their application. Keep in mind that not all GUI applications (especially the view logic) need top-tier performance. The fact that Electron is as popular as it is shows this. Apps that do need this performance can use pure Rust with Iced or roll their own GUI. This would be a solution for apps designed for weak and/or embedded systems. There are quite a few options for a markup language like this. There are the Rust scripting languages that have come up already in this thread. There are many interpreted ones like Rhai or Rune that provide near instant feedback on changes. Those could work, and they're extendable enough to suit Iced's needs. However, there is a bit of a performance penalty. For most apps it won't make a difference, but it is true that it can be difficult to migrate off of them. Still, it would be used only for the view logic, not for many of the things that would be written in Rust for the benefits it provides. An option could be to build a transpiler for one of these languages to Rust. That could alleviate some of the performance issues. If we stick it in a procedural macro or another way that it isn't to be intended to be edited by the user, it doesn't matter how messy it is as long as it compiles. Then, LLVM will do it's magic to give near native performance. We could also make a dedicated markup language for Iced, but that is difficult and wouldn't really give many more benefits than just using an existing one. Another option could be to build a RAD tool to build UIs visually. The file it spits out doesn't necessarily have to be human-readable. In fact, it could just be a serde serialization of Iced types, which if done right can make it trivial to update and add new widgets. A drawback to this would be that these tools tend to not scale well, and there's even less of an option to convert it to code. For small apps though, this could a realistic option. One point that is being left out in this is ease of development. All of us know how Rust works and can use it effectively. However, there is a huge point to be made that integrating a simpler language such as Rhai can help people who do not know Rust use Iced. For instance, someone more skilled in UI design can build the views with a scripting language, while someone seasoned in Rust could build the fancy logic. This ease of development could be a massive selling point for the library. An well-designed interface will probably have to be made for one of these languages to facilitate passing State and Messages between Rust and the scripting language. I feel that this will be one of the biggest pain points for this, just as it is in passing info into shaders in graphics programming. I'm not sure the best way to tackle this, but I feel a good combination of metaprogramming and APIs could make it intuitive to pass this data around. This would likely be the problem of a 3rd party crate rather than Iced directly, but it's worth considering, especially if it ends up making some small tweaks to how data is treated in Iced. Some sort of rapid iteration tool, whether it be a scripting language or a RAD tool, can make development with Iced faster and more accessible. Thank you for your time in reading through this. |
I choose iced because the view is in rust, the great part you can build variable lists with iterators and you can do pattern matching for showing or not showing widgets. I think you should look at slint, they seem to do it the way you like. I when using a scripting language rust have to include every feature with could be possible to use in the scripting language in the binary. |
There are many things I dislike about Slint, such as its lack of custom widgets and its pricing model. The great thing about these scripting languages is that they interop very well with Rust, which can allow doing the pattern matching stuff in Rust, then doing other things in the scripting language where you can quickly tweak and play around with them. Perhaps this would be best as an external library, so people have the choice of which paradigm they would like to use. |
While there's definitely merit for defining ui declaratively in external ui files, I think this is simply a non-goal for iced (at least not as the main way, you can build systems like that on top of iced, much like the bevy has with scripting). I think the most tangible way in the short term would be trying to improve compile times, and pointing users to ways to improve compile times, the bevy setup guide is an excellent resource, in particular https://github.com/rust-lang/rustc_codegen_cranelift would be very interesting to test, and also changing the linker to In the long term, it'll be interesting to explore other approaches:
|
Good points. For the Iced GUI editor, perhaps something similar to GTK's GTKBuilder could be implemented. As I understand it, GTK will spit out the code from an XML file directly with its properties, but you can go in with C code and change certain properties programmatically as needed. This opens up the possibility of having tools like Glade and Cambalache using completely visual interfaces, then modifying those widgets programmatically if there's something that has to be dynamically set within the program. The exact implementation wouldn't work very well with Iced, but perhaps some sort of variant could be explored. One issue is that you address these in GTK by ID, and Iced doesn't really have a widget ID system. Maybe an optional one could be added, though set to |
I think iced does have ids, I'm not exactly sure about the difference between id types or how they are used as I haven't used iced in a while, they are important for accessibility and other things like managing focus.
I don't think this discussion matters because I doubt @hecrj would be interested in implementing something like this, in addition to this being too late for such a change (to do this well it would require a lot of effort and probably rewriting large parts of the library), he's against integrating fluent because he sees it as an unnecessary DSL, and that is a much smaller but similar change and it has a better motivation, since it's meant to also be used by non-coders. (Also I'm not saying this as a negative thing towards hecrj, he has a clear vision for the scope and identity of the library and that is important to have, I also generally agree with the direction of iced, including the lack of external ui files, there's a reason modern ui frameworks are moving away from them, e.g. jsx and other web frameworks, flutter, swiftui, etc.) |
Ok, I wasn't aware of this general trend among GUI libraries. Perhaps something of a live preview could be developed that reads the view function and renders out the widgets, but that is quite the project. |
I actually had this exact idea but it very quickly died down once I realised vscode doesn't allow for embedding images or html inside the editor (actually just checked and apparently there has been some recent progress on that recently), although I'm mainly using Zed now and it also lacks most extension capabilities currently. |
I have done some more research, and I'd like to share what I've learned about that. With the way views in Iced are currently done, it is not feasible to simply match for the strings of various widgets and render them, as I was originally thinking. This is because it is commonplace to have a lot of conditionals determining what is shown. An app like that would have to pretty much interpret the code with as much of Rust's functionality as possible. Not impossible, but a tremendous amount of work. I have looked at the possibility of creating a macro that takes a block of Rust code and interprets it using It is seeming a hot-swappable Rust module is the best option, as this thread was come to many times. There isn't currently a working prototype of it on all platforms. I will have some spare time so I can try. Another thing we can consider is implementing a way to change function parameters at runtime. This wouldn't allow changing logic or adding/removing widgets, but would allow playing with colours, spacing, text, etc. at runtime. I feel this could be a good benefit. I'm not 100% sure about how to implement it, but I feel this may not be the trickiest thing in the world. There was some promising work with external RON files to this effect earlier in the thread, and it would be useful if there could be similar functionality implemented in pure Rust. |
@breynard0 please see my post above, and in particular this issue. Unfortunately there does not seem to be much activity behind |
Maybe can use the inline_tweak library for hot reloading in specific's layouts, like hotlayout!(...) ? |
@garcia-andy Nice find! I had some time to throw together a basic example. My attempt is in this repository. A few issues I came across:
Video of my implementation: iced_inline_tweak.mp4Other than the hiccups I mentioned, I found that |
thanks for the code, I've looked at the issues you mentioned in the repo.I had problems doing a git clone so I made another repo (but I mentioned you in the readme anyway).
I hope this helps with the development of iced's hot reloading. |
I had an idea. On debug mode, serialize the state structs to the #[derive(Serialize)]
struct AppState;
iced::application("Iced app", update, view)
.persistence(std::path::Path::new(r#"/var/tmp/my-iced-app.state"#)) Also, use an external app with a Wayland compositor like Casilda, to keep the window open: $ export GDK_BACKEND=wayland
$ export WAYLAND_DISPLAY=/tmp/casilda-example.sock
$ cargo run The Cambalache app uses wlroots for this. For X11 / others, Iced could render to a local server and the viewer app connect to it (probably more complex than this). The build time can be reduced with LLD (rust-lang/rust#39915) / mold and some precompilation like Bevy uses. |
That may work in Linux environments, but they would be forced to implement a different solution on MacOS and Windows. Cambalache and the like may focus only on GTK environments, but iced is maintained for all systems. The biggest problem is maintaining compatibility with different systems, while trying to have as little boilerplate code as possible. Likewise, any example code that works can be useful for future ideas, maybe using the principle of those ideas can lead to something. For my part, I continue experimenting with dynamic linking and changing constant values with inline_tweak to increase development speed a little, but there is still a lot to polish. Maybe in 0.14 or 0.15 we will see some progress on this. |
I feel it's not a bad idea to implement feature-locked serde traits on Iced's types, though. If a Windows and MacOS solution can be found, the above idea could be used. Perhaps stored in an environment variable? A quick Google search tells me that Windows has a limit of around 32kb for them, but maybe there's something else like the Windows registry or something. Not an expert with Windows, maybe someone else has an idea of where you store them. Implementing the serde traits also opens the door for things like 3rd party markup languages and GUI builders. I know that's not the direction that Iced wants to go, but giving a feature-locked option for someone else to make something can't hurt. |
Windows has a |
I did just find this unstable feature, Single-file packages. Perhaps it could be used for some dynamic linking shenanigans. I will investigate more when I have some time, and if others want to look into it, that would be great. |
Could you make Rust not change ABIs or memory layout or inline between view code and everything else? Supporting adding/removing fields in the model/state would be useful too although you need to make default values for new fields.
Serializing state and then restarting the application is bad for many applications since it will kill any kinds of open I/O and it would bring additional slowness. If there will be an editor, I think that the Android view XML editor is something to take inspiration from with all the style configuration, but please let's not make a separate view language. I like doing view code in Rust so maybe the AST could be modified? Maybe using rustc's or rust-analyzer's code for type and param info and auto-importing? |
Like discussed on Discord - perhaps it's nice to provide some sort of "quick iteration" possibility, for example using Lua.
The text was updated successfully, but these errors were encountered: