Skip to content
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

Open
PvdBerg1998 opened this issue Oct 7, 2019 · 51 comments
Open

Hot reloading of layout #21

PvdBerg1998 opened this issue Oct 7, 2019 · 51 comments
Labels
developer experience feature New feature or request help wanted Extra attention is needed question Further information is requested

Comments

@PvdBerg1998
Copy link

Like discussed on Discord - perhaps it's nice to provide some sort of "quick iteration" possibility, for example using Lua.

@hecrj hecrj added feature New feature or request help wanted Extra attention is needed question Further information is requested labels Oct 7, 2019
@krooq
Copy link

krooq commented Apr 19, 2020

I think this is an essential feature for designing any kind of complex UI.
It takes me 30 seconds to recompile my 90 line main file when I make a small change to the view which is just far far too long for iterative development.

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.

@clawoflight
Copy link

It might be worth thinking about clever imgui integration instead, at least in the short run?

@ethanpailes
Copy link
Contributor

#303

@tangmi
Copy link

tangmi commented May 19, 2020

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:

  1. Markup/templating language: Something like XAML, JSX, Handlebars + HTML, etc. I think this is a very useful component for iced to have, regardless of hot swap capabilities.
    • The template engine needs to support some control flow (if, for each, etc) based on user state.
    • The runtime component of the markup language needs to:
      • Resolve custom widgets into iced::Elements.
        • This might need widgets to have some declarative metadata describing attributes and events, which would allow custom and "builtin" widgets to be treated the same
      • Store/reference widget state. (Note: This could happen automatically! It'd be an awesome gain for iced user producivity!)
      • Reference message construction functions for callbacks.
        • This probably needs reflection (codegen or runtime).
          • I'm currently playing around with a HashMap<String, &dyn Any> that the app fills on initialization and downcasting to the exact function pointer type, but am running into an issue with Any::downcast to a function pointer.
          • Also going to look into leveraging serde's codegen to generate messages.
    • This could probably be built as a library, given changes in iced for support.
  2. Hot reload runtime component: This will actually do the hotswapping of the resolved markup and ends up being pretty trivial (file watcher with an optional code recompiler) once we have the markup language.
    • This can probably be done as a library, with minimal or no support needed in iced itself.
    • I can imagine a setup where the layout itself is reloaded from the markup language (almost) instantly and when state/callbacks change the entire app could be recompiled and the binary hotswapped in, preserving app state.

Some additional notes:

  • My current prototype uses ron as the markup "language" and can currently generate/reload non-interactive stateless views. Most features I described above are not implemented.
  • It'd be nice to eventually include the markup language and reloading as "blessed" crates.
  • I'm currently ignoring the issue of how to make this zero/low overhead in release builds.

@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.

@tangmi
Copy link

tangmi commented May 20, 2020

https://github.com/tangmi/iced-hotswap-prototype

Here's a working proof-of-concept of the following features:

  • Ron-based markup language templated with handlebars
    • currently supports what's in iml::Element: Column, Row, Text, Button, Slider (with minimal parameters)
    • resolves templates with user-provided state
  • automatically creates, owns, and maintains widget "ephemeral" state (i.e. button::State)
  • resolves callbacks with user-provided "message" struct
  • can read values from user-provided state

Some caveats:

  • I'm heavily leveraging serde's code generation to e.g. discover the variants of a user-provided messages enum.
  • A lot of features are missing
  • no errors are handled gracefully (if you spell your markup file wrong, it'll immediately crash).
  • I'm abusing std::mem::transmute in a couple places to avoid "solving" where to put lifetime annotations
  • After saving the file, one needs to interact with the app (mouseover) to get it to reload. This could be fixed by implementing a Subscription that watches the file system I've added a Subscription that just wakes up the app every half second to force a rerender. (there's definitely a smarter way to do this).

@hecrj
Copy link
Member

hecrj commented May 20, 2020

@tangmi Hey! That's really cool!

I really like how it keeps update code type-safe. I imagine we could build our own Application trait wrapping iced::Application to hide the Hotswappo struct and view and provide the state directly to update. This should also help us use a different strategy for release mode (ideally generating Rust view code from the template and using the iced::Application directly for no runtime cost).

I would also probably rename the various callback_name to the actual widget method names (i.e. on_press for a Button, etc.), but I imagine the details of the language are not very important right now.

Overall this is very, very promising! It could ease development process a lot and help us build view editors!

@hecrj hecrj pinned this issue May 20, 2020
@hecrj
Copy link
Member

hecrj commented May 20, 2020

automatically creates, owns, and maintains widget "ephemeral" state (i.e. button::State)

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).

