diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 39a450e..6d8ca31 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -4,6 +4,14 @@ pub struct Rect; impl Element for Rect { fn render(&mut self, scope: &mut crate::render::RenderScope) { - scope.draw_rect(); + scope.draw_rect(300, 300, 0xffffff); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self } } diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs new file mode 100644 index 0000000..4935328 --- /dev/null +++ b/src/extensions/mod.rs @@ -0,0 +1,11 @@ +pub mod tick; +pub mod velocity; + +use std::sync::Arc; + +use crate::widget::Widget; + +pub trait Extension { + fn init(&self, _widgets: &Vec>) {} + fn render(&self, _widget: &Arc) {} +} diff --git a/src/extensions/tick.rs b/src/extensions/tick.rs new file mode 100644 index 0000000..eed4495 --- /dev/null +++ b/src/extensions/tick.rs @@ -0,0 +1,24 @@ +use std::sync::Arc; + +use crate::{component, extensions::Extension, widget::Widget}; + +pub struct TickExtension(pub usize); + +impl Extension for TickExtension { + fn init(&self, widgets: &Vec>) { + let rate_dur = 1000 / self.0 as u64; + std::thread::spawn({ + let widgets = widgets.clone(); + move || loop { + for w in &widgets { + if let Some(on_tick) = w.get::() { + (on_tick.0)(&w) + } + } + std::thread::sleep(std::time::Duration::from_millis(rate_dur)); + } + }); + } +} + +component!(OnTick(pub fn(&Arc))); diff --git a/src/extensions/velocity.rs b/src/extensions/velocity.rs new file mode 100644 index 0000000..8df0868 --- /dev/null +++ b/src/extensions/velocity.rs @@ -0,0 +1,66 @@ +use std::sync::Arc; + +use crate::{ + component, + extensions::Extension, + style::{Position, Transform}, + widget::Widget, +}; + +pub struct VelocityExtension; + +impl VelocityExtension { + fn apply_velocity(ticks: usize, velocity: i32, x: &mut usize) { + if velocity.abs() != 0 && ticks as i32 % (1000 / velocity.abs()) == 0 { + if velocity > 0 { + *x += 1; + } else if velocity < 0 { + *x -= 1; + } + } + } +} + +impl VelocityExtension { + fn apply_velocity_xy(ticks: usize, 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(&self, widgets: &Vec>) { + std::thread::spawn({ + let widgets = widgets.clone(); + move || { + let mut tick = 0; + loop { + for widget in &widgets { + Self::apply_velocity_xy(tick, widget); + } + if tick > 1000 { + tick = 0; + } else { + tick += 1; + } + std::thread::sleep(std::time::Duration::from_millis(1)); + } + } + }); + } +} + +component!(Velocity(pub i32, pub i32)); diff --git a/src/lib.rs b/src/lib.rs index 6a1b612..29fe51f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,46 +1,80 @@ -pub mod elements; -pub mod render; -pub mod widget; - -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use minifb::Window; -use crate::{render::RenderScope, widget::Element}; +use crate::{ + extensions::Extension, + render::RenderScope, + style::Transform, + widget::{Element, Widget}, +}; -pub struct App { +pub mod elements; +pub mod extensions; +pub mod macros; +pub mod render; +pub mod style; +pub mod widget; + +pub struct Screen { window: Window, - elements: Vec>>>, + pub widgets: Vec>, + extensions: Vec>>, } -impl App { - pub fn new(win: Window) -> Self { - Self { +impl Screen { + pub fn new(win: Window) -> Screen { + Screen { window: win, - elements: Vec::new(), + widgets: Vec::new(), + extensions: Vec::new(), } } - pub fn element(&mut self, e: E) { - self.elements.push(Arc::new(Mutex::new(Box::new(e)))); + pub fn draw(&mut self, element: E) -> &Arc { + self.widgets.push(Arc::new(Widget::new(Box::new(element)))); + self.widgets.last().unwrap() } - pub fn run(&mut self) { + pub fn extension(&mut self, ext: E) { + self.extensions.push(Arc::new(Box::new(ext))); + } + + pub fn run(&mut self) -> std::io::Result<()> { + for elem in &mut self.widgets { + elem.component(Transform::new()); + } + + for ext in &self.extensions { + ext.init(&self.widgets); + } + while self.window.is_open() { self.render(); + std::thread::sleep(std::time::Duration::from_millis(28)); } + + Ok(()) } fn render(&mut self) { let (w, h) = self.window.get_size(); let mut scope = RenderScope::new(w, h); - for e in &self.elements { - e.lock().unwrap().render(&mut scope); + for e in &self.widgets { + e.0.lock().unwrap().render(&mut scope); + scope.draw(); } self.window .update_with_buffer(&scope.get_buffer(), w, h) .unwrap(); } + + pub fn render_extension(&self, wi: Arc) -> std::io::Result<()> { + for ext in &self.extensions { + ext.render(&wi); + } + Ok(()) + } } diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..f415f18 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,129 @@ +#[macro_export] +macro_rules! event { + ($name:ident) => { + #[derive(Debug, Clone)] + pub struct $name; + + impl $crate::events::Event for $name { + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + }; + + ($name:ident {$($inner:tt)*}) => { + #[derive(Debug, Clone)] + pub struct $name { + $($inner)* + } + + impl $crate::events::Event for $name { + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + }; + + ($name:ident ($($inner:tt)*)) => { + #[derive(Debug, Clone)] + pub struct $name ($($inner)*); + + impl $crate::events::Event for $name { + fn as_any(&self) -> &dyn std::any::Any { + self + } + } + }; +} + +#[macro_export] +macro_rules! component { + ($name:ident) => { + #[derive(Debug, Clone)] + pub struct $name; + + impl $crate::widget::Component for $name { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + } + }; + + ($name:ident {$($inner:tt)*}) => { + #[derive(Debug, Clone)] + pub struct $name { + $($inner)* + } + + impl $crate::widget::Component for $name { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + } + }; + + ($name:ident ($($inner:tt)*)) => { + #[derive(Debug, Clone)] + pub struct $name ($($inner)*); + + impl $crate::widget::Component for $name { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + } + }; +} + +/// Macro to create an event handler closure that calls a method on `self` +/// +/// This macro automates the pattern of capturing `self` as a raw pointer, +/// then passing an event to a method on `self` inside an unsafe closure. +/// +/// # Parameters +/// - `$self_ty`: The self type. +/// - `$self`: The instance variable (usually `self`) to call the method on. +/// - `$events`: The event source which has an `.on` method to register the handler. +/// - `$method`: The method name on `self` to call when an event occurs. +/// +/// # Usage +/// ```rust +/// event_handler!(self, events, on_keypress); +/// ``` +/// +/// This expands roughly to: +/// ```rust +/// let self_ref = self as *mut Self; +/// events.on(move |event| unsafe { (*self_ref).on_keypress(event) }); +/// ``` +/// +/// # Safety +/// This macro uses `unsafe` code because it dereferences a raw pointer inside the closure. +/// Ensure that the `self` reference lives at least as long as the closure to avoid undefined behavior. +/// +/// # Why use raw pointers here? +/// Often, event handlers require `'static` closures, but `self` is a stack reference. +/// Capturing `self` directly is not possible, so this workaround uses a raw pointer. +#[macro_export] +macro_rules! event_handler { + ($self_ty:ty, $self:ident, $events:ident, $method:ident) => {{ + let self_ref = $self as *mut $self_ty; + $events.on(move |es, e| unsafe { (*self_ref).$method(es, e) }); + }}; +} + +#[macro_export] +macro_rules! rsx { + () => {}; +} diff --git a/src/main.rs b/src/main.rs index 28fdba6..3ef81fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,26 @@ use minifb::{Window, WindowOptions}; -use osgui::{elements::Rect, App}; +use osgui::{ + elements::Rect, + extensions::velocity::{Velocity, VelocityExtension}, + Screen, +}; fn main() { - let window = Window::new("minifb example", 500, 500, WindowOptions::default()).unwrap(); + let window = Window::new( + "minifb example", + 500, + 500, + WindowOptions { + resize: false, + ..Default::default() + }, + ) + .unwrap(); - let mut app = App::new(window); + let mut app = Screen::new(window); + app.extension(VelocityExtension); - app.element(Rect); + app.draw(Rect).component(Velocity(100, 0)); - app.run(); + app.run().unwrap(); } diff --git a/src/render.rs b/src/render.rs index d9b71da..3797913 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,26 +1,76 @@ +use crate::style::{RawTransform, Transform}; + +enum RenderMethod { + // Text(String), + Rectangle(usize, usize, u32), +} + pub struct RenderScope { + transform: RawTransform, + render_stack: Vec, + parent_width: usize, + parent_height: usize, buffer: Vec, - width: usize, - height: usize, } impl RenderScope { pub fn new(w: usize, h: usize) -> Self { Self { buffer: vec![0; w * h], - width: w, - height: h, + render_stack: Vec::new(), + parent_width: w, + parent_height: h, + transform: RawTransform::new(), } } - pub fn draw_rect(&mut self) { - for y in 0..200 { - for x in 0..200 { - self.buffer[y * self.height + x] = 0xFF00FF; + pub fn set_transform(&mut self, transform: &Transform) { + transform.use_dimensions(&mut self.transform); + transform.use_position(self.parent_width, self.parent_height, &mut self.transform); + } + + // pub fn draw_text(&mut self, text: &str) { + // self.render_stack.push(RenderMethod::Text(text.to_string())); + // let (w, h) = utils::str_size(text); + // self.transform.width = self.transform.width.max(w); + // self.transform.height = self.transform.height.max(h); + // } + + pub fn draw_rect(&mut self, width: usize, height: usize, color: u32) { + self.render_stack + .push(RenderMethod::Rectangle(width, height, color)); + self.transform.width = self.transform.width.max(width); + self.transform.height = self.transform.height.max(height); + } + + pub fn draw(&mut self) { + for m in &self.render_stack { + match m { + &RenderMethod::Rectangle(width, height, color) => { + for y in 0..height { + for x in 0..width { + self.buffer[y * self.parent_height + x] = color; + } + } + } } } } + pub fn clear(&mut self) { + self.render_stack.clear(); + self.transform.width = 0; + self.transform.height = 0; + } + + pub fn get_size(&self) -> (usize, usize) { + (self.transform.width, self.transform.height) + } + + pub fn get_parent_size(&self) -> (usize, usize) { + (self.parent_width, self.parent_height) + } + pub fn get_buffer(&self) -> Vec { self.buffer.clone() } diff --git a/src/style.rs b/src/style.rs new file mode 100644 index 0000000..b08932e --- /dev/null +++ b/src/style.rs @@ -0,0 +1,119 @@ +use crate::component; + +pub struct RawTransform { + pub x: usize, + pub y: usize, + pub width: usize, + pub height: usize, +} + +#[derive(Debug, Clone)] +pub enum Position { + Const(usize), + Center, + End, +} + +#[derive(Debug, Clone)] +pub enum Dimension { + Auto, + Const(usize), +} + +component!(Transform { + pub x: Position, + pub y: Position, + pub mx: i32, + pub my: i32, + pub width: Dimension, + pub height: Dimension, +}); + +impl Transform { + pub fn new() -> Transform { + Transform { + x: Position::Const(0), + y: Position::Const(0), + mx: 0, + my: 0, + width: Dimension::Auto, + height: Dimension::Auto, + } + } + + pub fn center() -> Transform { + Transform { + x: Position::Center, + y: Position::Center, + mx: 0, + my: 0, + width: Dimension::Auto, + height: Dimension::Auto, + } + } + + pub fn bottom(mut self) -> Self { + self.y = Position::End; + self + } + + pub fn margin(mut self, x: i32, y: i32) -> Self { + self.mx = x; + self.my = y; + self + } + + pub fn dimensions(mut self, width: usize, height: usize) -> Self { + self.width = Dimension::Const(width); + self.height = Dimension::Const(height); + self + } + + pub fn use_dimensions(&self, raw: &mut RawTransform) { + self.width.use_dimension(&mut raw.width); + self.height.use_dimension(&mut raw.height); + } + + pub fn use_position(&self, parent_width: usize, parent_height: usize, raw: &mut RawTransform) { + self.x + .use_position(raw.width, parent_width, self.mx, &mut raw.x); + self.y + .use_position(raw.height, parent_height, self.my, &mut raw.y); + } +} + +impl RawTransform { + pub fn new() -> RawTransform { + RawTransform { + x: 0, + y: 0, + width: 0, + height: 0, + } + } +} + +impl Dimension { + pub fn use_dimension(&self, r: &mut usize) { + match self { + Self::Auto => {} + Self::Const(n) => *r = *n, + } + } +} + +impl Position { + pub fn use_position(&self, size: usize, parent: usize, m: i32, r: &mut usize) { + match self { + Self::Center => *r = (parent - size) / 2, + Self::Const(n) => *r = *n, + Self::End => *r = parent - size, + } + + if m > 0 { + *r += m as usize; + } else { + *r -= -m as usize; + } + } +} diff --git a/src/widget.rs b/src/widget.rs index f9c9618..cfc4acc 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,5 +1,56 @@ +use std::{ + any::{Any, TypeId}, + collections::HashMap, + sync::{Arc, Mutex}, +}; + use crate::render::RenderScope; -pub trait Element { - fn render(&mut self, scope: &mut RenderScope); +pub type BoxedElement = Box; +pub type BoxedComponent = Box; + +pub trait Element: Send + Sync { + #[allow(unused)] + fn render(&mut self, scope: &mut RenderScope) {} + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +pub trait Component: Send + Sync { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +pub struct Widget( + pub Mutex, + Mutex>, +); + +impl Widget { + pub fn new(e: Box) -> Self { + Self(Mutex::new(e), Mutex::new(HashMap::new())) + } + + pub fn component(self: &Arc, c: C) -> &Arc { + self.1 + .lock() + .unwrap() + .entry(c.type_id()) + .or_insert_with(|| Box::new(c)); + self + } + + pub fn set_component(self: &Arc, c: C) -> &Arc { + self.1.lock().unwrap().insert(c.type_id(), Box::new(c)); + self + } + + pub fn get(&self) -> Option { + self.1 + .lock() + .unwrap() + .get(&TypeId::of::()) + .and_then(|c| c.as_any().downcast_ref::()) + .map(|c| c.clone()) + } }