performane optimisations
This commit is contained in:
Generated
+1
@@ -341,6 +341,7 @@ name = "osgui"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"minifb",
|
||||
"once_cell",
|
||||
"rusttype",
|
||||
]
|
||||
|
||||
|
||||
@@ -10,5 +10,6 @@ name = "osgui"
|
||||
|
||||
[dependencies]
|
||||
minifb = "0.28.0"
|
||||
once_cell = "1.21.3"
|
||||
rusttype = "0.9.3"
|
||||
|
||||
|
||||
@@ -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<Widget>) {
|
||||
if let Some(velocity) = widget.get::<Velocity>() {
|
||||
if let Some(mut t) = widget.get::<Transform>() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-11
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+92
-90
@@ -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<Font<'static>> = 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<Vec<u32>>,
|
||||
buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
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<Vec<u32>>) {
|
||||
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<u32> {
|
||||
self.buffer
|
||||
.iter()
|
||||
.flat_map(|row| row.iter().copied())
|
||||
.collect()
|
||||
pub fn get_buffer1d(&self) -> &Vec<u32> {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub fn get_buffer(&self) -> Vec<Vec<u32>> {
|
||||
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<Vec<u32>> {
|
||||
&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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user