@tangmi
Copy link

tangmi commented May 20, 2020

Glad you like it! Thanks for the feedback!

I like the idea of hiding Hotswapo behind a separate Application trait opening up the possibility for transparent hotswapping in debug builds for the user.

For the markup side, I was imagining two "runtimes":

  • Dynamic: one with a similar lineage to the prototype--intended to load dynamically and have additional hot-swapping abilities at the cost of performance depredations.
    • This is the one hotswapping would use.
  • Static: a compile-time affair that generates rust code for the view (that includes the branching as rust control flow) and a struct that contains the ephemeral state (please correct me if there's a better name for this) of exactly the widgets that appear in the markup file.
    • In this case, I don't think the user needs to provide widget state, which is an otherwise very mechanical action (every widget one adds needs its associated state, it's very unlikely that two separate widgets want to share the same state, etc).
    • This is the one non-hotswapping (release builds?) would use.

In both cases, the result would be a function something like fn view(&loaded_layout, &mut user_state) -> Element<'_, M>. I might be handwaving the difficulty of implementation/maintaining cost this dual-action runtime, but I believe some combo dynamic/static layout file solution gets us both perf and flexibility.

@aevyrie
Copy link

aevyrie commented May 21, 2020

@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 StyleSheet for now. I would then reference this StyleSheet whenever I define a widget in an iced fn view(). Similar to CSS classes.

The hot swapping logic would live in an async loop constantly looking for edits to a file, then triggering an iced Command when an edit is detected. This Command would use serde to import this (TOML?) markup file containing the serialized StyleSheet object. This would allow me to rapidly iterate on the styling (widths, spacing, coloring, borders, etc) of my framed out UI. Once I'm happy with it, I can just drop this tweaked StyleSheet object into my rust code to remove the need for the hot-reloading runtime.

This approach obviously doesn't allow for generating view code on the fly, but I haven't found that to be an issue so far. Compartmentalizing groupings of reusable widgets into "components" that I can then drop into the top level UI has been painless, and makes the top-level code very readable when the entire gui isn't expressed in a giant nest of .push()s. I've also found that iced's Elmish way of defining layout makes it dead simple to get the layout, if not the styling, where I want it to be very rapidly.

I'm also skeptical of the utility of generated view code, because it quickly becomes customized - especially views that hold lists of things - with .filter()s and .fold()s. Once I've prototyped a gui and hook up all this logic, the next time I want to tweak my view code via hot-reloading, I'd have to manually reconfigure all this logic, which seems to defeat the purpose!

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. 🙂

@tangmi
Copy link

tangmi commented May 22, 2020

@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.

@hecrj
Copy link
Member

hecrj commented May 22, 2020

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?

@tangmi
Copy link

tangmi commented May 22, 2020

(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. Widget::set_attribute(attribute_name: &str, attribute_value: _)), then it's pretty feasible to go from

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 Button. We just need to know that widgets have attributes that can be set (compared to my prototype, which needs to know about every widget, which is an unbounded set if we include user implementations of iced_native::widget::Widget).

...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)?

@aevyrie
Copy link

aevyrie commented May 23, 2020

I think I mean some consistent mapping from markup elements to code. E.g. If all widgets provide an "attribute setter" interface

Could you implement a trait for each widget in the meantime, e.g.

trait Attribute {
     fn set_attribute<T>(&mut self, attribute: String, value: T);
}

