diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index 80bb55bb..5ababd8d 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -64,6 +64,25 @@ default-features = false optional = true version = "0.9" +[dependencies.js-sys] +version = "0.3.64" +optional = true + +[dependencies.web-sys] +optional = true +version = "0.3.64" +features = [ + "Window", +] + +[dependencies.wasm-bindgen] +optional = true +version = "0.2.87" + +[dependencies.wasm-bindgen-futures] +optional = true +version = "0.4.37" + [features] default = [] doc-cfg = [] @@ -71,6 +90,7 @@ builder = ["inventory", "cursive-macros/builder"] markdown = ["pulldown-cmark"] ansi = ["ansi-parser"] unstable_scroll = [] # Deprecated feature, remove in next version +wasm = ["js-sys", "web-sys", "wasm-bindgen", "wasm-bindgen-futures"] [lib] name = "cursive_core" diff --git a/cursive-core/src/cursive.rs b/cursive-core/src/cursive.rs index e15fe29f..e45f4494 100644 --- a/cursive-core/src/cursive.rs +++ b/cursive-core/src/cursive.rs @@ -840,8 +840,8 @@ impl Cursive { /// Runs a dummy event loop. /// /// Initializes a dummy backend for the event loop. - pub fn run_dummy(&mut self) { - self.run_with(backend::Dummy::init) + pub async fn run_dummy(&mut self) { + self.run_with(backend::Dummy::init).await } /// Returns a new runner on the given backend. @@ -871,23 +871,23 @@ impl Cursive { /// Initialize the backend and runs the event loop. /// /// Used for infallible backend initializers. - pub fn run_with(&mut self, backend_init: F) + pub async fn run_with(&mut self, backend_init: F) where F: FnOnce() -> Box, { - self.try_run_with::<(), _>(|| Ok(backend_init())).unwrap(); + self.try_run_with::<(), _>(|| Ok(backend_init())).await.unwrap(); } /// Initialize the backend and runs the event loop. /// /// Returns an error if initializing the backend fails. - pub fn try_run_with(&mut self, backend_init: F) -> Result<(), E> + pub async fn try_run_with(&mut self, backend_init: F) -> Result<(), E> where F: FnOnce() -> Result, E>, { let mut runner = self.runner(backend_init()?); - runner.run(); + runner.run().await; Ok(()) } diff --git a/cursive-core/src/cursive_run.rs b/cursive-core/src/cursive_run.rs index 69ede54a..c0f92752 100644 --- a/cursive-core/src/cursive_run.rs +++ b/cursive-core/src/cursive_run.rs @@ -1,5 +1,6 @@ use crate::{backend, event::Event, theme, Cursive, Vec2}; use std::borrow::{Borrow, BorrowMut}; +#[cfg(not(feature = "async"))] use std::time::Duration; // How long we wait between two empty input polls @@ -151,7 +152,7 @@ where /// [1]: CursiveRunner::run() /// [2]: CursiveRunner::step() /// [3]: CursiveRunner::process_events() - pub fn post_events(&mut self, received_something: bool) { + pub async fn post_events(&mut self, received_something: bool) { let boring = !received_something; // How many times should we try if it's still boring? // Total duration will be INPUT_POLL_DELAY_MS * repeats @@ -175,11 +176,36 @@ where } if boring { - std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); + self.sleep().await; self.boring_frame_count += 1; } } + #[cfg(not(feature = "wasm"))] + async fn sleep(&self) { + std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); + } + + #[cfg(feature = "wasm")] + async fn sleep(&self) { + use wasm_bindgen::prelude::*; + let promise = js_sys::Promise::new(&mut |resolve, _| { + let closure = Closure::new(move || { + resolve.call0(&JsValue::null()).unwrap(); + }) as Closure; + web_sys::window() + .expect("window is None for sleep") + .set_timeout_with_callback_and_timeout_and_arguments_0( + closure.as_ref().unchecked_ref(), + INPUT_POLL_DELAY_MS as i32, + ) + .expect("should register timeout for sleep"); + closure.forget(); + }); + let js_future = wasm_bindgen_futures::JsFuture::from(promise); + js_future.await.expect("should await sleep"); + } + /// Refresh the screen with the current view tree state. pub fn refresh(&mut self) { self.boring_frame_count = 0; @@ -211,9 +237,9 @@ where /// during this step, and `false` otherwise. /// /// [`run(&mut self)`]: #method.run - pub fn step(&mut self) -> bool { + pub async fn step(&mut self) -> bool { let received_something = self.process_events(); - self.post_events(received_something); + self.post_events(received_something).await; received_something } @@ -230,12 +256,12 @@ where /// /// [`step(&mut self)`]: #method.step /// [`quit(&mut self)`]: #method.quit - pub fn run(&mut self) { + pub async fn run(&mut self) { self.refresh(); // And the big event loop begins! while self.is_running() { - self.step(); + self.step().await; } - } + } } diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 5a64d6a5..f723a1d3 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -25,6 +25,7 @@ libc = "0.2" maplit = { version = "1.0", optional = true } log = "0.4" ahash = "0.8" +async-trait = "0.1.73" [dependencies.bear-lib-terminal] optional = true @@ -48,6 +49,23 @@ version = "2" optional = true version = "0.26" +[dependencies.wasm-bindgen] +optional = true +version = "0.2.63" + +[dependencies.web-sys] +optional = true +version = "0.3.64" +features = [ + "HtmlCanvasElement", + "CanvasRenderingContext2d", + "KeyboardEvent", + "TextMetrics", + "Window", + "Document", + "console", +] + [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. builder = ["cursive_core/builder"] @@ -61,6 +79,7 @@ markdown = ["cursive_core/markdown"] ansi = ["cursive_core/ansi"] unstable_scroll = [] # Deprecated feature, remove in next version toml = ["cursive_core/toml"] +wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm"] [lib] name = "cursive" diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js new file mode 100644 index 00000000..2e560dc0 --- /dev/null +++ b/cursive/src/backends/canvas.js @@ -0,0 +1,34 @@ +const fontWidth = 12; +const fontHeight = fontWidth * 2; +const textColorPairSize = 12; + +export function paint(buffer) { + const data = new Uint8Array(buffer); + const canvas = document.getElementById('cursive-wasm-canvas'); + const context = canvas.getContext('2d'); + const WIDTH = 100; + const HEIGHT = 100; + context.font = `${fontHeight - 2}px monospace`; + for (let x = 0; x < WIDTH; x++) { + for (let y = 0; y < HEIGHT; y++) { + const n = WIDTH * y + x; + const textColorPair = data.slice(n * textColorPairSize, (n + 1) * textColorPairSize); + const text = String.fromCharCode(textColorPair[0] + (2**8) *textColorPair[1] + (2**16)* textColorPair[2] + (2 ** 24) + textColorPair[3]); + const front = byte_to_hex_string(textColorPair.slice(4, 7)); + const back = byte_to_hex_string(textColorPair.slice(7, 10)); + context.fillStyle = back; + context.fillRect(x * fontWidth, y * fontHeight, fontWidth, fontHeight); + if (text != ' ') { + context.fillStyle = front; + context.fillText(text, x * fontWidth, (y + 0.8) * fontHeight); + } + } + } +} + +function byte_to_hex_string(bytes) { + const red = bytes[0].toString(16).padStart(2, '0'); + const green = bytes[1].toString(16).padStart(2, '0'); + const blue = bytes[2].toString(16).padStart(2, '0'); + return `#${red}${green}${blue}`; +} \ No newline at end of file diff --git a/cursive/src/backends/mod.rs b/cursive/src/backends/mod.rs index 342dd550..c4796be4 100644 --- a/cursive/src/backends/mod.rs +++ b/cursive/src/backends/mod.rs @@ -16,6 +16,8 @@ pub mod crossterm; pub mod curses; pub mod puppet; pub mod termion; +/// Provides a backend using the `wasm`. +pub mod wasm; #[allow(dead_code)] fn boxed(e: impl std::error::Error + 'static) -> Box { @@ -30,6 +32,7 @@ fn boxed(e: impl std::error::Error + 'static) -> Box { /// * Crossterm /// * Pancurses /// * Ncurses +/// * wasm /// * Dummy pub fn try_default() -> Result, Box> { @@ -44,6 +47,8 @@ pub fn try_default() -> Result, Box Self { + Self { + text, + color, + } + } +} + +impl Clone for ScreenChar { + fn clone(&self) -> Self { + Self { + text: self.text, + color: self.color.clone(), + } + } +} + +#[repr(transparent)] +struct Buffer { + chars: Vec, +} + +impl Buffer { + pub fn new(color: ColorPair) -> Self { + Self { + chars: vec![ScreenChar::new(' ', color.clone()); BUFFER_WIDTH * BUFFER_HEIGHT], + } + } + + pub fn set(self: &mut Buffer, index: usize, screen_char: ScreenChar) { + self.chars[index] = screen_char; + } +} + +struct Writer { + buffer: Buffer, + color: ColorPair, +} + +impl Writer { + pub fn new() -> Self { + let color = terminal_default_color_pair(); + Self { + buffer: Buffer::new(color.clone()), + color, + } + } + + pub fn write(self: &mut Writer, pos: Vec2, text: &str) { + for (i, c) in text.chars().enumerate() { + let x = pos.x + i; + self.buffer.set(BUFFER_WIDTH * pos.y + x, ScreenChar::new(c, self.color.clone())); + } + } + + pub fn set_color(self: &mut Writer, color: ColorPair) { + self.color = color; + } + + pub fn clear(self: &mut Writer, color: Color) { + let screen_char = ScreenChar::new(' ', ColorPair::new(color, color)); + + self.buffer + .chars + .iter_mut() + .for_each(|c| *c = screen_char.clone()); + } + + pub fn buffer(self: &Writer) -> &Vec { + &self.buffer.chars + } +} + +/// Type of hex color which is r,g,b +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(C)] +struct Color { + red: u8, + green: u8, + blue: u8 +} + +impl Color { + /// Creates a new `Color` with the given red, green, and blue values. + pub fn new(red: u8, green: u8, blue: u8) -> Self { + Self { + red, + green, + blue, + } + } +} + +use cursive_core::theme::{ Color as CColor, BaseColor as CBaseColor }; + +/// Convert cursive color to hex color. +fn cursive_to_color(color: CColor) -> Color { + match color { + CColor::Dark(CBaseColor::Black) => Color::new(0,0,0), + CColor::Dark(CBaseColor::Red) => Color::new(128,0,0), + CColor::Dark(CBaseColor::Green) => Color::new(0,128,0), + CColor::Dark(CBaseColor::Yellow) => Color::new(128,128,0), + CColor::Dark(CBaseColor::Blue) => Color::new(0,0,128), + CColor::Dark(CBaseColor::Magenta) => Color::new(128,0,128), + CColor::Dark(CBaseColor::Cyan) => Color::new(0,128,128), + CColor::Dark(CBaseColor::White) => Color::new(182,182,182), + CColor::Light(CBaseColor::Black) => Color::new(128,128,128), + CColor::Light(CBaseColor::Red) => Color::new(255,0,0), + CColor::Light(CBaseColor::Green) => Color::new(0,255, 0), + CColor::Light(CBaseColor::Yellow) => Color::new(255,255,0), + CColor::Light(CBaseColor::Blue) => Color::new(0,0,255), + CColor::Light(CBaseColor::Magenta) => Color::new(255,0,255), + CColor::Light(CBaseColor::Cyan) => Color::new(0,255,255), + CColor::Light(CBaseColor::White) => Color::new(255,255,255), + CColor::Rgb(r, g, b) => Color::new(r,g,b), + CColor::RgbLowRes(r,g ,b ) => Color::new(r,g,b), + CColor::TerminalDefault => Color::new(0,255,0), + } +} + +/// Type of color pair. +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(C)] +struct ColorPair { + /// Foreground text color. + pub front: Color, + /// Background color. + pub back: Color, +} + +impl ColorPair { + /// Creates a new `ColorPair` with the given foreground and background colors. + pub fn new(front: Color, back: Color) -> Self { + Self { + front, + back, + } + } +} + +use cursive_core::theme::ColorPair as CColorPair; +/// Convert cursive color pair to hex color pair. +fn cursive_to_color_pair(c: CColorPair) -> ColorPair { + ColorPair { + front: cursive_to_color(c.front), + back: cursive_to_color(c.back), + } +} + +fn terminal_default_color_pair() -> ColorPair { + cursive_to_color_pair(CColorPair { + front: CColor::Light(CBaseColor::Black), + back:CColor::Dark(CBaseColor::Green), + }) +} + +#[wasm_bindgen(module = "/src/backends/canvas.js")] +extern "C" { + fn paint(buffer: &[u8]); +} + +use std::collections::VecDeque; +use std::rc::Rc; +use std::cell::RefCell; +use web_sys::{ Document, HtmlCanvasElement }; + +/// Backend using wasm. +pub struct Backend { + canvas: HtmlCanvasElement, + events: Rc>>, + writer: RefCell, +} + +use cursive_core::{ + event::{ Event, Key }, + Vec2, + theme::Effect, +}; + +impl Backend { + /// Creates a new Cursive root using a wasm backend and given HTML canvas. + pub fn new(canvas: HtmlCanvasElement) -> std::io::Result> { + let document = Self::document()?; + let events = Rc::new(RefCell::new(VecDeque::new())); + let cloned = events.clone(); + let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { + let event = Self::to_cursive_event(event); + if event != Event::Unknown(Vec::new()) { + cloned.borrow_mut().push_back(event); + } + }) as Box); + document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) + .map_err(|_| std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to add event listener", + ))?; + closure.forget(); + + let c = Backend { + canvas, + events, + writer: RefCell::new(Writer::new()), + }; + Ok(Box::new(c)) + } + + /// Creates a new Cursive root using a wasm backend. + pub fn init() -> std::io::Result> { + let canvas = Self::canvas()?; + canvas.set_width(1000); + canvas.set_height(1000); + + Self::new(canvas) + } + + fn document() -> Result { + web_sys::window() + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to get window", + ))? + .document() + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to get document", + )) + } + + fn canvas() -> Result { + Self::document()? + .get_element_by_id("cursive-wasm-canvas") + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to get window", + ))? + .dyn_into::() + .map_err(|_| std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to cast canvas", + )) + } + + fn terminal_default_color() -> ColorPair { + terminal_default_color_pair() + } + + fn to_cursive_event(event: web_sys::KeyboardEvent) -> Event { + match event.key_code() { + 8 => Event::Key(Key::Backspace), + 9 => Event::Key(Key::Tab), + 13 => Event::Key(Key::Enter), + 19 => Event::Key(Key::PauseBreak), + 27 => Event::Key(Key::Esc), + 33 => Event::Key(Key::PageUp), + 34 => Event::Key(Key::PageDown), + 35 => Event::Key(Key::End), + 36 => Event::Key(Key::Home), + 37 => Event::Key(Key::Left), + 38 => Event::Key(Key::Up), + 39 => Event::Key(Key::Right), + 40 => Event::Key(Key::Down), + 45 => Event::Key(Key::Ins), + 46 => Event::Key(Key::Del), + 101 => Event::Key(Key::NumpadCenter), + 112 => Event::Key(Key::F1), + 113 => Event::Key(Key::F2), + 114 => Event::Key(Key::F3), + 115 => Event::Key(Key::F4), + 116 => Event::Key(Key::F5), + 117 => Event::Key(Key::F6), + 118 => Event::Key(Key::F7), + 119 => Event::Key(Key::F8), + 120 => Event::Key(Key::F9), + 121 => Event::Key(Key::F10), + 122 => Event::Key(Key::F11), + 123 => Event::Key(Key::F12), + code => { + if let Some(c) = std::char::from_u32(code) { + Event::Char(c) + } else { Event::Unknown(Vec::new()) } + } + } + } + + fn text_color_pairs_to_bytes(self: &Backend) -> &[u8] { + let binding = self.writer.borrow(); + let data = binding.buffer(); + unsafe { + std::slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * std::mem::size_of::(), + ) + } + } +} + +impl cursive_core::backend::Backend for Backend { + fn poll_event(self: &mut Backend) -> Option { + self.events.borrow_mut().pop_front() + } + + fn set_title(self: &mut Backend, title: String) { + self.canvas.set_title(&title); + } + + fn refresh(self: &mut Backend) { + paint(self.text_color_pairs_to_bytes()); + } + + fn has_colors(self: &Backend) -> bool { + true + } + + fn screen_size(self: &Backend) -> Vec2 { + Vec2::new(BUFFER_WIDTH, BUFFER_HEIGHT) + } + + fn print_at(self: &Backend, pos: Vec2, text: &str) { + self.writer.borrow_mut().write(pos, text); + } + + fn clear(self: &Backend, color: CColor) { + self.writer.borrow_mut().clear(cursive_to_color(color)) + } + + fn set_color(self: &Backend, color_pair: CColorPair) -> CColorPair { + self.writer.borrow_mut().set_color(cursive_to_color_pair(color_pair)); + color_pair + } + + fn set_effect(self: &Backend, _: Effect) { + } + + fn unset_effect(self: &Backend, _: Effect) { + } + + fn name(&self) -> &str { + "cursive-wasm-backend" + } +} diff --git a/cursive/src/cursive_ext.rs b/cursive/src/cursive_ext.rs index 516357cb..badfc9e7 100644 --- a/cursive/src/cursive_ext.rs +++ b/cursive/src/cursive_ext.rs @@ -1,3 +1,6 @@ + +use async_trait::async_trait; + /// Extension trait for the `Cursive` root to simplify initialization. /// /// It brings backend-specific methods to initialize a `Cursive` root. @@ -15,16 +18,19 @@ /// /// // Or explicitly use a specific backend /// #[cfg(feature = "ncurses-backend")] -/// siv.run_ncurses().unwrap(); +/// siv.run_ncurses(); /// #[cfg(feature = "panncurses-backend")] -/// siv.run_pancurses().unwrap(); +/// siv.run_pancurses(); /// #[cfg(feature = "termion-backend")] -/// siv.run_termion().unwrap(); +/// siv.run_termion(); /// #[cfg(feature = "crossterm-backend")] -/// siv.run_crossterm().unwrap(); +/// siv.run_crossterm(); /// #[cfg(feature = "blt-backend")] /// siv.run_blt(); +/// #[cfg(feature = "wasm-backend")] +/// siv.run_wasm(); /// ``` +#[async_trait(?Send)] pub trait CursiveExt { /// Tries to use one of the enabled backends. /// @@ -33,81 +39,95 @@ pub trait CursiveExt { /// # Panics /// /// If the backend initialization fails. - fn run(&mut self); + async fn run(&mut self); /// Creates a new Cursive root using a ncurses backend. #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] - fn run_ncurses(&mut self) -> std::io::Result<()>; + async fn run_ncurses(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a pancurses backend. #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] - fn run_pancurses(&mut self) -> std::io::Result<()>; + async fn run_pancurses(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a termion backend. #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] - fn run_termion(&mut self) -> std::io::Result<()>; + async fn run_termion(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a crossterm backend. #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] - fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind>; + async fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind>; /// Creates a new Cursive root using a bear-lib-terminal backend. #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] - fn run_blt(&mut self); + async fn run_blt(&mut self); + + /// Creates a new Cursive root using a wasm backend. + #[cfg(feature = "wasm-backend")] + #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "wasm-backend")))] + async fn run_wasm(&mut self) -> Result<(), std::io::Error>; } +#[async_trait(?Send)] impl CursiveExt for cursive_core::Cursive { - fn run(&mut self) { + async fn run(&mut self) { cfg_if::cfg_if! { if #[cfg(feature = "blt-backend")] { - self.run_blt() + self.run_blt().await } else if #[cfg(feature = "termion-backend")] { - self.run_termion().unwrap() + self.run_termion().await.unwrap() } else if #[cfg(feature = "crossterm-backend")] { - self.run_crossterm().unwrap() + self.run_crossterm().await.unwrap() } else if #[cfg(feature = "pancurses-backend")] { - self.run_pancurses().unwrap() + self.run_pancurses().await.unwrap() } else if #[cfg(feature = "ncurses-backend")] { - self.run_ncurses().unwrap() + self.run_ncurses().await.unwrap() + } else if #[cfg(feature = "wasm-backend")] { + self.run_wasm().await.unwrap() } else { log::warn!("No built-it backend, falling back to Cursive::dummy()."); - self.run_dummy() + self.run_dummy().await } } } #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "curses-backend")))] - fn run_ncurses(&mut self) -> std::io::Result<()> { - self.try_run_with(crate::backends::curses::n::Backend::init) + async fn run_ncurses(&mut self) -> std::io::Result<()> { + self.try_run_with(crate::backends::curses::n::Backend::init).await } #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] - fn run_pancurses(&mut self) -> std::io::Result<()> { - self.try_run_with(crate::backends::curses::pan::Backend::init) + async fn run_pancurses(&mut self) -> std::io::Result<()> { + self.try_run_with(crate::backends::curses::pan::Backend::init).await } #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] - fn run_termion(&mut self) -> std::io::Result<()> { - self.try_run_with(crate::backends::termion::Backend::init) + async fn run_termion(&mut self) -> std::io::Result<()> { + self.try_run_with(crate::backends::termion::Backend::init).await } #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] - fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind> { - self.try_run_with(crate::backends::crossterm::Backend::init) + async fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind> { + self.try_run_with(crate::backends::crossterm::Backend::init).await } #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] - fn run_blt(&mut self) { - self.run_with(crate::backends::blt::Backend::init) + async fn run_blt(&mut self) { + self.run_with(crate::backends::blt::Backend::init).await + } + + #[cfg(feature = "wasm-backend")] + #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "wasm-backend")))] + async fn run_wasm(&mut self) -> Result<(), std::io::Error> { + self.try_run_with(crate::backends::wasm::Backend::init).await } } diff --git a/cursive/src/cursive_runnable.rs b/cursive/src/cursive_runnable.rs index 19eb8fe3..b102d92b 100644 --- a/cursive/src/cursive_runnable.rs +++ b/cursive/src/cursive_runnable.rs @@ -84,13 +84,13 @@ impl CursiveRunnable { /// # Panics /// /// If the backend initialization fails. - pub fn run(&mut self) { - self.try_run().unwrap(); + pub async fn run(&mut self) { + self.try_run().await.unwrap(); } /// Runs the event loop with the registered backend initializer. - pub fn try_run(&mut self) -> Result<(), Box> { - self.siv.try_run_with(&mut self.backend_init) + pub async fn try_run(&mut self) -> Result<(), Box> { + self.siv.try_run_with(&mut self.backend_init).await } /// Gets a runner with the registered backend. @@ -194,4 +194,12 @@ impl CursiveRunnable { pub fn blt() -> Self { Self::new::(|| Ok(backends::blt::Backend::init())) } + + /// Creates a new Cursive wrapper using the wasm backend. + /// + /// _Requires the `wasm-backend` feature._ + #[cfg(feature = "wasm-backend")] + pub fn wasm() -> Self { + Self::new(backends::wasm::Backend::init) + } } diff --git a/cursive/src/lib.rs b/cursive/src/lib.rs index 3fea3750..f29b3712 100644 --- a/cursive/src/lib.rs +++ b/cursive/src/lib.rs @@ -130,6 +130,12 @@ pub fn blt() -> CursiveRunnable { CursiveRunnable::blt() } +/// Creates a new Cursive root using a crossterm backend. +#[cfg(feature = "wasm-backend")] +pub fn wasm() -> CursiveRunnable { + CursiveRunnable::wasm() +} + /// Creates a new Cursive root using a dummy backend. /// /// Nothing will be output. This is mostly here for tests.