From e219ed8f007f975df577b43aaffa8e512ef3ceee Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Thu, 13 Jul 2023 15:35:55 +0900 Subject: [PATCH 01/19] add wasm-backend using HTML canvas element(#1) * add wasm-backend * add wasm configuration and replace sleep * add wasm-backend --- cursive-core/Cargo.toml | 12 +- cursive-core/src/cursive_run.rs | 20 +++- cursive/Cargo.toml | 25 +++- cursive/src/backends/mod.rs | 5 + cursive/src/backends/wasm.rs | 194 ++++++++++++++++++++++++++++++++ cursive/src/cursive_ext.rs | 13 +++ cursive/src/cursive_runnable.rs | 8 ++ cursive/src/lib.rs | 6 + 8 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 cursive/src/backends/wasm.rs diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index 80bb55bb..08038af2 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -64,13 +64,23 @@ default-features = false optional = true version = "0.9" +[dependencies.js-sys] +version = "0.3.64" +optional = true + +[dependencies.getrandom] +optional = true +version = "0.2" +features = ["js"] + [features] -default = [] +default = ["wasm"] doc-cfg = [] builder = ["inventory", "cursive-macros/builder"] markdown = ["pulldown-cmark"] ansi = ["ansi-parser"] unstable_scroll = [] # Deprecated feature, remove in next version +wasm = ["js-sys", "getrandom"] [lib] name = "cursive_core" diff --git a/cursive-core/src/cursive_run.rs b/cursive-core/src/cursive_run.rs index 69ede54a..10137aaf 100644 --- a/cursive-core/src/cursive_run.rs +++ b/cursive-core/src/cursive_run.rs @@ -1,7 +1,11 @@ use crate::{backend, event::Event, theme, Cursive, Vec2}; use std::borrow::{Borrow, BorrowMut}; +#[cfg(not(feature = "wasm"))] use std::time::Duration; +#[cfg(feature = "wasm")] +use js_sys::Date; + // How long we wait between two empty input polls const INPUT_POLL_DELAY_MS: u64 = 30; @@ -175,11 +179,25 @@ where } if boring { - std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); + self.sleep(); self.boring_frame_count += 1; } } + #[cfg(not(feature = "wasm"))] + fn sleep(&self) { + std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); + } + + #[cfg(feature = "wasm")] + fn sleep(&self) { + let start = Date::now(); + let mut now = start; + while (now - start) < INPUT_POLL_DELAY_MS as f64 { + now = Date::now(); + } + } + /// Refresh the screen with the current view tree state. pub fn refresh(&mut self) { self.boring_frame_count = 0; diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 5a64d6a5..737ee9bd 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -48,11 +48,33 @@ 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", +] + +[dependencies.getrandom] +optional = true +version = "0.2" +features = ["js"] + + [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. builder = ["cursive_core/builder"] blt-backend = ["bear-lib-terminal"] -default = ["ncurses-backend"] +default = ["wasm-backend"] ncurses-backend = ["ncurses", "maplit"] pancurses-backend = ["pancurses", "maplit"] termion-backend = ["termion"] @@ -61,6 +83,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", "getrandom"] [lib] name = "cursive" 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, + font_height: usize, + font_width: usize, + events: Rc>>, +} +impl Backend { + /// Creates a new Cursive root using a wasm backend. + pub fn init() -> std::io::Result> { + let document = 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", + ))?; + let canvas = document.create_element("canvas") + .map_err(|_| std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to create canvas", + ))? + .dyn_into::() + .map_err(|_| std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to cast canvas", + ))?; + canvas.set_width(1000); + canvas.set_height(1000); + + let font_width = 12; + let font_height = font_width * 2; + let ctx: CanvasRenderingContext2d = canvas.get_context("2d") + .map_err(|_| std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to get canvas context", + ))? + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to get canvas context", + ))? + .dyn_into::() + .map_err(|_| std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to cast canvas context", + ))?; + ctx.set_font(&format!("{}px monospace", font_height)); + + let color = RefCell::new(cursive_to_color_pair(theme::ColorPair { + front: theme::Color::Light(theme::BaseColor::White), + back:theme::Color::Dark(theme::BaseColor::Black), + })); + + let events = Rc::new(RefCell::new(VecDeque::new())); + let cloned = events.clone(); + let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { + for c in event.key().chars() { + cloned.borrow_mut().push_back(Event::Char(c)); + } + }) as Box); + canvas.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, + ctx, + color, + font_height, + font_width, + events, + }; + Ok(Box::new(c)) + } +} + +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) {} + + fn has_colors(self: &Backend) -> bool { + true + } + + fn screen_size(self: &Backend) -> Vec2 { + Vec2::new(self.canvas.width() as usize, self.canvas.height() as usize) + } + + fn print_at(self: &Backend, pos: Vec2, text: &str) { + let color = self.color.borrow(); + self.ctx.set_fill_style(&JsValue::from_str(&color.back)); + self.ctx.fill_rect((pos.x * self.font_width) as f64, (pos.y * self.font_height) as f64, (self.font_width * text.len()) as f64, self.font_height as f64); + self.ctx.set_fill_style(&JsValue::from_str(&color.front)); + self.ctx.fill_text(text, (pos.x * self.font_width) as f64, (pos.y * self.font_height + self.font_height * 3/4) as f64).unwrap(); + } + + fn clear(self: &Backend, color: cursive_core::theme::Color) { + self.ctx.set_fill_style(&JsValue::from_str(&cursive_to_color(color))); + } + + fn set_color(self: &Backend, color_pair: cursive_core::theme::ColorPair) -> cursive_core::theme::ColorPair { + let mut color = self.color.borrow_mut(); + *color = cursive_to_color_pair(color_pair); + color_pair + } + + fn set_effect(self: &Backend, _: cursive_core::theme::Effect) { + } + + fn unset_effect(self: &Backend, _: cursive_core::theme::Effect) { + } + + fn name(&self) -> &str { + "cursive-wasm-backend" + } +} + + +/// Type of hex color which starts with #. +pub type Color = String; + +/// Type of color pair. +pub struct ColorPair { + /// Foreground text color. + pub front: Color, + /// Background color. + pub back: Color, +} + +/// Convert cursive color to hex color. +pub fn cursive_to_color(color: theme::Color) -> Color { + match color { + theme::Color::Dark(theme::BaseColor::Black) => "#000000".to_string(), + theme::Color::Dark(theme::BaseColor::Red) => "#800000".to_string(), + theme::Color::Dark(theme::BaseColor::Green) => "#008000".to_string(), + theme::Color::Dark(theme::BaseColor::Yellow) => "#808000".to_string(), + theme::Color::Dark(theme::BaseColor::Blue) => "#000080".to_string(), + theme::Color::Dark(theme::BaseColor::Magenta) => "#800080".to_string(), + theme::Color::Dark(theme::BaseColor::Cyan) => "#008080".to_string(), + theme::Color::Dark(theme::BaseColor::White) => "#c0c0c0".to_string(), + theme::Color::Light(theme::BaseColor::Black) => "#808080".to_string(), + theme::Color::Light(theme::BaseColor::Red) => "#ff0000".to_string(), + theme::Color::Light(theme::BaseColor::Green) => "#00ff00".to_string(), + theme::Color::Light(theme::BaseColor::Yellow) => "#ffff00".to_string(), + theme::Color::Light(theme::BaseColor::Blue) => "#0000ff".to_string(), + theme::Color::Light(theme::BaseColor::Magenta) => "#ff00ff".to_string(), + theme::Color::Light(theme::BaseColor::Cyan) => "#00ffff".to_string(), + theme::Color::Light(theme::BaseColor::White) => "#ffffff".to_string(), + theme::Color::Rgb(r, g, b) => format!("#{:02x}{:02x}{:02x}", r, g, b).to_string(), + theme::Color::RgbLowRes(r,g ,b ) => format!("#{:01x}{:01x}{:01x}", r, g, b).to_string(), + theme::Color::TerminalDefault => "#00ff00".to_string(), + } +} + +/// Convert cursive color pair to hex color pair. +pub fn cursive_to_color_pair(c: theme::ColorPair) -> ColorPair { + ColorPair { + front: cursive_to_color(c.front), + back: cursive_to_color(c.back), + } +} diff --git a/cursive/src/cursive_ext.rs b/cursive/src/cursive_ext.rs index 516357cb..2830be67 100644 --- a/cursive/src/cursive_ext.rs +++ b/cursive/src/cursive_ext.rs @@ -24,6 +24,8 @@ /// siv.run_crossterm().unwrap(); /// #[cfg(feature = "blt-backend")] /// siv.run_blt(); +/// #[cfg(feature="wasm-backend")] +/// siv.run_wasm().unwrap(); /// ``` pub trait CursiveExt { /// Tries to use one of the enabled backends. @@ -59,6 +61,10 @@ pub trait CursiveExt { #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] fn run_blt(&mut self); + + /// Creates a new Cursive root using a wasm backend. + #[cfg(feature = "wasm-backend")] + fn run_wasm(&mut self) -> std::io::Result<()>; } impl CursiveExt for cursive_core::Cursive { @@ -74,6 +80,8 @@ impl CursiveExt for cursive_core::Cursive { self.run_pancurses().unwrap() } else if #[cfg(feature = "ncurses-backend")] { self.run_ncurses().unwrap() + } else if #[cfg(feature = "wasm-backend")] { + self.run_wasm().unwrap() } else { log::warn!("No built-it backend, falling back to Cursive::dummy()."); self.run_dummy() @@ -110,4 +118,9 @@ impl CursiveExt for cursive_core::Cursive { fn run_blt(&mut self) { self.run_with(crate::backends::blt::Backend::init) } + + #[cfg(feature = "wasm-backend")] + fn run_wasm(&mut self) -> std::io::Result<()> { + self.try_run_with(crate::backends::wasm::Backend::init) + } } diff --git a/cursive/src/cursive_runnable.rs b/cursive/src/cursive_runnable.rs index 19eb8fe3..1dd8bd1c 100644 --- a/cursive/src/cursive_runnable.rs +++ b/cursive/src/cursive_runnable.rs @@ -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. From 5db16fc16d64024c08bf5c5a42732d83d7a32193 Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Tue, 25 Jul 2023 09:14:25 +0900 Subject: [PATCH 02/19] add wasm-backend * add wasm-backend * add wasm configuration and replace sleep * add wasm-backend * fix busy_waiting * fix busy waiting * add buffer to print * add js module * change passing parameter as pointer --- cursive-core/Cargo.toml | 18 +++- cursive-core/src/cursive.rs | 24 +++++ cursive-core/src/cursive_run.rs | 75 ++++++++++++-- cursive/Cargo.toml | 6 +- cursive/src/backends/canvas.js | 48 +++++++++ cursive/src/backends/wasm.rs | 172 +++++++++++++++++++------------- cursive/src/cursive_runnable.rs | 7 ++ 7 files changed, 273 insertions(+), 77 deletions(-) create mode 100644 cursive/src/backends/canvas.js diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index 08038af2..577d0616 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -73,6 +73,22 @@ optional = true version = "0.2" features = ["js"] + +[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 = ["wasm"] doc-cfg = [] @@ -80,7 +96,7 @@ builder = ["inventory", "cursive-macros/builder"] markdown = ["pulldown-cmark"] ansi = ["ansi-parser"] unstable_scroll = [] # Deprecated feature, remove in next version -wasm = ["js-sys", "getrandom"] +wasm = ["js-sys", "getrandom", "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..8226a173 100644 --- a/cursive-core/src/cursive.rs +++ b/cursive-core/src/cursive.rs @@ -878,6 +878,17 @@ impl Cursive { self.try_run_with::<(), _>(|| Ok(backend_init())).unwrap(); } + /// Initialize the backend and runs the event loop. + /// + /// Used for infallible backend initializers. + #[cfg(feature = "wasm")] + pub async fn run_with_async(&mut self, backend_init: F) + where + F: FnOnce() -> Box, + { + self.try_run_with_async::<(), _>(|| Ok(backend_init())).await.unwrap(); + } + /// Initialize the backend and runs the event loop. /// /// Returns an error if initializing the backend fails. @@ -892,6 +903,19 @@ impl Cursive { Ok(()) } + /// try run with async + #[cfg(feature = "wasm")] + pub async fn try_run_with_async(&mut self, backend_init: F) -> Result<(), E> + where + F: FnOnce() -> Result, E>, + { + let mut runner = self.runner(backend_init()?); + + runner.run_async().await; + + Ok(()) + } + /// Stops the event loop. pub fn quit(&mut self) { self.running = false; diff --git a/cursive-core/src/cursive_run.rs b/cursive-core/src/cursive_run.rs index 10137aaf..eb95fe41 100644 --- a/cursive-core/src/cursive_run.rs +++ b/cursive-core/src/cursive_run.rs @@ -1,6 +1,5 @@ use crate::{backend, event::Event, theme, Cursive, Vec2}; use std::borrow::{Borrow, BorrowMut}; -#[cfg(not(feature = "wasm"))] use std::time::Duration; #[cfg(feature = "wasm")] @@ -184,18 +183,59 @@ where } } - #[cfg(not(feature = "wasm"))] + /// post_events asynchronously + #[cfg(feature = "wasm")] + pub async fn post_events_async(&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 + // So effectively fps = 1000 / INPUT_POLL_DELAY_MS / repeats + if !boring + || self + .fps() + .map(|fps| 1000 / INPUT_POLL_DELAY_MS as u32 / fps.get()) + .map(|repeats| self.boring_frame_count >= repeats) + .unwrap_or(false) + { + // We deserve to draw something! + + if boring { + // We're only here because of a timeout. + self.on_event(Event::Refresh); + self.process_pending_backend_calls(); + } + + self.refresh(); + } + + if boring { + self.sleep_async().await; + self.boring_frame_count += 1; + } + } + fn sleep(&self) { std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); } #[cfg(feature = "wasm")] - fn sleep(&self) { - let start = Date::now(); - let mut now = start; - while (now - start) < INPUT_POLL_DELAY_MS as f64 { - now = Date::now(); - } + async fn sleep_async(&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. @@ -235,6 +275,14 @@ where received_something } + /// step asynchronously + #[cfg(feature = "wasm")] + pub async fn step_async(&mut self) -> bool { + let received_something = self.process_events(); + self.post_events_async(received_something).await; + received_something + } + /// Runs the event loop. /// /// It will wait for user input (key presses) @@ -256,4 +304,15 @@ where self.step(); } } + + /// Runs the event loop asynchronously. + #[cfg(feature = "wasm")] + pub async fn run_async(&mut self) { + self.refresh(); + + // And the big event loop begins! + while self.is_running() { + self.step_async().await; + } + } } diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 737ee9bd..1f5dd391 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -69,6 +69,10 @@ optional = true version = "0.2" features = ["js"] +[dependencies.serde-wasm-bindgen] +optional = true +version = "0.5.0" + [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. @@ -83,7 +87,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", "getrandom"] +wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm", "getrandom", "serde-wasm-bindgen"] [lib] name = "cursive" diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js new file mode 100644 index 00000000..f6a5002a --- /dev/null +++ b/cursive/src/backends/canvas.js @@ -0,0 +1,48 @@ +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 backBuffer = new Map(); + const frontBuffer = new Map(); + context.font = `${fontHeight}px monospace`; + for (let x = 0; x < 1000; x++) { + for (let y = 0; y < 1000; y++) { + const n = 1000 * 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)); + if (text != ' ') { + const buffer = frontBuffer.get(front) || []; + buffer.push({ x, y, text }); + frontBuffer.set(front, buffer); + } + const buffer = backBuffer.get(back) || []; + buffer.push({ x, y }); + backBuffer.set(back, buffer); + } + } + backBuffer.forEach((buffer, back) => { + context.fillStyle = back; + buffer.forEach(value => { + context.fillRect(value.x * fontWidth, value.y * fontHeight, fontWidth, fontHeight); + }); + }); + frontBuffer.forEach((buffer, front) => { + context.fillStyle = front; + buffer.forEach(value => { + context.fillText(value.text, value.x * fontWidth, (value.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/wasm.rs b/cursive/src/backends/wasm.rs index fb63c31b..7bbf78dc 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -8,22 +8,57 @@ use cursive_core::{ use std::collections::VecDeque; use std::rc::Rc; use std::cell::RefCell; -use web_sys::{ - HtmlCanvasElement, - CanvasRenderingContext2d, -}; +use web_sys::HtmlCanvasElement; use wasm_bindgen::prelude::*; use crate::backend; +#[wasm_bindgen] +#[derive(Debug, PartialEq)] +#[repr(C)] +struct TextColorPair { + text: char, + color: ColorPair, +} + +impl TextColorPair { + pub fn new(text: char, color: ColorPair) -> Self { + Self { + text, + color, + } + } +} + +fn text_color_pairs_to_bytes(buffer: &Vec) -> &[u8] { + unsafe { + std::slice::from_raw_parts( + buffer.as_ptr() as *const u8, + buffer.len() * std::mem::size_of::(), + ) + } +} + +impl Clone for TextColorPair { + fn clone(&self) -> Self { + Self { + text: self.text, + color: self.color.clone(), + } + } +} + + +#[wasm_bindgen(module = "/src/backends/canvas.js")] +extern "C" { + fn paint(buffer: &[u8]); +} /// Backend using wasm. pub struct Backend { canvas: HtmlCanvasElement, - ctx: CanvasRenderingContext2d, color: RefCell, - font_height: usize, - font_width: usize, events: Rc>>, + buffer: RefCell>, } impl Backend { /// Creates a new Cursive root using a wasm backend. @@ -38,10 +73,10 @@ impl Backend { std::io::ErrorKind::Other, "Failed to get document", ))?; - let canvas = document.create_element("canvas") - .map_err(|_| std::io::Error::new( + let canvas = document.get_element_by_id("cursive-wasm-canvas") + .ok_or(std::io::Error::new( std::io::ErrorKind::Other, - "Failed to create canvas", + "Failed to get window", ))? .dyn_into::() .map_err(|_| std::io::Error::new( @@ -51,28 +86,10 @@ impl Backend { canvas.set_width(1000); canvas.set_height(1000); - let font_width = 12; - let font_height = font_width * 2; - let ctx: CanvasRenderingContext2d = canvas.get_context("2d") - .map_err(|_| std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to get canvas context", - ))? - .ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to get canvas context", - ))? - .dyn_into::() - .map_err(|_| std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to cast canvas context", - ))?; - ctx.set_font(&format!("{}px monospace", font_height)); - - let color = RefCell::new(cursive_to_color_pair(theme::ColorPair { - front: theme::Color::Light(theme::BaseColor::White), - back:theme::Color::Dark(theme::BaseColor::Black), - })); + let color = cursive_to_color_pair(theme::ColorPair { + front: theme::Color::Light(theme::BaseColor::Black), + back:theme::Color::Dark(theme::BaseColor::Green), + }); let events = Rc::new(RefCell::new(VecDeque::new())); let cloned = events.clone(); @@ -88,13 +105,13 @@ impl Backend { ))?; closure.forget(); - let c = Backend { + let buffer = vec![TextColorPair::new(' ', color.clone()); 1_000_000]; + + let c = Backend { canvas, - ctx, - color, - font_height, - font_width, + color: RefCell::new(color), events, + buffer: RefCell::new(buffer), }; Ok(Box::new(c)) } @@ -109,7 +126,10 @@ impl cursive_core::backend::Backend for Backend { self.canvas.set_title(&title); } - fn refresh(self: &mut Backend) {} + fn refresh(self: &mut Backend) { + let data = self.buffer.borrow().clone(); + paint(text_color_pairs_to_bytes(&data)); + } fn has_colors(self: &Backend) -> bool { true @@ -120,15 +140,15 @@ impl cursive_core::backend::Backend for Backend { } fn print_at(self: &Backend, pos: Vec2, text: &str) { - let color = self.color.borrow(); - self.ctx.set_fill_style(&JsValue::from_str(&color.back)); - self.ctx.fill_rect((pos.x * self.font_width) as f64, (pos.y * self.font_height) as f64, (self.font_width * text.len()) as f64, self.font_height as f64); - self.ctx.set_fill_style(&JsValue::from_str(&color.front)); - self.ctx.fill_text(text, (pos.x * self.font_width) as f64, (pos.y * self.font_height + self.font_height * 3/4) as f64).unwrap(); + let color = (*self.color.borrow()).clone(); + let mut buffer = self.buffer.borrow_mut(); + for (i, c) in text.chars().enumerate() { + let x = pos.x + i; + buffer[1000 * pos.y + x] = TextColorPair::new(c, color.clone()); + } } - fn clear(self: &Backend, color: cursive_core::theme::Color) { - self.ctx.set_fill_style(&JsValue::from_str(&cursive_to_color(color))); + fn clear(self: &Backend, _color: cursive_core::theme::Color) { } fn set_color(self: &Backend, color_pair: cursive_core::theme::ColorPair) -> cursive_core::theme::ColorPair { @@ -149,10 +169,28 @@ impl cursive_core::backend::Backend for Backend { } -/// Type of hex color which starts with #. -pub type Color = String; +/// Type of hex color which is r,g,b +#[wasm_bindgen] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub 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, + } + } +} -/// Type of color pair. +/// Type of color pair. +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ColorPair { /// Foreground text color. pub front: Color, @@ -163,25 +201,25 @@ pub struct ColorPair { /// Convert cursive color to hex color. pub fn cursive_to_color(color: theme::Color) -> Color { match color { - theme::Color::Dark(theme::BaseColor::Black) => "#000000".to_string(), - theme::Color::Dark(theme::BaseColor::Red) => "#800000".to_string(), - theme::Color::Dark(theme::BaseColor::Green) => "#008000".to_string(), - theme::Color::Dark(theme::BaseColor::Yellow) => "#808000".to_string(), - theme::Color::Dark(theme::BaseColor::Blue) => "#000080".to_string(), - theme::Color::Dark(theme::BaseColor::Magenta) => "#800080".to_string(), - theme::Color::Dark(theme::BaseColor::Cyan) => "#008080".to_string(), - theme::Color::Dark(theme::BaseColor::White) => "#c0c0c0".to_string(), - theme::Color::Light(theme::BaseColor::Black) => "#808080".to_string(), - theme::Color::Light(theme::BaseColor::Red) => "#ff0000".to_string(), - theme::Color::Light(theme::BaseColor::Green) => "#00ff00".to_string(), - theme::Color::Light(theme::BaseColor::Yellow) => "#ffff00".to_string(), - theme::Color::Light(theme::BaseColor::Blue) => "#0000ff".to_string(), - theme::Color::Light(theme::BaseColor::Magenta) => "#ff00ff".to_string(), - theme::Color::Light(theme::BaseColor::Cyan) => "#00ffff".to_string(), - theme::Color::Light(theme::BaseColor::White) => "#ffffff".to_string(), - theme::Color::Rgb(r, g, b) => format!("#{:02x}{:02x}{:02x}", r, g, b).to_string(), - theme::Color::RgbLowRes(r,g ,b ) => format!("#{:01x}{:01x}{:01x}", r, g, b).to_string(), - theme::Color::TerminalDefault => "#00ff00".to_string(), + theme::Color::Dark(theme::BaseColor::Black) => Color::new(0,0,0), + theme::Color::Dark(theme::BaseColor::Red) => Color::new(128,0,0), + theme::Color::Dark(theme::BaseColor::Green) => Color::new(0,128,0), + theme::Color::Dark(theme::BaseColor::Yellow) => Color::new(128,128,0), + theme::Color::Dark(theme::BaseColor::Blue) => Color::new(0,0,128), + theme::Color::Dark(theme::BaseColor::Magenta) => Color::new(128,0,128), + theme::Color::Dark(theme::BaseColor::Cyan) => Color::new(0,128,128), + theme::Color::Dark(theme::BaseColor::White) => Color::new(182,182,182), + theme::Color::Light(theme::BaseColor::Black) => Color::new(128,128,128), + theme::Color::Light(theme::BaseColor::Red) => Color::new(255,0,0), + theme::Color::Light(theme::BaseColor::Green) => Color::new(0,0,255), + theme::Color::Light(theme::BaseColor::Yellow) => Color::new(255,255,0), + theme::Color::Light(theme::BaseColor::Blue) => Color::new(0,0,255), + theme::Color::Light(theme::BaseColor::Magenta) => Color::new(255,0,255), + theme::Color::Light(theme::BaseColor::Cyan) => Color::new(0,255,255), + theme::Color::Light(theme::BaseColor::White) => Color::new(255,255,255), + theme::Color::Rgb(r, g, b) => Color::new(r,g,b), + theme::Color::RgbLowRes(r,g ,b ) => Color::new(r,g,b), + theme::Color::TerminalDefault => Color::new(0,255,0), } } diff --git a/cursive/src/cursive_runnable.rs b/cursive/src/cursive_runnable.rs index 1dd8bd1c..c5637d5f 100644 --- a/cursive/src/cursive_runnable.rs +++ b/cursive/src/cursive_runnable.rs @@ -93,6 +93,13 @@ impl CursiveRunnable { self.siv.try_run_with(&mut self.backend_init) } + + /// try_run_ asynchronously + #[cfg(feature = "wasm-backend")] + pub async fn try_run_async(&mut self) -> Result<(), Box> { + self.siv.try_run_with_async(&mut self.backend_init).await + } + /// Gets a runner with the registered backend. /// /// Used to manually control the event loop. In most cases, running From 491bc17dddf4f89b3a465f8db6cf1c0f260a025b Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Tue, 25 Jul 2023 09:59:55 +0900 Subject: [PATCH 03/19] fix event handling (#3) --- cursive-core/src/cursive_run.rs | 3 +-- cursive/src/backends/wasm.rs | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cursive-core/src/cursive_run.rs b/cursive-core/src/cursive_run.rs index eb95fe41..e0e1a398 100644 --- a/cursive-core/src/cursive_run.rs +++ b/cursive-core/src/cursive_run.rs @@ -2,8 +2,7 @@ use crate::{backend, event::Event, theme, Cursive, Vec2}; use std::borrow::{Borrow, BorrowMut}; use std::time::Duration; -#[cfg(feature = "wasm")] -use js_sys::Date; + // How long we wait between two empty input polls const INPUT_POLL_DELAY_MS: u64 = 30; diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index 7bbf78dc..a78e30f9 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -1,7 +1,7 @@ #![cfg(feature = "wasm-backend")] use cursive_core::{ - event::Event, + event::{ Event, Key }, Vec2, theme, }; @@ -94,11 +94,21 @@ impl Backend { let events = Rc::new(RefCell::new(VecDeque::new())); let cloned = events.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { - for c in event.key().chars() { - cloned.borrow_mut().push_back(Event::Char(c)); - } + match event.key_code() { + 8 => cloned.borrow_mut().push_back(Event::Key(Key::Backspace)), + 13 => cloned.borrow_mut().push_back(Event::Key(Key::Enter)), + 37 => cloned.borrow_mut().push_back(Event::Key(Key::Left)), + 38 => cloned.borrow_mut().push_back(Event::Key(Key::Up)), + 39 => cloned.borrow_mut().push_back(Event::Key(Key::Right)), + 40 => cloned.borrow_mut().push_back(Event::Key(Key::Down)), + code => { + if let Some(c) = std::char::from_u32(code) { + cloned.borrow_mut().push_back(Event::Char(c)); + } + } + } }) as Box); - canvas.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) + 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", From 2ca849deab747f3367ffb8c753b859cee1fb04dc Mon Sep 17 00:00:00 2001 From: genieCS Date: Tue, 25 Jul 2023 16:13:16 +0900 Subject: [PATCH 04/19] remove buffering in js --- cursive/Cargo.toml | 1 + cursive/src/backends/canvas.js | 26 ++++++-------------------- cursive/src/backends/wasm.rs | 2 ++ 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 1f5dd391..f6a60e6a 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -62,6 +62,7 @@ features = [ "TextMetrics", "Window", "Document", + "console", ] [dependencies.getrandom] diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js index f6a5002a..3638a55f 100644 --- a/cursive/src/backends/canvas.js +++ b/cursive/src/backends/canvas.js @@ -3,11 +3,10 @@ const fontHeight = fontWidth * 2; const textColorPairSize = 12; export function paint(buffer) { + console.time('paint'); const data = new Uint8Array(buffer); const canvas = document.getElementById('cursive-wasm-canvas'); const context = canvas.getContext('2d'); - const backBuffer = new Map(); - const frontBuffer = new Map(); context.font = `${fontHeight}px monospace`; for (let x = 0; x < 1000; x++) { for (let y = 0; y < 1000; y++) { @@ -16,28 +15,15 @@ export function paint(buffer) { 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 != ' ') { - const buffer = frontBuffer.get(front) || []; - buffer.push({ x, y, text }); - frontBuffer.set(front, buffer); + context.fillStyle = front; + context.fillText(text, x * fontWidth, (y + 0.8) * fontHeight); } - const buffer = backBuffer.get(back) || []; - buffer.push({ x, y }); - backBuffer.set(back, buffer); } } - backBuffer.forEach((buffer, back) => { - context.fillStyle = back; - buffer.forEach(value => { - context.fillRect(value.x * fontWidth, value.y * fontHeight, fontWidth, fontHeight); - }); - }); - frontBuffer.forEach((buffer, front) => { - context.fillStyle = front; - buffer.forEach(value => { - context.fillText(value.text, value.x * fontWidth, (value.y + 0.8) * fontHeight); - }); - }); + console.timeEnd('paint'); } function byte_to_hex_string(bytes) { diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index a78e30f9..34886d44 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -137,8 +137,10 @@ impl cursive_core::backend::Backend for Backend { } fn refresh(self: &mut Backend) { + web_sys::console::time_with_label("refresh"); let data = self.buffer.borrow().clone(); paint(text_color_pairs_to_bytes(&data)); + web_sys::console::time_end_with_label("refresh"); } fn has_colors(self: &Backend) -> bool { From 327b52b17f6fa8e4fa4ecae76d05ee11cbf1a2ea Mon Sep 17 00:00:00 2001 From: genieCS Date: Wed, 26 Jul 2023 14:54:48 +0900 Subject: [PATCH 05/19] adjust font size --- cursive/src/backends/canvas.js | 6 +++--- cursive/src/backends/wasm.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js index 3638a55f..e61a1b89 100644 --- a/cursive/src/backends/canvas.js +++ b/cursive/src/backends/canvas.js @@ -3,11 +3,11 @@ const fontHeight = fontWidth * 2; const textColorPairSize = 12; export function paint(buffer) { - console.time('paint'); + // console.time('paint'); const data = new Uint8Array(buffer); const canvas = document.getElementById('cursive-wasm-canvas'); const context = canvas.getContext('2d'); - context.font = `${fontHeight}px monospace`; + context.font = `${fontHeight - 2}px monospace`; for (let x = 0; x < 1000; x++) { for (let y = 0; y < 1000; y++) { const n = 1000 * y + x; @@ -23,7 +23,7 @@ export function paint(buffer) { } } } - console.timeEnd('paint'); + // console.timeEnd('paint'); } function byte_to_hex_string(bytes) { diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index 34886d44..86e387b3 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -137,10 +137,10 @@ impl cursive_core::backend::Backend for Backend { } fn refresh(self: &mut Backend) { - web_sys::console::time_with_label("refresh"); + // web_sys::console::time_with_label("refresh"); let data = self.buffer.borrow().clone(); paint(text_color_pairs_to_bytes(&data)); - web_sys::console::time_end_with_label("refresh"); + // web_sys::console::time_end_with_label("refresh"); } fn has_colors(self: &Backend) -> bool { @@ -223,7 +223,7 @@ pub fn cursive_to_color(color: theme::Color) -> Color { theme::Color::Dark(theme::BaseColor::White) => Color::new(182,182,182), theme::Color::Light(theme::BaseColor::Black) => Color::new(128,128,128), theme::Color::Light(theme::BaseColor::Red) => Color::new(255,0,0), - theme::Color::Light(theme::BaseColor::Green) => Color::new(0,0,255), + theme::Color::Light(theme::BaseColor::Green) => Color::new(0,255, 0), theme::Color::Light(theme::BaseColor::Yellow) => Color::new(255,255,0), theme::Color::Light(theme::BaseColor::Blue) => Color::new(0,0,255), theme::Color::Light(theme::BaseColor::Magenta) => Color::new(255,0,255), From fe6b6c57e35edfe91bbbcd55dfedbc99576f40b7 Mon Sep 17 00:00:00 2001 From: genieCS Date: Wed, 26 Jul 2023 16:28:29 +0900 Subject: [PATCH 06/19] separate async feature from wasm --- cursive-core/Cargo.toml | 4 +++- cursive-core/src/cursive.rs | 21 +++++++++++++++------ cursive-core/src/cursive_run.rs | 29 ++++++++++++++++------------- cursive/Cargo.toml | 8 ++------ cursive/src/cursive_ext.rs | 13 ------------- cursive/src/cursive_runnable.rs | 16 ++++++++++++---- 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index 577d0616..e5d16132 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -34,6 +34,7 @@ lazy_static = "1" ahash = "0.8" serde_json = "1.0.85" serde_yaml = "0.9.11" +futures = "0.3.28" [dependencies.cursive-macros] path = "../cursive-macros" @@ -96,7 +97,8 @@ builder = ["inventory", "cursive-macros/builder"] markdown = ["pulldown-cmark"] ansi = ["ansi-parser"] unstable_scroll = [] # Deprecated feature, remove in next version -wasm = ["js-sys", "getrandom", "web-sys", "wasm-bindgen", "wasm-bindgen-futures"] +wasm = ["js-sys", "getrandom", "web-sys", "wasm-bindgen", "wasm-bindgen-futures", "async"] +async = [] [lib] name = "cursive_core" diff --git a/cursive-core/src/cursive.rs b/cursive-core/src/cursive.rs index 8226a173..2b696e10 100644 --- a/cursive-core/src/cursive.rs +++ b/cursive-core/src/cursive.rs @@ -840,10 +840,17 @@ impl Cursive { /// Runs a dummy event loop. /// /// Initializes a dummy backend for the event loop. + #[cfg(not(feature = "async"))] pub fn run_dummy(&mut self) { self.run_with(backend::Dummy::init) } + /// run_dummy with async feature + #[cfg(feature = "async")] + pub fn run_dummy(&mut self) { + futures::executor::block_on(self.run_with(backend::Dummy::init)) + } + /// Returns a new runner on the given backend. /// /// Used to manually control the event loop. In most cases, running @@ -871,6 +878,7 @@ impl Cursive { /// Initialize the backend and runs the event loop. /// /// Used for infallible backend initializers. + #[cfg(not(feature = "async"))] pub fn run_with(&mut self, backend_init: F) where F: FnOnce() -> Box, @@ -881,17 +889,18 @@ impl Cursive { /// Initialize the backend and runs the event loop. /// /// Used for infallible backend initializers. - #[cfg(feature = "wasm")] - pub async fn run_with_async(&mut self, backend_init: F) + #[cfg(feature = "async")] + pub async fn run_with(&mut self, backend_init: F) where F: FnOnce() -> Box, { - self.try_run_with_async::<(), _>(|| Ok(backend_init())).await.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. + #[cfg(not(feature = "async"))] pub fn try_run_with(&mut self, backend_init: F) -> Result<(), E> where F: FnOnce() -> Result, E>, @@ -904,14 +913,14 @@ impl Cursive { } /// try run with async - #[cfg(feature = "wasm")] - pub async fn try_run_with_async(&mut self, backend_init: F) -> Result<(), E> + #[cfg(feature = "async")] + 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_async().await; + runner.run().await; Ok(()) } diff --git a/cursive-core/src/cursive_run.rs b/cursive-core/src/cursive_run.rs index e0e1a398..a88b51b7 100644 --- a/cursive-core/src/cursive_run.rs +++ b/cursive-core/src/cursive_run.rs @@ -1,9 +1,8 @@ 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 const INPUT_POLL_DELAY_MS: u64 = 30; @@ -153,6 +152,7 @@ where /// [1]: CursiveRunner::run() /// [2]: CursiveRunner::step() /// [3]: CursiveRunner::process_events() + #[cfg(not(feature = "async"))] pub fn post_events(&mut self, received_something: bool) { let boring = !received_something; // How many times should we try if it's still boring? @@ -183,8 +183,8 @@ where } /// post_events asynchronously - #[cfg(feature = "wasm")] - pub async fn post_events_async(&mut self, received_something: bool) { + #[cfg(feature = "async")] + 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 @@ -208,17 +208,18 @@ where } if boring { - self.sleep_async().await; + self.sleep().await; self.boring_frame_count += 1; } } + #[cfg(not(feature = "async"))] fn sleep(&self) { std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); } - #[cfg(feature = "wasm")] - async fn sleep_async(&self) { + #[cfg(feature = "async")] + async fn sleep(&self) { use wasm_bindgen::prelude::*; let promise = js_sys::Promise::new(&mut |resolve, _| { let closure = Closure::new(move || { @@ -268,6 +269,7 @@ where /// during this step, and `false` otherwise. /// /// [`run(&mut self)`]: #method.run + #[cfg(not(feature = "async"))] pub fn step(&mut self) -> bool { let received_something = self.process_events(); self.post_events(received_something); @@ -275,10 +277,10 @@ where } /// step asynchronously - #[cfg(feature = "wasm")] - pub async fn step_async(&mut self) -> bool { + #[cfg(feature = "async")] + pub async fn step(&mut self) -> bool { let received_something = self.process_events(); - self.post_events_async(received_something).await; + self.post_events(received_something).await; received_something } @@ -295,6 +297,7 @@ where /// /// [`step(&mut self)`]: #method.step /// [`quit(&mut self)`]: #method.quit + #[cfg(not(feature = "async"))] pub fn run(&mut self) { self.refresh(); @@ -305,13 +308,13 @@ where } /// Runs the event loop asynchronously. - #[cfg(feature = "wasm")] - pub async fn run_async(&mut self) { + #[cfg(feature = "async")] + pub async fn run(&mut self) { self.refresh(); // And the big event loop begins! while self.is_running() { - self.step_async().await; + self.step().await; } } } diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index f6a60e6a..3bbd9c4b 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -70,11 +70,6 @@ optional = true version = "0.2" features = ["js"] -[dependencies.serde-wasm-bindgen] -optional = true -version = "0.5.0" - - [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. builder = ["cursive_core/builder"] @@ -88,7 +83,8 @@ 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", "getrandom", "serde-wasm-bindgen"] +wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm", "async", "getrandom"] +async = ["cursive_core/async"] [lib] name = "cursive" diff --git a/cursive/src/cursive_ext.rs b/cursive/src/cursive_ext.rs index 2830be67..516357cb 100644 --- a/cursive/src/cursive_ext.rs +++ b/cursive/src/cursive_ext.rs @@ -24,8 +24,6 @@ /// siv.run_crossterm().unwrap(); /// #[cfg(feature = "blt-backend")] /// siv.run_blt(); -/// #[cfg(feature="wasm-backend")] -/// siv.run_wasm().unwrap(); /// ``` pub trait CursiveExt { /// Tries to use one of the enabled backends. @@ -61,10 +59,6 @@ pub trait CursiveExt { #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] fn run_blt(&mut self); - - /// Creates a new Cursive root using a wasm backend. - #[cfg(feature = "wasm-backend")] - fn run_wasm(&mut self) -> std::io::Result<()>; } impl CursiveExt for cursive_core::Cursive { @@ -80,8 +74,6 @@ impl CursiveExt for cursive_core::Cursive { self.run_pancurses().unwrap() } else if #[cfg(feature = "ncurses-backend")] { self.run_ncurses().unwrap() - } else if #[cfg(feature = "wasm-backend")] { - self.run_wasm().unwrap() } else { log::warn!("No built-it backend, falling back to Cursive::dummy()."); self.run_dummy() @@ -118,9 +110,4 @@ impl CursiveExt for cursive_core::Cursive { fn run_blt(&mut self) { self.run_with(crate::backends::blt::Backend::init) } - - #[cfg(feature = "wasm-backend")] - fn run_wasm(&mut self) -> std::io::Result<()> { - self.try_run_with(crate::backends::wasm::Backend::init) - } } diff --git a/cursive/src/cursive_runnable.rs b/cursive/src/cursive_runnable.rs index c5637d5f..e7e79d4c 100644 --- a/cursive/src/cursive_runnable.rs +++ b/cursive/src/cursive_runnable.rs @@ -84,20 +84,28 @@ impl CursiveRunnable { /// # Panics /// /// If the backend initialization fails. + #[cfg(not(feature = "async"))] pub fn run(&mut self) { self.try_run().unwrap(); } + /// run asynchronously + #[cfg(feature = "async")] + pub async fn run(&mut self) { + self.try_run().await.unwrap(); + } + /// Runs the event loop with the registered backend initializer. + #[cfg(not(feature = "async"))] pub fn try_run(&mut self) -> Result<(), Box> { self.siv.try_run_with(&mut self.backend_init) } - /// try_run_ asynchronously - #[cfg(feature = "wasm-backend")] - pub async fn try_run_async(&mut self) -> Result<(), Box> { - self.siv.try_run_with_async(&mut self.backend_init).await + /// try_run asynchronously + #[cfg(feature = "async")] + 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. From 10964f180377c4d2263d2440e2eaddddf2526b48 Mon Sep 17 00:00:00 2001 From: genieCS Date: Wed, 26 Jul 2023 16:34:30 +0900 Subject: [PATCH 07/19] empty default config --- cursive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 3bbd9c4b..a1376b8a 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -74,7 +74,7 @@ features = ["js"] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. builder = ["cursive_core/builder"] blt-backend = ["bear-lib-terminal"] -default = ["wasm-backend"] +default = [] ncurses-backend = ["ncurses", "maplit"] pancurses-backend = ["pancurses", "maplit"] termion-backend = ["termion"] From bea24fb6a906a4cabc9955e1e94b113f038fca6b Mon Sep 17 00:00:00 2001 From: genieCS Date: Thu, 27 Jul 2023 15:54:00 +0900 Subject: [PATCH 08/19] fix canvas size --- cursive/src/backends/canvas.js | 6 +++--- cursive/src/backends/wasm.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js index e61a1b89..97117a9d 100644 --- a/cursive/src/backends/canvas.js +++ b/cursive/src/backends/canvas.js @@ -8,9 +8,9 @@ export function paint(buffer) { const canvas = document.getElementById('cursive-wasm-canvas'); const context = canvas.getContext('2d'); context.font = `${fontHeight - 2}px monospace`; - for (let x = 0; x < 1000; x++) { - for (let y = 0; y < 1000; y++) { - const n = 1000 * y + x; + for (let x = 0; x < 100; x++) { + for (let y = 0; y < 100; y++) { + const n = 100 * 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)); diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index 86e387b3..513fcf1b 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -115,7 +115,7 @@ impl Backend { ))?; closure.forget(); - let buffer = vec![TextColorPair::new(' ', color.clone()); 1_000_000]; + let buffer = vec![TextColorPair::new(' ', color.clone()); 10_000]; let c = Backend { canvas, @@ -148,7 +148,7 @@ impl cursive_core::backend::Backend for Backend { } fn screen_size(self: &Backend) -> Vec2 { - Vec2::new(self.canvas.width() as usize, self.canvas.height() as usize) + Vec2::new(100, 100) } fn print_at(self: &Backend, pos: Vec2, text: &str) { @@ -156,7 +156,7 @@ impl cursive_core::backend::Backend for Backend { let mut buffer = self.buffer.borrow_mut(); for (i, c) in text.chars().enumerate() { let x = pos.x + i; - buffer[1000 * pos.y + x] = TextColorPair::new(c, color.clone()); + buffer[100 * pos.y + x] = TextColorPair::new(c, color.clone()); } } From bc15a0115cec199339709e0f9c3fe2d97254f33f Mon Sep 17 00:00:00 2001 From: genieCS Date: Thu, 27 Jul 2023 16:52:01 +0900 Subject: [PATCH 09/19] fix default and remove getrandom --- cursive-core/Cargo.toml | 10 ++-------- cursive/Cargo.toml | 9 ++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index e5d16132..7fd9292c 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -69,12 +69,6 @@ version = "0.9" version = "0.3.64" optional = true -[dependencies.getrandom] -optional = true -version = "0.2" -features = ["js"] - - [dependencies.web-sys] optional = true version = "0.3.64" @@ -91,13 +85,13 @@ optional = true version = "0.4.37" [features] -default = ["wasm"] +default = [] doc-cfg = [] builder = ["inventory", "cursive-macros/builder"] markdown = ["pulldown-cmark"] ansi = ["ansi-parser"] unstable_scroll = [] # Deprecated feature, remove in next version -wasm = ["js-sys", "getrandom", "web-sys", "wasm-bindgen", "wasm-bindgen-futures", "async"] +wasm = ["js-sys", "web-sys", "wasm-bindgen", "wasm-bindgen-futures", "async"] async = [] [lib] diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index a1376b8a..073662da 100644 --- a/cursive/Cargo.toml +++ b/cursive/Cargo.toml @@ -65,16 +65,11 @@ features = [ "console", ] -[dependencies.getrandom] -optional = true -version = "0.2" -features = ["js"] - [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. builder = ["cursive_core/builder"] blt-backend = ["bear-lib-terminal"] -default = [] +default = ["ncurses-backend"] ncurses-backend = ["ncurses", "maplit"] pancurses-backend = ["pancurses", "maplit"] termion-backend = ["termion"] @@ -83,7 +78,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", "async", "getrandom"] +wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm", "async"] async = ["cursive_core/async"] [lib] From fb8f22ca5f292826e193e5c8a197c4bc83013266 Mon Sep 17 00:00:00 2001 From: genieCS Date: Thu, 27 Jul 2023 17:15:38 +0900 Subject: [PATCH 10/19] add wasm initializer with canvas --- cursive/src/backends/wasm.rs | 56 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index 513fcf1b..7b205d84 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -61,8 +61,8 @@ pub struct Backend { buffer: RefCell>, } impl Backend { - /// Creates a new Cursive root using a wasm backend. - pub fn init() -> std::io::Result> { + /// Creates a new Cursive root using a wasm backend and given HTML canvas. + pub fn new(canvas: HtmlCanvasElement) -> std::io::Result> { let document = web_sys::window() .ok_or(std::io::Error::new( std::io::ErrorKind::Other, @@ -73,18 +73,6 @@ impl Backend { std::io::ErrorKind::Other, "Failed to get document", ))?; - let canvas = 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", - ))?; - canvas.set_width(1000); - canvas.set_height(1000); let color = cursive_to_color_pair(theme::ColorPair { front: theme::Color::Light(theme::BaseColor::Black), @@ -92,8 +80,8 @@ impl Backend { }); let events = Rc::new(RefCell::new(VecDeque::new())); - let cloned = events.clone(); - let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { + let cloned = events.clone(); + let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { match event.key_code() { 8 => cloned.borrow_mut().push_back(Event::Key(Key::Backspace)), 13 => cloned.borrow_mut().push_back(Event::Key(Key::Enter)), @@ -107,13 +95,13 @@ impl Backend { } } } - }) as Box); - document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) + }) 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(); + closure.forget(); let buffer = vec![TextColorPair::new(' ', color.clone()); 10_000]; @@ -122,9 +110,37 @@ impl Backend { color: RefCell::new(color), events, buffer: RefCell::new(buffer), - }; + }; Ok(Box::new(c)) } + + /// Creates a new Cursive root using a wasm backend. + pub fn init() -> std::io::Result> { + let document = 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", + ))?; + let canvas = 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", + ))?; + canvas.set_width(1000); + canvas.set_height(1000); + + Self::new(canvas) + } } impl cursive_core::backend::Backend for Backend { From 6980b4cb270ef055836cfa59f65f02f813c672cb Mon Sep 17 00:00:00 2001 From: genieCS Date: Thu, 27 Jul 2023 17:29:20 +0900 Subject: [PATCH 11/19] fix wasm canvas size --- cursive/src/backends/canvas.js | 8 ++++---- cursive/src/backends/wasm.rs | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js index 97117a9d..92b27fcc 100644 --- a/cursive/src/backends/canvas.js +++ b/cursive/src/backends/canvas.js @@ -2,15 +2,15 @@ const fontWidth = 12; const fontHeight = fontWidth * 2; const textColorPairSize = 12; -export function paint(buffer) { +export function paint(buffer, width, height) { // console.time('paint'); const data = new Uint8Array(buffer); const canvas = document.getElementById('cursive-wasm-canvas'); const context = canvas.getContext('2d'); context.font = `${fontHeight - 2}px monospace`; - for (let x = 0; x < 100; x++) { - for (let y = 0; y < 100; y++) { - const n = 100 * y + x; + 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)); diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index 7b205d84..c506b0f0 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -56,6 +56,8 @@ extern "C" { /// Backend using wasm. pub struct Backend { canvas: HtmlCanvasElement, + width: usize, + height: usize, color: RefCell, events: Rc>>, buffer: RefCell>, @@ -104,11 +106,15 @@ impl Backend { closure.forget(); let buffer = vec![TextColorPair::new(' ', color.clone()); 10_000]; + let width = canvas.width() as usize; + let height = canvas.height() as usize; let c = Backend { canvas, + width, + height, color: RefCell::new(color), - events, + events, buffer: RefCell::new(buffer), }; Ok(Box::new(c)) @@ -164,7 +170,7 @@ impl cursive_core::backend::Backend for Backend { } fn screen_size(self: &Backend) -> Vec2 { - Vec2::new(100, 100) + Vec2::new(self.width, self.height) } fn print_at(self: &Backend, pos: Vec2, text: &str) { @@ -172,7 +178,7 @@ impl cursive_core::backend::Backend for Backend { let mut buffer = self.buffer.borrow_mut(); for (i, c) in text.chars().enumerate() { let x = pos.x + i; - buffer[100 * pos.y + x] = TextColorPair::new(c, color.clone()); + buffer[self.width * pos.y + x] = TextColorPair::new(c, color.clone()); } } From 55de09cb69557d1fe89f230f98142881d5d62286 Mon Sep 17 00:00:00 2001 From: genieCS Date: Fri, 28 Jul 2023 14:34:35 +0900 Subject: [PATCH 12/19] Add support for the different keys in the WASM backend --- cursive/src/backends/wasm.rs | 53 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index c506b0f0..7c9bd448 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -84,18 +84,9 @@ impl Backend { let events = Rc::new(RefCell::new(VecDeque::new())); let cloned = events.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { - match event.key_code() { - 8 => cloned.borrow_mut().push_back(Event::Key(Key::Backspace)), - 13 => cloned.borrow_mut().push_back(Event::Key(Key::Enter)), - 37 => cloned.borrow_mut().push_back(Event::Key(Key::Left)), - 38 => cloned.borrow_mut().push_back(Event::Key(Key::Up)), - 39 => cloned.borrow_mut().push_back(Event::Key(Key::Right)), - 40 => cloned.borrow_mut().push_back(Event::Key(Key::Down)), - code => { - if let Some(c) = std::char::from_u32(code) { - cloned.borrow_mut().push_back(Event::Char(c)); - } - } + let event = Self::to_cursive_event(event); + if (event != Event::Unknown) { + cloned.borrow_mut().push_back(event); } }) as Box); document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) @@ -147,6 +138,44 @@ impl Backend { Self::new(canvas) } + + 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 } + } + } + } } impl cursive_core::backend::Backend for Backend { From e15fe3041d53f206d31400b55e39308282b3b8a6 Mon Sep 17 00:00:00 2001 From: genieCS Date: Fri, 28 Jul 2023 14:36:27 +0900 Subject: [PATCH 13/19] implement clear for WASM backend --- cursive/src/backends/wasm.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index 7c9bd448..b0feb357 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -212,6 +212,10 @@ impl cursive_core::backend::Backend for Backend { } fn clear(self: &Backend, _color: cursive_core::theme::Color) { + let mut buffer = self.buffer.borrow_mut(); + for i in 0..self.width * self.height { + buffer[i] = TextColorPair::new(' ', _color.clone()); + } } fn set_color(self: &Backend, color_pair: cursive_core::theme::ColorPair) -> cursive_core::theme::ColorPair { From 7a5c925446aee020df9fd9d73dde99cdf3def58d Mon Sep 17 00:00:00 2001 From: genieCS Date: Fri, 28 Jul 2023 14:42:28 +0900 Subject: [PATCH 14/19] fix errors --- cursive/src/backends/wasm.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index b0feb357..e1072257 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -85,7 +85,7 @@ impl Backend { 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) { + if event != Event::Unknown(Vec::new()) { cloned.borrow_mut().push_back(event); } }) as Box); @@ -172,7 +172,7 @@ impl Backend { code => { if let Some(c) = std::char::from_u32(code) { Event::Char(c) - } else { Event::Unknown } + } else { Event::Unknown(Vec::new()) } } } } @@ -212,9 +212,14 @@ impl cursive_core::backend::Backend for Backend { } fn clear(self: &Backend, _color: cursive_core::theme::Color) { + let color = cursive_to_color(_color); + let pair = ColorPair { + front: color, + back: color, + }; let mut buffer = self.buffer.borrow_mut(); for i in 0..self.width * self.height { - buffer[i] = TextColorPair::new(' ', _color.clone()); + buffer[i] = TextColorPair::new(' ', pair.clone()); } } From 379227a05f242b18656481b24ed937956a3b4f29 Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Fri, 4 Aug 2023 13:09:11 +0900 Subject: [PATCH 15/19] fix width and height --- cursive/src/backends/canvas.js | 14 +++++++------- cursive/src/backends/wasm.rs | 31 +++++++++++++------------------ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js index 92b27fcc..f569ce93 100644 --- a/cursive/src/backends/canvas.js +++ b/cursive/src/backends/canvas.js @@ -1,16 +1,17 @@ -const fontWidth = 12; +const fontWidth = 7; const fontHeight = fontWidth * 2; const textColorPairSize = 12; -export function paint(buffer, width, height) { - // console.time('paint'); +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; + 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)); @@ -23,7 +24,6 @@ export function paint(buffer, width, height) { } } } - // console.timeEnd('paint'); } function byte_to_hex_string(bytes) { diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index e1072257..b6264cb3 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -47,6 +47,8 @@ impl Clone for TextColorPair { } } +const WIDTH: usize = 100; +const HEIGHT: usize = 100; #[wasm_bindgen(module = "/src/backends/canvas.js")] extern "C" { @@ -56,8 +58,6 @@ extern "C" { /// Backend using wasm. pub struct Backend { canvas: HtmlCanvasElement, - width: usize, - height: usize, color: RefCell, events: Rc>>, buffer: RefCell>, @@ -96,14 +96,10 @@ impl Backend { ))?; closure.forget(); - let buffer = vec![TextColorPair::new(' ', color.clone()); 10_000]; - let width = canvas.width() as usize; - let height = canvas.height() as usize; - + let buffer = vec![TextColorPair::new(' ', color.clone()); WIDTH * HEIGHT]; + let c = Backend { canvas, - width, - height, color: RefCell::new(color), events, buffer: RefCell::new(buffer), @@ -123,6 +119,7 @@ impl Backend { std::io::ErrorKind::Other, "Failed to get document", ))?; + let canvas = document.get_element_by_id("cursive-wasm-canvas") .ok_or(std::io::Error::new( std::io::ErrorKind::Other, @@ -188,10 +185,8 @@ impl cursive_core::backend::Backend for Backend { } fn refresh(self: &mut Backend) { - // web_sys::console::time_with_label("refresh"); let data = self.buffer.borrow().clone(); paint(text_color_pairs_to_bytes(&data)); - // web_sys::console::time_end_with_label("refresh"); } fn has_colors(self: &Backend) -> bool { @@ -199,7 +194,7 @@ impl cursive_core::backend::Backend for Backend { } fn screen_size(self: &Backend) -> Vec2 { - Vec2::new(self.width, self.height) + Vec2::new(WIDTH, HEIGHT) } fn print_at(self: &Backend, pos: Vec2, text: &str) { @@ -207,20 +202,20 @@ impl cursive_core::backend::Backend for Backend { let mut buffer = self.buffer.borrow_mut(); for (i, c) in text.chars().enumerate() { let x = pos.x + i; - buffer[self.width * pos.y + x] = TextColorPair::new(c, color.clone()); + buffer[WIDTH * pos.y + x] = TextColorPair::new(c, color.clone()); } } - fn clear(self: &Backend, _color: cursive_core::theme::Color) { - let color = cursive_to_color(_color); + fn clear(self: &Backend, color: cursive_core::theme::Color) { + let color = cursive_to_color(color); let pair = ColorPair { front: color, back: color, }; - let mut buffer = self.buffer.borrow_mut(); - for i in 0..self.width * self.height { - buffer[i] = TextColorPair::new(' ', pair.clone()); - } + + self.buffer + .borrow_mut() + .fill(TextColorPair::new(' ', pair.clone())); } fn set_color(self: &Backend, color_pair: cursive_core::theme::ColorPair) -> cursive_core::theme::ColorPair { From 040d6a6a86410b4cd948ee55f5a41250bca3e401 Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Fri, 4 Aug 2023 13:38:52 +0900 Subject: [PATCH 16/19] fix font size --- cursive/src/backends/canvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cursive/src/backends/canvas.js b/cursive/src/backends/canvas.js index f569ce93..2e560dc0 100644 --- a/cursive/src/backends/canvas.js +++ b/cursive/src/backends/canvas.js @@ -1,4 +1,4 @@ -const fontWidth = 7; +const fontWidth = 12; const fontHeight = fontWidth * 2; const textColorPairSize = 12; From 12d7dca3538d333e0851778c7e2f70e809be1bb3 Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Sun, 27 Aug 2023 14:53:19 +0900 Subject: [PATCH 17/19] wrap writer logic on wasm backend --- cursive/src/backends/wasm.rs | 337 ++++++++++++++++++++--------------- 1 file changed, 198 insertions(+), 139 deletions(-) diff --git a/cursive/src/backends/wasm.rs b/cursive/src/backends/wasm.rs index b6264cb3..47487fed 100644 --- a/cursive/src/backends/wasm.rs +++ b/cursive/src/backends/wasm.rs @@ -1,26 +1,20 @@ #![cfg(feature = "wasm-backend")] -use cursive_core::{ - event::{ Event, Key }, - Vec2, - theme, -}; -use std::collections::VecDeque; -use std::rc::Rc; -use std::cell::RefCell; -use web_sys::HtmlCanvasElement; use wasm_bindgen::prelude::*; use crate::backend; +const BUFFER_WIDTH: usize = 100; +const BUFFER_HEIGHT: usize = 100; + #[wasm_bindgen] #[derive(Debug, PartialEq)] #[repr(C)] -struct TextColorPair { +struct ScreenChar { text: char, color: ColorPair, } -impl TextColorPair { +impl ScreenChar { pub fn new(text: char, color: ColorPair) -> Self { Self { text, @@ -29,16 +23,7 @@ impl TextColorPair { } } -fn text_color_pairs_to_bytes(buffer: &Vec) -> &[u8] { - unsafe { - std::slice::from_raw_parts( - buffer.as_ptr() as *const u8, - buffer.len() * std::mem::size_of::(), - ) - } -} - -impl Clone for TextColorPair { +impl Clone for ScreenChar { fn clone(&self) -> Self { Self { text: self.text, @@ -47,40 +32,173 @@ impl Clone for TextColorPair { } } -const WIDTH: usize = 100; -const HEIGHT: usize = 100; +#[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, - color: RefCell, events: Rc>>, - buffer: RefCell>, + 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 = 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", - ))?; - - let color = cursive_to_color_pair(theme::ColorPair { - front: theme::Color::Light(theme::BaseColor::Black), - back:theme::Color::Dark(theme::BaseColor::Green), - }); - + 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| { @@ -95,21 +213,26 @@ impl Backend { "Failed to add event listener", ))?; closure.forget(); - - let buffer = vec![TextColorPair::new(' ', color.clone()); WIDTH * HEIGHT]; let c = Backend { canvas, - color: RefCell::new(color), events, - buffer: RefCell::new(buffer), + 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 document = web_sys::window() + 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", @@ -118,9 +241,12 @@ impl Backend { .ok_or(std::io::Error::new( std::io::ErrorKind::Other, "Failed to get document", - ))?; + )) + } - let canvas = document.get_element_by_id("cursive-wasm-canvas") + 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", @@ -129,11 +255,11 @@ impl Backend { .map_err(|_| std::io::Error::new( std::io::ErrorKind::Other, "Failed to cast canvas", - ))?; - canvas.set_width(1000); - canvas.set_height(1000); + )) + } - Self::new(canvas) + fn terminal_default_color() -> ColorPair { + terminal_default_color_pair() } fn to_cursive_event(event: web_sys::KeyboardEvent) -> Event { @@ -173,6 +299,17 @@ impl Backend { } } } + + 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 { @@ -185,8 +322,7 @@ impl cursive_core::backend::Backend for Backend { } fn refresh(self: &mut Backend) { - let data = self.buffer.borrow().clone(); - paint(text_color_pairs_to_bytes(&data)); + paint(self.text_color_pairs_to_bytes()); } fn has_colors(self: &Backend) -> bool { @@ -194,106 +330,29 @@ impl cursive_core::backend::Backend for Backend { } fn screen_size(self: &Backend) -> Vec2 { - Vec2::new(WIDTH, HEIGHT) + Vec2::new(BUFFER_WIDTH, BUFFER_HEIGHT) } fn print_at(self: &Backend, pos: Vec2, text: &str) { - let color = (*self.color.borrow()).clone(); - let mut buffer = self.buffer.borrow_mut(); - for (i, c) in text.chars().enumerate() { - let x = pos.x + i; - buffer[WIDTH * pos.y + x] = TextColorPair::new(c, color.clone()); - } + self.writer.borrow_mut().write(pos, text); } - fn clear(self: &Backend, color: cursive_core::theme::Color) { - let color = cursive_to_color(color); - let pair = ColorPair { - front: color, - back: color, - }; - - self.buffer - .borrow_mut() - .fill(TextColorPair::new(' ', pair.clone())); + fn clear(self: &Backend, color: CColor) { + self.writer.borrow_mut().clear(cursive_to_color(color)) } - fn set_color(self: &Backend, color_pair: cursive_core::theme::ColorPair) -> cursive_core::theme::ColorPair { - let mut color = self.color.borrow_mut(); - *color = cursive_to_color_pair(color_pair); + 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, _: cursive_core::theme::Effect) { + fn set_effect(self: &Backend, _: Effect) { } - fn unset_effect(self: &Backend, _: cursive_core::theme::Effect) { + fn unset_effect(self: &Backend, _: Effect) { } fn name(&self) -> &str { "cursive-wasm-backend" } } - - -/// Type of hex color which is r,g,b -#[wasm_bindgen] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub 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, - } - } -} - -/// Type of color pair. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ColorPair { - /// Foreground text color. - pub front: Color, - /// Background color. - pub back: Color, -} - -/// Convert cursive color to hex color. -pub fn cursive_to_color(color: theme::Color) -> Color { - match color { - theme::Color::Dark(theme::BaseColor::Black) => Color::new(0,0,0), - theme::Color::Dark(theme::BaseColor::Red) => Color::new(128,0,0), - theme::Color::Dark(theme::BaseColor::Green) => Color::new(0,128,0), - theme::Color::Dark(theme::BaseColor::Yellow) => Color::new(128,128,0), - theme::Color::Dark(theme::BaseColor::Blue) => Color::new(0,0,128), - theme::Color::Dark(theme::BaseColor::Magenta) => Color::new(128,0,128), - theme::Color::Dark(theme::BaseColor::Cyan) => Color::new(0,128,128), - theme::Color::Dark(theme::BaseColor::White) => Color::new(182,182,182), - theme::Color::Light(theme::BaseColor::Black) => Color::new(128,128,128), - theme::Color::Light(theme::BaseColor::Red) => Color::new(255,0,0), - theme::Color::Light(theme::BaseColor::Green) => Color::new(0,255, 0), - theme::Color::Light(theme::BaseColor::Yellow) => Color::new(255,255,0), - theme::Color::Light(theme::BaseColor::Blue) => Color::new(0,0,255), - theme::Color::Light(theme::BaseColor::Magenta) => Color::new(255,0,255), - theme::Color::Light(theme::BaseColor::Cyan) => Color::new(0,255,255), - theme::Color::Light(theme::BaseColor::White) => Color::new(255,255,255), - theme::Color::Rgb(r, g, b) => Color::new(r,g,b), - theme::Color::RgbLowRes(r,g ,b ) => Color::new(r,g,b), - theme::Color::TerminalDefault => Color::new(0,255,0), - } -} - -/// Convert cursive color pair to hex color pair. -pub fn cursive_to_color_pair(c: theme::ColorPair) -> ColorPair { - ColorPair { - front: cursive_to_color(c.front), - back: cursive_to_color(c.back), - } -} From c23b043bd589a7426dcdbe23db5a56a7cec5ead4 Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Sat, 2 Sep 2023 15:17:23 +0900 Subject: [PATCH 18/19] make run as async --- cursive-core/Cargo.toml | 4 +- cursive-core/src/cursive.rs | 37 +----------------- cursive-core/src/cursive_run.rs | 59 ++--------------------------- cursive/Cargo.toml | 4 +- cursive/src/cursive_ext.rs | 66 +++++++++++++++++++++------------ cursive/src/cursive_runnable.rs | 15 -------- 6 files changed, 51 insertions(+), 134 deletions(-) diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index 7fd9292c..5ababd8d 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -34,7 +34,6 @@ lazy_static = "1" ahash = "0.8" serde_json = "1.0.85" serde_yaml = "0.9.11" -futures = "0.3.28" [dependencies.cursive-macros] path = "../cursive-macros" @@ -91,8 +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", "async"] -async = [] +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 2b696e10..e45f4494 100644 --- a/cursive-core/src/cursive.rs +++ b/cursive-core/src/cursive.rs @@ -840,15 +840,8 @@ impl Cursive { /// Runs a dummy event loop. /// /// Initializes a dummy backend for the event loop. - #[cfg(not(feature = "async"))] - pub fn run_dummy(&mut self) { - self.run_with(backend::Dummy::init) - } - - /// run_dummy with async feature - #[cfg(feature = "async")] - pub fn run_dummy(&mut self) { - futures::executor::block_on(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. @@ -878,18 +871,6 @@ impl Cursive { /// Initialize the backend and runs the event loop. /// /// Used for infallible backend initializers. - #[cfg(not(feature = "async"))] - pub fn run_with(&mut self, backend_init: F) - where - F: FnOnce() -> Box, - { - self.try_run_with::<(), _>(|| Ok(backend_init())).unwrap(); - } - - /// Initialize the backend and runs the event loop. - /// - /// Used for infallible backend initializers. - #[cfg(feature = "async")] pub async fn run_with(&mut self, backend_init: F) where F: FnOnce() -> Box, @@ -900,20 +881,6 @@ impl Cursive { /// Initialize the backend and runs the event loop. /// /// Returns an error if initializing the backend fails. - #[cfg(not(feature = "async"))] - pub 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(); - - Ok(()) - } - - /// try run with async - #[cfg(feature = "async")] pub async fn try_run_with(&mut self, backend_init: F) -> Result<(), E> where F: FnOnce() -> Result, E>, diff --git a/cursive-core/src/cursive_run.rs b/cursive-core/src/cursive_run.rs index a88b51b7..c0f92752 100644 --- a/cursive-core/src/cursive_run.rs +++ b/cursive-core/src/cursive_run.rs @@ -152,38 +152,6 @@ where /// [1]: CursiveRunner::run() /// [2]: CursiveRunner::step() /// [3]: CursiveRunner::process_events() - #[cfg(not(feature = "async"))] - pub 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 - // So effectively fps = 1000 / INPUT_POLL_DELAY_MS / repeats - if !boring - || self - .fps() - .map(|fps| 1000 / INPUT_POLL_DELAY_MS as u32 / fps.get()) - .map(|repeats| self.boring_frame_count >= repeats) - .unwrap_or(false) - { - // We deserve to draw something! - - if boring { - // We're only here because of a timeout. - self.on_event(Event::Refresh); - self.process_pending_backend_calls(); - } - - self.refresh(); - } - - if boring { - self.sleep(); - self.boring_frame_count += 1; - } - } - - /// post_events asynchronously - #[cfg(feature = "async")] 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? @@ -213,12 +181,12 @@ where } } - #[cfg(not(feature = "async"))] - fn sleep(&self) { + #[cfg(not(feature = "wasm"))] + async fn sleep(&self) { std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS)); } - #[cfg(feature = "async")] + #[cfg(feature = "wasm")] async fn sleep(&self) { use wasm_bindgen::prelude::*; let promise = js_sys::Promise::new(&mut |resolve, _| { @@ -269,15 +237,6 @@ where /// during this step, and `false` otherwise. /// /// [`run(&mut self)`]: #method.run - #[cfg(not(feature = "async"))] - pub fn step(&mut self) -> bool { - let received_something = self.process_events(); - self.post_events(received_something); - received_something - } - - /// step asynchronously - #[cfg(feature = "async")] pub async fn step(&mut self) -> bool { let received_something = self.process_events(); self.post_events(received_something).await; @@ -297,18 +256,6 @@ where /// /// [`step(&mut self)`]: #method.step /// [`quit(&mut self)`]: #method.quit - #[cfg(not(feature = "async"))] - pub fn run(&mut self) { - self.refresh(); - - // And the big event loop begins! - while self.is_running() { - self.step(); - } - } - - /// Runs the event loop asynchronously. - #[cfg(feature = "async")] pub async fn run(&mut self) { self.refresh(); diff --git a/cursive/Cargo.toml b/cursive/Cargo.toml index 073662da..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 @@ -78,8 +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", "async"] -async = ["cursive_core/async"] +wasm-backend = ["wasm-bindgen", "web-sys", "cursive_core/wasm"] [lib] name = "cursive" diff --git a/cursive/src/cursive_ext.rs b/cursive/src/cursive_ext.rs index 516357cb..9f586dc3 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. @@ -24,7 +27,10 @@ /// siv.run_crossterm().unwrap(); /// #[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 e7e79d4c..b102d92b 100644 --- a/cursive/src/cursive_runnable.rs +++ b/cursive/src/cursive_runnable.rs @@ -84,26 +84,11 @@ impl CursiveRunnable { /// # Panics /// /// If the backend initialization fails. - #[cfg(not(feature = "async"))] - pub fn run(&mut self) { - self.try_run().unwrap(); - } - - /// run asynchronously - #[cfg(feature = "async")] pub async fn run(&mut self) { self.try_run().await.unwrap(); } /// Runs the event loop with the registered backend initializer. - #[cfg(not(feature = "async"))] - pub fn try_run(&mut self) -> Result<(), Box> { - self.siv.try_run_with(&mut self.backend_init) - } - - - /// try_run asynchronously - #[cfg(feature = "async")] pub async fn try_run(&mut self) -> Result<(), Box> { self.siv.try_run_with(&mut self.backend_init).await } From 1747b21de7e87c7510d65d76e1308f23f055b9c9 Mon Sep 17 00:00:00 2001 From: hyejin lee Date: Sat, 2 Sep 2023 15:27:48 +0900 Subject: [PATCH 19/19] fix doc-tests --- cursive/src/cursive_ext.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cursive/src/cursive_ext.rs b/cursive/src/cursive_ext.rs index 9f586dc3..badfc9e7 100644 --- a/cursive/src/cursive_ext.rs +++ b/cursive/src/cursive_ext.rs @@ -18,13 +18,13 @@ use async_trait::async_trait; /// /// // 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")]