summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2021-08-12 12:31:19 +0700
committerLibravatar GitHub <noreply@github.com>2021-08-12 12:31:19 +0700
commit86f5e56c55a3a1aab64efa6f03f52ac80c3b44eb (patch)
treeb45f286fc17ab161530ab4f1292edc4a33101d2c
parent3d640632f124294218442b2494282603a6f2919c (diff)
parentf39e608d7b9dd093c6f1e9b853cc9f8102f15542 (diff)
downloadiced-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
-rw-r--r--web/src/lib.rs165
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()
+ }
+}