diff --git a/examples/tour.rs b/examples/tour.rs index bae1f6f..8a85a9f 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -64,7 +64,7 @@ pub fn app(screen: Arc) -> Rsx { FlexRow { "Username" - @Transform::new().padding(1, 1).dimensions(40, 1); + @Transform::new().padding(1, 1).dimensions_const(40, 1); @Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xff0000) }; @Focused; Input { } @@ -73,7 +73,7 @@ pub fn app(screen: Arc) -> Rsx { FlexRow { "Password" - @Transform::new().padding(1, 1).dimensions(40, 1); + @Transform::new().padding(1, 1).dimensions_const(40, 1); @Style { foreground: Some(0xffffff), background: Background::RoundedOutline(0xffff00) }; Input { } } diff --git a/examples/velocity.rs b/examples/velocity.rs new file mode 100644 index 0000000..3d6043a --- /dev/null +++ b/examples/velocity.rs @@ -0,0 +1,52 @@ +use std::sync::Arc; + +use osui::prelude::*; + +fn main() -> std::io::Result<()> { + let screen = Screen::new(); + screen.extension(InputExtension); + + app(screen.clone()).draw(&screen); + + screen.run() +} + +pub fn app(screen: Arc) -> Rsx { + rsx! { + @Handler::new({ + let screen = screen.clone(); + move |_, e: &crossterm::event::Event| { + if let crossterm::event::Event::Key(e) = e { + if e.code == crossterm::event::KeyCode::Esc { + screen.close(); + } + } + } + }); + @Style { foreground: None, background: Background::Outline(0xff0000) }; + VelocityHandler { + @Velocity(700, 0); + @Handler::new({ + move |w, e: &crossterm::event::Event| { + if let crossterm::event::Event::Key(e) = e { + if e.code == crossterm::event::KeyCode::Char(' ') { + if let Some(Velocity(v, _)) = w.get() { + w.set_component(Velocity(-v, 0)); + } else { + w.component(Velocity(700, 0)); + } + } + } + } + }); + "(•_•)" + } + + @Transform::new().dimensions(Dimension::Full, Dimension::Full); + VelocityHandler { + @Velocity(0, -150); + @Transform::center().bottom(); + "^" + } + } +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index f408d11..4a580a1 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -4,6 +4,7 @@ pub mod heading; pub mod input; pub mod paginator; pub mod scroll; +pub mod velocity_handler; pub use div::*; pub use flex::*; @@ -11,6 +12,7 @@ pub use heading::*; pub use input::*; pub use paginator::*; pub use scroll::*; +pub use velocity_handler::*; use crate::Element; diff --git a/src/elements/velocity_handler.rs b/src/elements/velocity_handler.rs new file mode 100644 index 0000000..e70d953 --- /dev/null +++ b/src/elements/velocity_handler.rs @@ -0,0 +1,164 @@ +use std::sync::Arc; + +use crate::{ + component, + prelude::ElementRenderer, + style::RawTransform, + widget::{Element, Widget}, + NoRenderRoot, +}; + +pub struct VelocityRenderer<'a>(&'a mut RawTransform, &'a mut Vec<(i32, i32)>, usize, u16); + +pub struct VelocityHandler { + children: Vec>, + progress: Vec<(i32, i32)>, + render: (u16, u16), + tick: u16, +} + +impl VelocityHandler { + pub fn new() -> Self { + Self { + children: Vec::new(), + progress: Vec::new(), + render: (0, 0), + tick: 0, + } + } +} + +impl Element for VelocityHandler { + fn render( + &mut self, + scope: &mut crate::prelude::RenderScope, + _: &crate::render_scope::RenderContext, + ) { + scope.use_area(self.render.0, self.render.1); + } + + fn after_render( + &mut self, + parent_scope: &mut crate::render_scope::RenderScope, + render_context: &crate::render_scope::RenderContext, + ) { + let mut transform = { + let (width, height) = parent_scope.get_size_or_parent(); + let mut t = parent_scope.get_transform().clone(); + t.width = width; + t.height = height; + t.transform_parent(parent_scope.get_parent_transform()); + t + }; + + let mut scope = crate::render_scope::RenderScope::new(); + scope.set_parent_transform(transform.clone()); + + transform.width = 0; + transform.height = 0; + let mut renderer = VelocityRenderer(&mut transform, &mut self.progress, 0, self.tick); + for widget in &self.children { + scope.render_widget(&mut renderer, render_context.get_context(), widget); + } + + self.render = (transform.width, transform.height); + + if self.tick == 1000 { + self.tick = 0; + } else { + self.tick += 1; + } + } + + fn draw_child(&mut self, element: &Arc) { + self.progress.push((0, 0)); + self.children.push(element.clone()); + element.inject(|w| w.component(NoRenderRoot)); + } + + fn undraw_child(&mut self, element: &Arc) { + if let Some(i) = self.children.iter().position(|v| Arc::ptr_eq(v, element)) { + self.children.remove(i); + self.progress.remove(i); + } + } + + fn is_ghost(&mut self) -> bool { + true + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + +impl ElementRenderer for VelocityRenderer<'_> { + fn before_draw(&mut self, scope: &mut crate::prelude::RenderScope, widget: &Arc) { + let t = scope.get_transform_mut(); + + if let Some(Velocity(vx, vy)) = widget.get() { + if let Some((ax, ay)) = self.1.get_mut(self.2) { + add_i32_to_u16(&mut t.x, *ax); + add_i32_to_u16(&mut t.y, *ay); + + if apply_velocity(self.3, vx, &mut t.x) { + apply_value(ax, vx); + } + if apply_velocity(self.3, vy, &mut t.y) { + apply_value(ay, vy); + } + } else { + self.1.push((0, 0)); + } + } + + self.2 += 1; + + self.0.width = self.0.width.max(t.x + t.width + (t.px * 2)); + self.0.height = self.0.height.max(t.y + t.height + (t.py * 2)); + + t.x += self.0.x + self.0.px; + t.y += self.0.y + self.0.py; + t.offset_y = self.0.offset_y; + } +} + +fn apply_velocity(ticks: u16, velocity: i32, x: &mut u16) -> bool { + let abs_v = velocity.abs(); + if abs_v == 0 { + return false; + } + + let interval = (1000 / abs_v).max(1); // prevent divide-by-zero + if ticks as i32 % interval == 0 { + if velocity > 0 { + *x = x.saturating_add(1); + } else { + *x = x.saturating_sub(1); + } + return true; + } + false +} + +fn add_i32_to_u16(value: &mut u16, delta: i32) { + if delta >= 0 { + *value = value.saturating_add(delta as u16); + } else { + *value = value.saturating_sub((-delta) as u16); + } +} + +fn apply_value(x: &mut i32, velocity: i32) { + *x = if velocity > 0 { + x.saturating_add(1) + } else { + x.saturating_sub(1) + }; +} + +component!(Velocity(pub i32, pub i32)); diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index 2e375cf..69a4f3f 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -2,13 +2,11 @@ pub mod focus; pub mod id; pub mod input_handling; pub mod tick; -pub mod velocity; pub use focus::*; pub use id::*; pub use input_handling::*; pub use tick::*; -pub use velocity::*; use std::{ any::{type_name, Any}, diff --git a/src/extensions/velocity.rs b/src/extensions/velocity.rs deleted file mode 100644 index 13fae15..0000000 --- a/src/extensions/velocity.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::sync::Arc; - -use crate::{ - component, - extensions::Extension, - style::{Position, Transform}, - widget::Widget, -}; - -pub struct VelocityExtension; - -component!(Velocity(pub i32, pub i32)); - -impl VelocityExtension { - fn apply_velocity(ticks: u16, velocity: i32, x: &mut u16) { - if velocity.abs() != 0 && ticks as i32 % (1000 / velocity.abs()) == 0 { - if velocity > 0 { - *x += 1; - } else if velocity < 0 { - *x -= 1; - } - } - } - - fn apply_velocity_xy(ticks: u16, widget: &Arc) { - if let Some(velocity) = widget.get::() { - if let Some(mut t) = widget.get::() { - match &mut t.x { - Position::Const(x) => Self::apply_velocity(ticks, velocity.0, x), - _ => {} - } - - match &mut t.y { - Position::Const(y) => Self::apply_velocity(ticks, velocity.1, y), - _ => {} - } - - widget.set_component(t); - } - } - } -} - -impl Extension for VelocityExtension { - fn init(&mut self, ctx: &super::Context) { - let ctx = ctx.clone(); - std::thread::spawn({ - move || { - let mut tick = 0; - loop { - for widget in ctx.get_widgets().iter() { - Self::apply_velocity_xy(tick, widget); - } - if tick > 1000 { - tick = 0; - } else { - tick += 1; - } - std::thread::sleep(std::time::Duration::from_millis(1)); - } - } - }); - } -} diff --git a/src/render_scope.rs b/src/render_scope.rs index 02429bc..9bade78 100644 --- a/src/render_scope.rs +++ b/src/render_scope.rs @@ -84,23 +84,10 @@ impl RenderScope { } } - /// Directly sets the raw transform (position and size) for this scope. - pub fn set_transform_raw(&mut self, transform: RawTransform) { - self.transform = transform; - } - /// Applies a `Transform` to this scope, factoring in parent dimensions. - pub fn set_transform(&mut self, transform: &Transform) { - transform.use_dimensions( - self.parent_transform.width, - self.parent_transform.height, - &mut self.transform, - ); - transform.use_position( - self.parent_transform.width, - self.parent_transform.height, - &mut self.transform, - ); + pub fn apply_transform(&mut self, transform: &Transform) { + transform.use_dimensions(&self.parent_transform, &mut self.transform); + transform.use_position(&self.parent_transform, &mut self.transform); self.transform.px = transform.px; self.transform.py = transform.py; } @@ -114,7 +101,7 @@ impl RenderScope { self.transform.height = self.transform.height.max(h); } - /// Adds a text draw instruction. + /// Adds a text draw instruction (inverted colors). pub fn draw_text_inverted(&mut self, x: u16, y: u16, text: &str) { self.render_stack .push(RenderMethod::TextInverted(x, y, text.to_string())); @@ -161,16 +148,15 @@ impl RenderScope { let height = height + self.transform.py * 2; if width > 1 && height > 1 { let d = "─".repeat(width as usize - 2); - utils::print_liner( - self.transform.x, - self.transform.y, + utils::print_liner_nopad( + &self.transform, + &self.parent_transform, &utils::hex_ansi(c), &format!( "┌{d}┐{}\n└{d}┘", format!("\n│{}│", " ".repeat(width as usize - 2)) .repeat(height as usize - 2), ), - &self.parent_transform, ); } } @@ -179,28 +165,26 @@ impl RenderScope { let height = height + self.transform.py * 2; if width > 1 && height > 1 { let d = "─".repeat(width as usize - 2); - utils::print_liner( - self.transform.x, - self.transform.y, + utils::print_liner_nopad( + &self.transform, + &self.parent_transform, &utils::hex_ansi(c), &format!( "╭{d}╮{}\n╰{d}╯", format!("\n│{}│", " ".repeat(width as usize - 2)) .repeat(height as usize - 2), ), - &self.parent_transform, ); } } - crate::style::Background::Solid(c) => utils::print_liner( - self.transform.x, - self.transform.y, + crate::style::Background::Solid(c) => utils::print_liner_nopad( + &self.transform, + &self.parent_transform, &hex_ansi_bg(c), &std::iter::repeat(" ".repeat(width as usize)) .take(height as usize) .collect::>() .join("\n"), - &self.parent_transform, ), } @@ -209,55 +193,49 @@ impl RenderScope { RenderMethod::Text(x, y, t) => { if let Some(c) = self.style.foreground { utils::print_liner( - self.transform.x + self.transform.px + x, - self.transform.y + self.transform.py + y, + *x, + *y, + &self.transform, + &self.parent_transform, &utils::hex_ansi(c), t, - &self.parent_transform, ); } else { - utils::print( - self.transform.x + self.transform.px, - self.transform.y + self.transform.py, - t, - &self.parent_transform, - ); + utils::print(*x, *y, &self.transform, &self.parent_transform, t); } } RenderMethod::TextInverted(x, y, t) => { if let Some(c) = self.style.foreground { utils::print_liner( - self.transform.x + self.transform.px + x, - self.transform.y + self.transform.py + y, + *x, + *y, + &self.transform, + &self.parent_transform, &utils::hex_ansi_bg(c), t, - &self.parent_transform, ); } else { - utils::print( - self.transform.x + self.transform.px, - self.transform.y + self.transform.py, - t, - &self.parent_transform, - ); + utils::print(*x, *y, &self.transform, &self.parent_transform, t); } } RenderMethod::TextColored(x, y, t, c) => utils::print_liner( - self.transform.x + self.transform.px + x, - self.transform.y + self.transform.py + y, + *x, + *y, + &self.transform, + &self.parent_transform, &utils::hex_ansi(*c), t, - &self.parent_transform, ), - RenderMethod::Rectangle(x, y, width, height, color) => utils::print_liner( - self.transform.x + self.transform.px + x, - self.transform.y + self.transform.py + y, - &hex_ansi_bg(*color), + RenderMethod::Rectangle(x, y, width, height, c) => utils::print_liner( + *x, + *y, + &self.transform, + &self.parent_transform, + &utils::hex_ansi_bg(*c), &std::iter::repeat(" ".repeat(*width as usize)) .take(*height as usize) .collect::>() .join("\n"), - &self.parent_transform, ), } } @@ -306,22 +284,27 @@ impl RenderScope { }, ) } + /// Returns parent size. + pub fn get_parent_size(&self) -> (u16, u16) { + (self.parent_transform.width, self.parent_transform.height) + } /// Gets the dimensions of the parent container. pub fn get_parent_transform(&self) -> &RawTransform { &self.parent_transform } - /// Sets the dimensions of the parent container. - // pub fn set_parent_size(&mut self, width: u16, height: u16) { - // self.parent_transform.width = width; - // self.parent_transform.height = height; - // } - + /// Sets the transform of the parent container. pub fn set_parent_transform(&mut self, transform: RawTransform) { self.parent_transform = transform; } + /// Sets the transform of the parent container. + pub fn set_parent_size(&mut self, width: u16, height: u16) { + self.parent_transform.width = width; + self.parent_transform.height = height; + } + /// Returns a mutable reference to the internal raw transform. pub fn get_transform_mut(&mut self) -> &mut RawTransform { &mut self.transform @@ -364,14 +347,14 @@ impl RenderScope { self.set_style(style); } if let Some(t) = widget.get() { - self.set_transform(&t); + self.apply_transform(&t); } widget.get_elem().render(self, &render_context); ctx.render(widget, self); if let Some(t) = widget.get() { - self.set_transform(&t); + self.apply_transform(&t); } renderer.before_draw(self, widget); diff --git a/src/style.rs b/src/style.rs index a7abc1d..d1e1015 100644 --- a/src/style.rs +++ b/src/style.rs @@ -139,24 +139,33 @@ impl Transform { } /// Sets constant dimensions. - pub fn dimensions(mut self, width: u16, height: u16) -> Self { + pub fn dimensions_const(mut self, width: u16, height: u16) -> Self { self.width = Dimension::Const(width); self.height = Dimension::Const(height); self } + /// Sets constant dimensions. + pub fn dimensions(mut self, width: Dimension, height: Dimension) -> Self { + self.width = width; + self.height = height; + self + } + /// Resolves `Dimension` rules into absolute values for the given parent size. - pub fn use_dimensions(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform) { - self.width.use_dimension(parent_width, &mut raw.width); - self.height.use_dimension(parent_height, &mut raw.height); + pub fn use_dimensions(&self, parent_transform: &RawTransform, raw: &mut RawTransform) { + self.width + .use_dimension(parent_transform.width, &mut raw.width); + self.height + .use_dimension(parent_transform.height, &mut raw.height); } /// Resolves `Position` rules into absolute positions for the given parent size. - pub fn use_position(&self, parent_width: u16, parent_height: u16, raw: &mut RawTransform) { + pub fn use_position(&self, parent_transform: &RawTransform, raw: &mut RawTransform) { self.x - .use_position(raw.width, parent_width, self.mx, &mut raw.x); + .use_position(raw.width, parent_transform.width, self.mx, &mut raw.x); self.y - .use_position(raw.height, parent_height, self.my, &mut raw.y); + .use_position(raw.height, parent_transform.height, self.my, &mut raw.y); } } diff --git a/src/utils.rs b/src/utils.rs index 59ebde8..ae93413 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -109,7 +109,16 @@ pub fn hex_ansi_bg(hex: u32) -> String { format!("\x1b[48;2;{r};{g};{b}m") } -pub(crate) fn print(x: u16, y: u16, text: &str, parent_transform: &RawTransform) { +pub(crate) fn print( + x: u16, + y: u16, + transform: &RawTransform, + parent_transform: &RawTransform, + text: &str, +) { + let x = transform.x + transform.px + x; + let y = transform.y + transform.py + y; + for (i, line) in text.lines().enumerate() { if parent_transform.offset_y > y + i as u16 { continue; @@ -128,10 +137,14 @@ pub(crate) fn print(x: u16, y: u16, text: &str, parent_transform: &RawTransform) pub(crate) fn print_liner( x: u16, y: u16, + transform: &RawTransform, + parent_transform: &RawTransform, liner: &str, text: &str, - parent_transform: &RawTransform, ) { + let x = transform.x + transform.px + x; + let y = transform.y + transform.py + y; + for (i, line) in text.lines().enumerate() { if parent_transform.offset_y > y + i as u16 { continue; @@ -146,3 +159,24 @@ pub(crate) fn print_liner( flush().unwrap(); } } + +pub(crate) fn print_liner_nopad( + transform: &RawTransform, + parent_transform: &RawTransform, + liner: &str, + text: &str, +) { + for (i, line) in text.lines().enumerate() { + if parent_transform.offset_y > transform.y + i as u16 { + continue; + } + + let y = transform.y + i as u16 - parent_transform.offset_y; + + if y >= parent_transform.height + parent_transform.y + parent_transform.py { + break; + } + print!("\x1b[{};{}H{liner}{line}\x1b[0m", y + 1, transform.x + 1); + flush().unwrap(); + } +}