diff options
author | 2021-08-12 12:31:19 +0700 | |
---|---|---|
committer | 2021-08-12 12:31:19 +0700 | |
commit | 86f5e56c55a3a1aab64efa6f03f52ac80c3b44eb (patch) | |
tree | b45f286fc17ab161530ab4f1292edc4a33101d2c /web/src/lib.rs | |
parent | 3d640632f124294218442b2494282603a6f2919c (diff) | |
parent | f39e608d7b9dd093c6f1e9b853cc9f8102f15542 (diff) | |
download | iced-86f5e56c55a3a1aab64efa6f03f52ac80c3b44eb.tar.gz iced-86f5e56c55a3a1aab64efa6f03f52ac80c3b44eb.tar.bz2 iced-86f5e56c55a3a1aab64efa6f03f52ac80c3b44eb.zip |
Merge pull request #517 from Kaiden42/embedded
iced_web: Add an option to select the element
Diffstat (limited to 'web/src/lib.rs')
-rw-r--r-- | web/src/lib.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/web/src/lib.rs b/web/src/lib.rs index 6b7d0115..bb09bb0d 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -244,3 +244,168 @@ where .finish() } } + +/// An interactive embedded web application. +/// +/// This trait is the main entrypoint of Iced. Once implemented, you can run +/// your GUI application by simply calling [`run`](#method.run). It will either +/// take control of the `<body>' or of an HTML element of the document specified +/// by `container_id`. +/// +/// An [`Embedded`](trait.Embedded.html) can execute asynchronous actions +/// by returning a [`Command`](struct.Command.html) in some of its methods. +pub trait Embedded { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [`executor::WasmBindgen`] can be a good choice for the Web. + /// + /// [`Executor`]: trait.Executor.html + /// [`executor::Default`]: executor/struct.Default.html + type Executor: Executor; + + /// The type of __messages__ your [`Embedded`] application will produce. + /// + /// [`Embedded`]: trait.Embedded.html + type Message: Send; + + /// The data needed to initialize your [`Embedded`] application. + /// + /// [`Embedded`]: trait.Embedded.html + type Flags; + + /// Initializes the [`Embedded`] application. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`](struct.Command.html) if you + /// need to perform some async action in the background on startup. This is + /// useful if you want to load state from a file, perform an initial HTTP + /// request, etc. + /// + /// [`Embedded`]: trait.Embedded.html + fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) + where + Self: Sized; + + /// Handles a __message__ and updates the state of the [`Embedded`] + /// application. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Embedded`]: trait.Embedded.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + + /// Returns the widgets to display in the [`Embedded`] application. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Embedded`]: trait.Embedded.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Returns the event [`Subscription`] for the current state of the embedded + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + fn subscription(&self) -> Subscription<Self::Message> { + Subscription::none() + } + + /// Runs the [`Embedded`] application. + /// + /// [`Embedded`]: trait.Embedded.html + fn run(flags: Self::Flags, container_id: Option<String>) + where + Self: 'static + Sized, + { + use futures::stream::StreamExt; + use wasm_bindgen::JsCast; + use web_sys::HtmlElement; + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let container: HtmlElement = container_id + .map(|id| document.get_element_by_id(&id).unwrap()) + .map(|container| { + container.dyn_ref::<HtmlElement>().unwrap().to_owned() + }) + .unwrap_or_else(|| document.body().unwrap()); + + let (sender, receiver) = + iced_futures::futures::channel::mpsc::unbounded(); + + let mut runtime = iced_futures::Runtime::new( + Self::Executor::new().expect("Create executor"), + sender.clone(), + ); + + let (app, command) = runtime.enter(|| Self::new(flags)); + + runtime.spawn(command); + + let application = Rc::new(RefCell::new(app)); + + let instance = EmbeddedInstance { + application: application.clone(), + bus: Bus::new(sender), + }; + + let vdom = dodrio::Vdom::new(&container, instance); + + let event_loop = receiver.for_each(move |message| { + let (command, subscription) = runtime.enter(|| { + let command = application.borrow_mut().update(message); + let subscription = application.borrow().subscription(); + + (command, subscription) + }); + + runtime.spawn(command); + runtime.track(subscription); + + vdom.weak().schedule_render(); + + futures::future::ready(()) + }); + + wasm_bindgen_futures::spawn_local(event_loop); + } +} + +struct EmbeddedInstance<A: Embedded> { + application: Rc<RefCell<A>>, + bus: Bus<A::Message>, +} + +impl<'a, A> dodrio::Render<'a> for EmbeddedInstance<A> +where + A: Embedded, +{ + fn render( + &self, + context: &mut dodrio::RenderContext<'a>, + ) -> dodrio::Node<'a> { + use dodrio::builder::*; + + let mut ui = self.application.borrow_mut(); + let element = ui.view(); + let mut css = Css::new(); + + let node = element.widget.node(context.bump, &self.bus, &mut css); + + div(context.bump) + .attr("style", "width: 100%; height: 100%") + .children(vec![css.node(context.bump), node]) + .finish() + } +} |