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
Compatibility with FRP event handling #400
Comments
@aepsil0n I love where carboxyl is going, keep it up! I think conrod's possibly already quite compatible? Although it's quite imperative internally, it exposes a very functionalish API. Re decoupling of rendering from event logic - we just landed this in the same PR in which elmesque was integrated :) All event handling, widget state updating, mutation and reactions are handled within the Rendering of all the widgets occurs in a single step when the user calls From a functional API perspective each widget has a couple inputs and outputs:
Perhaps it would be possible to make a wrapper function for a widget like this: fn ui(input: Stream<(&mut AppData, &mut Ui)>) -> Stream<(&mut AppData, &mut Ui)> {
input.map(|(app_data, ui)| {
Toggle::new(app_data.switch)
.react(|new_switch| app_data.switch = new_switch)
.set(SWITCH, ui);
// Other widgets...
(app_data, ui)
})
} Hmmm I'm really just throwing around ideas, what are your thoughts? What would you like to see? |
Unfortunately it won't work just like that. One fundamental aspect of FRP is, that it abstracts away mutable state. Hence, we can't just put a And this is the reason why we probably can't reuse a lot of conrod's existing event handling system, because it appears to rely on mutable state. But anyway I will try to build a simple checkbox with carboxyl and elmesque to demonstrate how the state would be handled in a case like that. Then you can probably judge better, in how far conrod can be made conform to this pattern. In any case, it is already really helpful to have a functional API to create an The more I think about it, it appears to me that FRP actually solves pretty much the same problem as conrod's What could (theoretically) be reused from conrod's event system are the purely functional parts that look like this: /// Map a widget's current state to its new state given an event
fn update(old: Widget, event: Event) -> Widget;
/// Does a mouse click hit a button?
fn hits(button: Button, position: (f64, f64)) -> bool; |
As conrod is currently, widgets are quite heavily dependent upon the |
That's good to know. Let me address these in general terms first. I'm working on a more concrete example at the moment. Contains the state of all widgets which can be indexed via their UiId The behaviour of a widget can be described as a Stores rendering state for each widget until the end of each render cycle In the current examples, the content of the window is assumed to be one big As a side note, maybe it is a good idea to have some kind of Contains the theme used for default styling of the widgets At some point you need a function Maintains the latest user input state (for mouse and keyboard) The |
These all sound great! While I think of it, there are a couple of other things aren't listed that the Relative widget positioning. Conrod allows you to describe layout relatively i.e.
Capturing. The
The only draw state Conrod holds onto is an
Can you elaborate on what you mean by "combine conrod with some other graphics element"? Do you mean giving access to its |
@aepsil0n I liked your article! |
Yes, these two are more problematic. I just stumbled over it trying to refactor the toggle widget's Relative widget positioning. When I understand this correctly, it makes a difference for the layout, in what order Capturing. I think this is essentially a part of event handling. It can be expressed using FRP primitives as well. You just have to filter the input stream for each element taking into account the entire UI. So if I picture the UI as a // keys: Stream<Key>
// ui: Cell<UI>
// widget_id: UiId
ui.snapshot(&keys)
.filter_map(|(ui, key)|
if ui.active_id() == widget_id { Some(key) }
else { None }
) (ignoring the details for now)
Yeah, sorry, that was a bit fuzzy. On one hand, I was talking about a collage with other Conclusion It looks like there's a lot to do. I think I have to prototype a couple of simple UI widgets out-of-tree to get a better idea of the precise API requirements. Anyway, generally it would be very beneficial to factor out the purely functional parts of the conrod internals. |
@aepsil0n #424 is related (a draft for separating |
So, as a quick update: what I did in milibopp/carboxyl#58 should pave the way to allow for some mutable state wrapped in FRP primitives. The essential idea is to provide a functional API around efficient in-place updates via Nonetheless, this change allows one to integrate with conrod's current UI object, until #424 is resolved. |
I've been a bit absent from Rust development lately. But I'd like to give the integration of these two libraries a try once more. I see #424 has been merged. Does that imply that conrod's API can be used in a more functional manner now? |
also care the progress of this topic. |
I am working on some general considerations about reactive architecture with continuous time semantics, which might inform, how to approach this. But I can't really judge whether conrod is sufficiently immutable to allow an immediate integration yet. One interesting note though: I've thrown out |
hard work, but I notice that conrod now is reactive mode, but seems no frp. Maybe author of conrod can explain his design and plan. |
@aepsil0n definitely keep us in the loop! Whether or not this turns out suitable for conrod, I'd love to hear about your findings myself 😸
Hmmm I haven't given too much thought to FRP compatibility since the last time we spoke on this - I haven't had an explicit goal in mind to steer conrod in that direction, but i'd absolutely be open to ideas about improving the API in whatever ways FRP can offer. I think part of the reason I haven't given it a lot of thought is simply due to not having done any FRP in the last year (been pretty exclusively hacking in rust come to think of it!). I've generally been feeling out the design of conrod as I go, trying to find solutions to problems that fit rust's ownership system nicely. This is how the reactive-mode/retained hybrid came to be - the reactive API solves a lot of ownership issues for the user, while the retained internals (see the widget graph, which is pretty much a giant cache and widget relationship description) offer the performance benefits of keeping state around. There's a small bit about this in the guide.
This sounds pretty great! I'd be pretty interested in seeing how this works. Another thing to note is that since the last time we spoke, conrod has stopped using These changes might make it more difficult to interop FRP with conrod, now that conrod no longer returns an I should mention; one of the reasons I didn't change conrod to follow a more elmesque style (as opposed to bringing the graphics closer to conrod's existing widget style) was due to the complexity of the relationships between widgets and the shape of the Graph. Conrod's widgets form a DAG, whereas Anyway, I'm interested to hear both your thoughts on all this! |
Thanks for your update on this. I haven't been following conrod's development much, so this is really appreciated. My general architecture approach strives towards a purely declarative API for users, which lets the library take this apart into all the imperative calls that need to be done to persist state, render stuff, etc. (this is what carboxyl and its dependents do). Your idea sounds similar in some ways. I would also compare this to the virtual DOM approach taken in frontend architecture these days. And I believe it makes sense for us (i.e. Rust devs) to take a similar approach, even though we can skip a lot of stuff, because we don't need to work with a DOM.
Interesting. My guess is, that because imperative event handling does not compose as nicely as FRP does, that it's impossible to write stateful widgets in the same manner as stateless declarative elmesque primitives. Anyhow, I'll write up my architecture ideas for declarative UI components (or widgets) soon. This should give us more concrete grounds for further discussion. But the general direction is Carboxyl streams & signals combined with something adapted from the Elm and Cycle.js architectures. |
@aepsil0n hey there, I'm going to close this as the discussion seems to have died down for now. If you do end up having a play around with declarative FRP style UI, please ping me! Still very much interested to see what approach you take, whether or not conrod turns out to be suitable 👍 |
Uhm, yeah, I neither have the time nor a use case right now to drive this forward. So don't let this clutter your issue list for too long. ;) Will let you know, if I ever pick it up again. |
As I mentioned in this blog post (also nice for motivation and background, in case anybody is unaware of what I'm trying to do), it would be pretty cool, if conrod's UI could be used in conjunction with FRP event logic. Now I assume that conrod does a lot of (imperative) event handling logic on its own. I'm not particularly familiar with its internals though.
I wonder if it is possible to find some common ground between a functional approach and what conrod currently implements. Possibly at least the rendering part could be shared, though I'm afraid most of the event handling logic cannot be shared.
In any case, I would be glad about your thoughts regarding whether it makes sense to try to use parts of conrod in this way and how it could be done. There are two concrete questions:
The text was updated successfully, but these errors were encountered: