diff --git a/Cargo.lock b/Cargo.lock index 0023605..75bb863 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ name = "osgui" version = "0.0.1" dependencies = [ "minifb", + "once_cell", "rusttype", ] diff --git a/Cargo.toml b/Cargo.toml index 0fbb60d..dc195df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,6 @@ name = "osgui" [dependencies] minifb = "0.28.0" +once_cell = "1.21.3" rusttype = "0.9.3" diff --git a/src/extensions/velocity.rs b/src/extensions/velocity.rs index 8eda07f..d8e9805 100644 --- a/src/extensions/velocity.rs +++ b/src/extensions/velocity.rs @@ -1,16 +1,11 @@ use std::sync::Arc; -use crate::{ - component, - extensions::Extension, - style::{Position, Transform}, - widget::Widget, -}; +use crate::{component, extensions::Extension, style::Transform, widget::Widget}; pub struct VelocityExtension; impl VelocityExtension { - fn apply_velocity(ticks: usize, velocity: i32, x: &mut usize) { + fn apply_velocity(ticks: usize, velocity: i32, x: &mut i32) { if velocity.abs() != 0 && ticks as i32 % (1000 / velocity.abs()) == 0 { if velocity > 0 { *x += 1; @@ -25,16 +20,8 @@ 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), - _ => {} - } - + Self::apply_velocity(ticks, velocity.0, &mut t.mx); + Self::apply_velocity(ticks, velocity.1, &mut t.my); widget.set_component(t); } } diff --git a/src/lib.rs b/src/lib.rs index 4f86755..ea74835 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ -use std::sync::{Arc, Mutex}; - use minifb::Window; +use std::sync::{Arc, Mutex}; use crate::{ extensions::Extension, @@ -24,8 +23,8 @@ pub struct Screen { } impl Screen { - pub fn new(win: Window) -> Screen { - Screen { + pub fn new(win: Window) -> Self { + Self { window: win, widgets: Vec::new(), extensions: Vec::new(), @@ -42,7 +41,7 @@ impl Screen { } pub fn run(&mut self) -> std::io::Result<()> { - for elem in &mut self.widgets { + for elem in &self.widgets { elem.component(Transform::new()); } @@ -50,17 +49,30 @@ impl Screen { ext.lock().unwrap().init(&self.widgets); } + let (w, h) = self.window.get_size(); + let mut scope = RenderScope::new(w, h); + + let mut last_fps_check = std::time::Instant::now(); + let mut frame_count = 0; + while self.window.is_open() { - self.render(); - std::thread::sleep(std::time::Duration::from_millis(28)); + self.render(&mut scope); + frame_count += 1; + + // Print FPS every second + if last_fps_check.elapsed() >= std::time::Duration::from_secs(1) { + println!("{}", frame_count); + frame_count = 0; + last_fps_check = std::time::Instant::now(); + } } Ok(()) } - fn render(&mut self) { + fn render(&mut self, scope: &mut RenderScope) { let (w, h) = self.window.get_size(); - let mut scope = RenderScope::new(w, h); + scope.resize_if_needed(w, h); for ext in &self.extensions { ext.lock() @@ -74,7 +86,7 @@ impl Screen { scope.set_transform(&t); } - elem.0.lock().unwrap().render(&mut scope); + elem.0.lock().unwrap().render(scope); if let Some(t) = elem.get() { scope.set_transform(&t); @@ -90,7 +102,8 @@ impl Screen { } self.window - .update_with_buffer(&scope.get_buffer1d(), w, h) + .update_with_buffer(scope.get_buffer1d(), w, h) .unwrap(); + scope.clear_buffer(); } } diff --git a/src/main.rs b/src/main.rs index 88749ee..c03976c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ use minifb::{Window, WindowOptions}; use osgui::{ elements::div::{Div, DivStyle}, - extensions::mouse::{MouseExtension, OnClick}, + extensions::{ + mouse::{MouseExtension, OnClick}, + velocity::{Velocity, VelocityExtension}, + }, style::Transform, Screen, }; @@ -20,6 +23,7 @@ fn main() { let mut app = Screen::new(window); app.extension(MouseExtension::new()); + app.extension(VelocityExtension); milestones(&mut app); @@ -49,6 +53,7 @@ fn milestones(app: &mut Screen) { osgui::style::Position::Center, osgui::style::Position::Const(145 + ((194 + 30) * i)), )) - .component(OnClick(|_| println!("Clicked!"))); + .component(OnClick(|_| println!("Clicked!"))) + .component(Velocity(50, 0)); } } diff --git a/src/render.rs b/src/render.rs index e6e3d0b..175ceac 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,3 +1,4 @@ +use once_cell::sync::Lazy; use rusttype::{Font, Scale}; use crate::{ @@ -6,6 +7,7 @@ use crate::{ }; const FONT: &[u8] = include_bytes!("../assets/DejaVuSans.ttf"); +static FONT_OBJ: Lazy> = Lazy::new(|| Font::try_from_bytes(FONT).unwrap()); #[derive(Debug, Clone)] pub enum RenderMethod { @@ -22,13 +24,13 @@ pub struct RenderScope { parent_width: usize, parent_height: usize, max_size: (usize, usize), - buffer: Vec>, + buffer: Vec, } impl RenderScope { pub fn new(w: usize, h: usize) -> Self { Self { - buffer: vec![vec![0; w]; h], + buffer: vec![0; w * h], render_stack: Vec::new(), parent_width: w, parent_height: h, @@ -43,8 +45,7 @@ impl RenderScope { } self.render_stack .push(RenderMethod::Text(text.to_string(), x, y, scale, color)); - let font = Font::try_from_bytes(FONT).unwrap(); - let (w, h) = utils::measure_text(&font, &text, Scale::uniform(scale)); + let (w, h) = utils::measure_text(&FONT_OBJ, text, Scale::uniform(scale)); self.transform.width = self.transform.width.max(w as usize); self.transform.height = self.transform.height.max(h as usize); self.update_size(); @@ -97,123 +98,115 @@ impl RenderScope { // After draw impl RenderScope { pub fn draw(&mut self) { + let stride = self.parent_width; let mut tmp = std::mem::take(&mut self.buffer); - self.draw_buf(&mut tmp); + self.draw_buf(&mut tmp, stride); self.buffer = tmp; } - pub fn draw_buf(&self, buf: &mut Vec>) { + pub fn draw_buf(&mut self, buf: &mut [u32], stride: usize) { let offset_x = self.transform.x; let offset_y = self.transform.y; - for m in &self.render_stack { + for m in &mut self.render_stack { match m { - &RenderMethod::Rectangle(px, py, width, height, color) => { - for y in offset_y + py..(offset_y + py + height).min(self.parent_height) { - for x in offset_x + px..(offset_x + px + width).min(self.parent_width) { - buf[y][x] = color; - } + /* ───────────── Rectangle ───────────── */ + RenderMethod::Rectangle(px, py, width, height, color) => { + let xs = offset_x + *px; + let xe = (xs + *width).min(self.parent_width); + let ys = offset_y + *py; + let ye = (ys + *height).min(self.parent_height); + + for y in ys..ye { + let start = y * stride + xs; + let end = start + (xe - xs); + buf[start..end].fill(*color); } } - &RenderMethod::RoundedRectangle(px, py, width, height, radius, color) => { - let radius = radius as i32; - let w = width as i32; - let h = height as i32; - let start_x = offset_x as i32 + px as i32; - let start_y = offset_y as i32 + py as i32; + /* ─────────── Rounded Rectangle ─────────── */ + RenderMethod::RoundedRectangle(px, py, width, height, radius, color) => { + let r = *radius as i32; + let ww = *width as i32; + let hh = *height as i32; + let sx = (offset_x + *px) as i32; + let sy = (offset_y + *py) as i32; - for y in 0..h { - for x in 0..w { - let dx = x; - let dy = y; + for y in 0..hh { + for x in 0..ww { + // corners ⇒ circle test + let in_tl = x < r && y < r; + let in_tr = x >= ww - r && y < r; + let in_bl = x < r && y >= hh - r; + let in_br = x >= ww - r && y >= hh - r; - let in_top_left = dx < radius && dy < radius; - let in_top_right = dx >= w - radius && dy < radius; - let in_bottom_left = dx < radius && dy >= h - radius; - let in_bottom_right = dx >= w - radius && dy >= h - radius; - - let dist = |cx: i32, cy: i32| -> bool { - let dx = dx - cx; - let dy = dy - cy; - dx * dx + dy * dy <= radius * radius + let ok = if in_tl { + (x - r + 1).pow(2) + (y - r + 1).pow(2) <= r.pow(2) + } else if in_tr { + (x - (ww - r)).pow(2) + (y - r + 1).pow(2) <= r.pow(2) + } else if in_bl { + (x - r + 1).pow(2) + (y - (hh - r)).pow(2) <= r.pow(2) + } else if in_br { + (x - (ww - r)).pow(2) + (y - (hh - r)).pow(2) <= r.pow(2) + } else { + true }; - let draw = match ( - in_top_left, - in_top_right, - in_bottom_left, - in_bottom_right, - ) { - (true, _, _, _) => dist(radius - 1, radius - 1), - (_, true, _, _) => dist(w - radius, radius - 1), - (_, _, true, _) => dist(radius - 1, h - radius), - (_, _, _, true) => dist(w - radius, h - radius), - _ => true, // middle or edges outside corner - }; - - if draw { - let global_x = start_x + x; - let global_y = start_y + y; - if global_x >= 0 - && global_x < self.parent_width as i32 - && global_y >= 0 - && global_y < self.parent_height as i32 + if ok { + let gx = sx + x; + let gy = sy + y; + if gx >= 0 + && gx < self.parent_width as i32 + && gy >= 0 + && gy < self.parent_height as i32 { - buf[global_y as usize][global_x as usize] = color; + buf[(gy as usize) * stride + gx as usize] = *color; } } } } } - RenderMethod::Text(text, px, py, scale, base_color) => { - let font = Font::try_from_bytes(FONT).unwrap(); + /* ────────────────── Text ───────────────── */ + RenderMethod::Text(text, px, py, scale, base) => { let scale = Scale::uniform(*scale); - let v_metrics = font.v_metrics(scale); + let ascent = FONT_OBJ.v_metrics(scale).ascent; - let glyphs: Vec<_> = font - .layout( - text, - scale, - rusttype::point( - (offset_x + px) as f32, - (offset_y + py) as f32 + v_metrics.ascent, - ), - ) - .collect(); + let r0 = ((*base >> 16) & 0xFF) as f32; + let g0 = ((*base >> 8) & 0xFF) as f32; + let b0 = (*base & 0xFF) as f32; - let r_base = ((base_color >> 16) & 0xFF) as f32; - let g_base = ((base_color >> 8) & 0xFF) as f32; - let b_base = (base_color & 0xFF) as f32; - - for glyph in glyphs { + for glyph in FONT_OBJ.layout( + text, + scale, + rusttype::point((offset_x + *px) as f32, (offset_y + *py) as f32 + ascent), + ) { if let Some(bb) = glyph.pixel_bounding_box() { glyph.draw(|gx, gy, v| { if v < 0.5 { return; } - let x = (gx as i32 + bb.min.x + *px as i32) as usize; - let y = (gy as i32 + bb.min.y + *py as i32) as usize; + let x = gx as i32 + bb.min.x; + let y = gy as i32 + bb.min.y; - if let Some(row) = buf.get_mut(y) { - if let Some(col) = row.get_mut(x) { - let r = (r_base * v) as u32; - let g = (g_base * v) as u32; - let b = (b_base * v) as u32; - *col = (r << 16) | (g << 8) | b; - } + let idx = (y as usize) * stride + x as usize; + if let Some(p) = buf.get_mut(idx) { + let r = (r0 * v) as u32; + let g = (g0 * v) as u32; + let b = (b0 * v) as u32; + *p = (r << 16) | (g << 8) | b; } }); } } } + + /* ───────────────── Merge ───────────────── */ RenderMethod::Merge(scope) => { - let mut scope = scope.clone(); scope.transform.x += self.transform.x; scope.transform.y += self.transform.y; - scope.draw_buf(buf); + scope.draw_buf(buf, stride); } } } @@ -223,6 +216,14 @@ impl RenderScope { // Update or variable functions impl RenderScope { + pub fn resize_if_needed(&mut self, w: usize, h: usize) { + if self.parent_width != w || self.parent_height != h { + self.parent_width = w; + self.parent_height = h; + self.buffer.resize(w * h, 0); + } + } + 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); @@ -270,18 +271,19 @@ impl RenderScope { (self.parent_width, self.parent_height) } - pub fn get_buffer1d(&self) -> Vec { - self.buffer - .iter() - .flat_map(|row| row.iter().copied()) - .collect() + pub fn get_buffer1d(&self) -> &Vec { + &self.buffer } - pub fn get_buffer(&self) -> Vec> { - self.buffer.clone() + pub fn get_buffer(&self) -> Vec<&[u32]> { + self.buffer.chunks(self.parent_width).collect() } - pub fn get_buffer_mut(&mut self) -> &mut Vec> { - &mut self.buffer + pub fn get_buffer_mut(&mut self) -> Vec<&mut [u32]> { + self.buffer.chunks_mut(self.parent_width).collect() + } + + pub fn clear_buffer(&mut self) { + self.buffer.fill(0); } }