then leave it up to the user to implement this trait if they want to use a custom widget with hot reloading?

@tangmi
Copy link

tangmi commented May 23, 2020

I think the attribute would need to take &dyn Any (+ Clone?), otherwise, I'm not sure what an implementation could look like

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`. !!
        }
    }
}

@aevyrie
Copy link

aevyrie commented May 23, 2020

@tangmi
Copy link

tangmi commented May 23, 2020

I think something like that world work! To support non-num-primitives, I think it'd still need Any... we're getting into weird territory, but maybe something like this?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=082e30cb889efedf3b83d0b4e9471f51

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...

@aevyrie
Copy link

aevyrie commented May 29, 2020

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.:

widget_xyz.spacing(stylesheet.spacing_outer)

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.

Limitations

This implementation does not allow dynamic renaming of appearance names, because these are set at compile time. So, once you have set widget_xyz.spacing() to use stylesheet.spacing_outer, you can't change it at runtime; all you can change is the value of spacing_outer.

Benefits

The immediate benefit of this is type checking. The compiler can guarantee that the type of spacing_outer is compatible with .spacing()'s signature. An alternate implementation might instead use a str to look up a matching appearance from a HashMap. The risk with that is you can assign a nonexistent appearance name (typo, stylesheet change), and it won't be caught at compile time. It's possible this could be solved with a macro, or maybe forcing a panic if an appearance call returns None while hot reloading is disabled.

@aevyrie
Copy link

aevyrie commented Jul 10, 2020

Well, I have a proof of concept working! The iced UI updates immediately when saving the stylesheet.
image
(Note the red text)

Usage in this case is:
.color(stylesheet.color(&stylesheet.text_color_h1))

I can change the color in this example by either changing the rgb values of the named "text" color, or by changing text_color_h1 to a completely different named color such as "primary". I chose this example because it relies on an extra layer of abstraction: colors can be added and referred to by name dynamically. Here's an example of adding a new named color and changing the reference to it:
image

I'm looking forward to being able to tweak the UI without recompiling. 😄

@shujaatak
Copy link

@aevyrie If you don't mind, would you please provide some implementation details or show us your code?

@aevyrie
Copy link

aevyrie commented Aug 23, 2020

@shujaatak Of course. Please excuse the state of the code, it's very much in flux.
https://github.com/aevyrie/tolstack/blob/master/src/ui/style.rs

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 NamedXYZ structs for each type of styled object, like a button or container, that implement the Named trait. This trait takes in the string of the style class you've defined in your stylesheet (e.e a named color such as "primary"), and the list to use as a style lookup. The string name is then resolved to generate an Iced style using the lookup, and panics if the style class name provided is invalid.

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.

@pickfire
Copy link

pickfire commented Sep 30, 2020

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

@ethanpailes
Copy link
Contributor

@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.

@pickfire
Copy link

pickfire commented Sep 30, 2020

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.

@mobile-bungalow
Copy link
Contributor

@pickfire You are probably thinking of the dymod crate. I've tried the dymod method (expirement here), it has the following issues.

  1. View logic must be in a separate crate. This becomes a problem if you are using more complex global state, or if you appstate contains many types from heavy dependencies.
  2. You need a concrete Renderer implementation in the dymod. this means you already have to relink wgpu and its dependencies, on my macbook that takes 9 seconds.

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:

  1. no need for scripting or markdown. You will never have to migrate work in markdown to rust. you will never need to ship markdown files with your application. You won't have to wait for the markdown crate you use to catch up to Iced's latest release cycle, or migrate if it gets abandoned.
  2. there will be no parallel effort to track all of iced's widget in an enum, which would exclude any third party widgets!
  3. static typing. sweet sweet static typing.
  4. only need to iterate on one element? return only that element from the dymod and keep the rest of your code consistent.

It seems like you should use the dymod method if you want to avoid registering widgets in some kind of interpreter beforehand like Qml.

@kkharji
Copy link

kkharji commented Feb 25, 2021

Oh hot reloading is so needed. Hope it becomes a reality for iced.

@yusdacra
Copy link
Contributor

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.

@yusdacra
Copy link
Contributor

yusdacra commented Jun 13, 2021

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 Clone, which almost all of iced's widgets don't. Most importantly, Element doesn't.
Problem 2. Potential lifetime issues (?), but I didn't dig into that much (couldn't use any widgets anyways, problem 1). (This seems like it can be "just" fixed by making use of 'static lifetimes)

Problem 1 could be fixed (?) by using Rc<RefCell<>> wrappers. But this has the problem that we have to redefine each and every method again, to take Rc<RefCell<>> wrapped version of the widget instead. This could be done with a proc macro, but it's still effort. It looks like this can only really be solved by making Element and other widgets cloneable.

I pushed a little "demo" with Text hot reloading to https://github.com/yusdacra/iced_rhai, and what follows is a video of it.

shot_2021_06_13_13_20.mp4

@yusdacra
Copy link
Contributor

yusdacra commented Jun 13, 2021

I was able to (hackily) implement Rc wrappers. I have pushed it to the repository, below is a sample with Row, Column and Container.

shot_2021_06_13_22_44.mp4

Another way this could be implemented without unsafe code is, we would practically "copy" all of the widgets' "data" part, create methods, derive Clone etc. This would probably take more time (?) to do though.

We could also implement Clone for the iced_native Widget trait. I tried doing this with https://lib.rs/crates/dyn-clonable, it complained about Renderer and Message generics not being Clone. Even if we could get Message to require Clone, Renderer is too much for that.

@johannesvollmer
Copy link

johannesvollmer commented Jul 28, 2021

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?

@Cambloid
Copy link

Cambloid commented Oct 3, 2021

some sort of markup language for iced would be awesome. to keep the crates lightweight this could be in a sparate crate like:
iced_markup. like iced_audio for audio related widgets.

@mikolajsnioch
Copy link

mikolajsnioch commented Jan 20, 2022

  1. Markup/templating language: Something like XAML, JSX, Handlebars + HTML, etc. I think this is a very useful component for iced to have, regardless of hot swap capabilities.

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.

@lucatrv
Copy link

lucatrv commented Dec 2, 2022

This is an interesting project which would provide hot reloading capabilities without any change to Iced:
https://robert.kra.hn/posts/hot-reloading-rust/
https://crates.io/crates/hot-lib-reloader
https://github.com/rksm/hot-lib-reloader-rs

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.

@breynard0
Copy link
Contributor

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.

@jedugsem
Copy link

jedugsem commented Aug 1, 2024

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.

@breynard0
Copy link
Contributor

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.

@bbb651
Copy link
Contributor

bbb651 commented Aug 1, 2024

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 mold (or lld on windows) but I assume people are more aware of that. Also dynamic linking #849 would be really nice to have, it seems to really help in bevy.

In the long term, it'll be interesting to explore other approaches:

  • Some kind of interpreter for rust (only for debug builds of course)? Using miri it would only have to do the rust -> mir part, even if it only supported a limited subset of rust would probably still be a huge amount of work.
  • Iced gui editor, would be very useful for generating code for new views, parsing code to try to recreate the view would be a lossy process but might be good enough for simple usecases (to be useful it would have to preserve code it doesn't care about or understand, sort of like toml_edit does with whitespace and comments).

@breynard0
Copy link
Contributor

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 None by default to preserve compatibility?

@bbb651
Copy link
Contributor

bbb651 commented Aug 1, 2024

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.

GtkBuilder works by reading loading the ui files at runtime, which is worst than Slint, but even with a system to do this at compile time, again I don't think this belongs in iced, this goes right back to the ecosystem split problem (splits development resources, documentation, user forum, etc.), when I was suggesting a gui editor I meant one that operates on rust code directly.

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.)

@breynard0
Copy link
Contributor

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.

@bbb651
Copy link
Contributor

bbb651 commented Aug 1, 2024

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.

@breynard0
Copy link
Contributor

breynard0 commented Aug 2, 2024

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 syn. This could work in theory, but it will likely be slow and involve a massive amount of effort. Another option to consider is using miri. This was suggested by @bbb651, and it could be an option. It's likely the view logic would have to be in a separate crate to avoid compiling the whole project on reload. It also appears that miri does not currently support linking a hot-swappable interpreted module with a regular compiled module. We could add that functionality either to the base repo or fork it. However, this would also be a lot of work, I suspect it will be buggy, and it may not even have the performance to justify using it even in development.

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.

@lucatrv
Copy link

lucatrv commented Aug 2, 2024

@breynard0 please see my post above, and in particular this issue. Unfortunately there does not seem to be much activity behind hot-lib-reloader lately, that's a pity because it looks very promising to me.

@garcia-andy
Copy link

Maybe can use the inline_tweak library for hot reloading in specific's layouts, like hotlayout!(...) ?

@breynard0
Copy link
Contributor

@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:

  • The tweak! macro uses std::column internally, which clashes with Iced's column macro and throws an error. This is easily solved by simply adding a use statement for this. The derive macro doesn't seem to cause this issue. I feel this won't be that hard of an issue to fix, we can make a PR to inline_tweak's repository.
  • Only literals are supported, though a fair amount of Iced configuration is in the form of enums. We could perhaps open a PR for this as well, though I'm not sure how hard it will be to implement.
  • When a change is made, it does not take effect until the UI is interacted with since Iced is retained mode. This could end up being annoying, I'm not sure if there's a fix for this other than just clicking a button in the app or something. There might be something else within iced that can work too, maybe others have some ideas.

Video of my implementation:

iced_inline_tweak.mp4

Other than the hiccups I mentioned, I found that inline_tweak worked quite well with Iced, which is great.

@garcia-andy
Copy link

garcia-andy commented Sep 8, 2024

@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:

  • The tweak! macro uses std::column internally, which clashes with Iced's column macro and throws an error. This is easily solved by simply adding a use statement for this. The derive macro doesn't seem to cause this issue. I feel this won't be that hard of an issue to fix, we can make a PR to inline_tweak's repository.
  • Only literals are supported, though a fair amount of Iced configuration is in the form of enums. We could perhaps open a PR for this as well, though I'm not sure how hard it will be to implement.
  • When a change is made, it does not take effect until the UI is interacted with since Iced is retained mode. This could end up being annoying, I'm not sure if there's a fix for this other than just clicking a button in the app or something. There might be something else within iced that can work too, maybe others have some ideas.

Video of my implementation:

iced_inline_tweak.mp4
Other than the hiccups I mentioned, I found that inline_tweak worked quite well with Iced, which is great.

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).

  • What I basically did is that in debug the window is redrawn every frame using subscribe and changing a field of the struct (although the correct way would be to throw a RedrawRequest event).
  • At the moment, when updating a window's content (like the title for example) the window is automatically updated without the need to interact with it, but I suspect that in a much larger and more complex application redrawing every frame could negatively affect performance.
  • I thought that this could be solved if the tweak_fn macro allowed you to select a callback so that when reloading some content you call it first, and the callback would then use the global state I created.
  • It would also be interesting not to use a static variable, since it forces the use of unsafe blocks.

I hope this helps with the development of iced's hot reloading.

@ricvelozo
Copy link

I had an idea. On debug mode, serialize the state structs to the tmp directory, and try automatically load that on startup. Something like:

#[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.

@garcia-andy
Copy link

I had an idea. On debug mode, serialize the state structs to the tmp directory, and try automatically load that on startup. Something like:

#[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.

@breynard0
Copy link
Contributor

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.

@ricvelozo
Copy link

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 tmp directory too. See std::env::temp_dir

@breynard0
Copy link
Contributor

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.

@axelkar
Copy link

axelkar commented Oct 15, 2024

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.

On debug mode, serialize the state structs to the tmp directory, and try automatically load that on startup.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
developer experience feature New feature or request help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests