maverick_os 3.0.2

Maverick OS
use std::num::NonZeroU32;

use font8x8::UnicodeFonts;
use image::RgbaImage;
use softbuffer::{Context, Surface};

use maverick_os::{Data, Renderer, Window};

pub struct Quad {
    pub label: &'static str,
    pub data: Option<Data>,
}

pub struct Screen {
    pub top_left: Quad,
    pub top_right: Quad,
    pub bottom_left: Quad,
    pub bottom_right: Quad,
    pub buttons: Vec<Button>,
}

#[derive(Clone, Copy)]
pub struct Button {
    pub label: &'static str,
    pub x: f32,
    pub y: f32,
    pub w: f32,
    pub h: f32,
}

pub struct DemoRenderer {
    surface: Surface<Window, Window>,
    _context: Context<Window>,
}

impl Renderer for DemoRenderer {
    type Instructions = Screen;

    fn new(window: Window) -> Self {
        let context = Context::new(window.clone()).unwrap();
        let surface = Surface::new(&context, window).unwrap();
        let mut renderer = DemoRenderer{surface, _context: context};
        renderer.resize();
        renderer
    }

    fn resize(&mut self) {
        let size = self.surface.window().physical();
        let (Some(width), Some(height)) = (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) else {return};
        self.surface.resize(width, height).unwrap();
    }

    fn draw(&mut self, screen: Self::Instructions) {
        let scale = self.surface.window().physical().scale_factor as f32;
        let mut buffer = match self.surface.buffer_mut() {
            Ok(buffer) => buffer,
            Err(err) => {log::error!("surface: {err}"); return}
        };
        let width = buffer.width().get();
        let height = buffer.height().get();
        Canvas{
            pixels: &mut buffer,
            width, height, scale,
            glyph: scale.round() as u32,
        }.paint(&screen);
        buffer.present().unwrap();
    }
}

struct Canvas<'a> {
    pixels: &'a mut [u32],
    width: u32,
    height: u32,
    scale: f32,
    glyph: u32,
}

impl Canvas<'_> {
    fn paint(&mut self, screen: &Screen) {
        let content = self.content_height(screen);
        let mid_x = self.width / 2;
        let mid_y = content / 2;
        let camera_on = [
            &screen.top_left, &screen.top_right,
            &screen.bottom_left, &screen.bottom_right,
        ].into_iter().any(|quad|
            quad.label == "camera" && matches!(quad.data, Some(Data::Image(_))));
        self.paint_quad(0, 0, mid_x, mid_y, 0x00161616, &screen.top_left);
        self.paint_quad(mid_x, 0, self.width - mid_x, mid_y, 0x001c1c1c, &screen.top_right);
        self.paint_quad(0, mid_y, mid_x, content - mid_y, 0x00141820, &screen.bottom_left);
        self.paint_quad(mid_x, mid_y, self.width - mid_x, content - mid_y, 0x00201814, &screen.bottom_right);
        self.fill(0, content, self.width, self.height.saturating_sub(content), 0x00101010);
        for button in &screen.buttons {self.paint_button(button, camera_on);}
    }

    fn content_height(&self, screen: &Screen) -> u32 {
        let Some(top) = screen.buttons.iter().map(|button| button.y).reduce(f32::min) else {return self.height};
        ((top * self.scale).round().max(0.0) as u32).min(self.height)
    }

    fn paint_button(&mut self, button: &Button, camera_on: bool) {
        let (x, y, w, h) = self.rect(button.x, button.y, button.w, button.h);
        let fill = if camera_on && button.label == "camera" {0x00245c3a} else {0x00282828};
        self.fill(x, y, w, h, 0x005a5a5a);
        let edge = self.glyph;
        self.fill(x + edge, y + edge, w.saturating_sub(edge * 2), h.saturating_sub(edge * 2), fill);
        let text_w = (button.label.chars().count() as u32 * self.cell()).min(w.saturating_sub(edge * 2));
        self.draw_text(x + w.saturating_sub(text_w) / 2, y + h.saturating_sub(self.cell()) / 2, text_w, self.cell(), button.label, 0x00f2f2f2);
    }

    fn rect(&self, x: f32, y: f32, w: f32, h: f32) -> (u32, u32, u32, u32) {
        (
            (x * self.scale).round().max(0.0) as u32,
            (y * self.scale).round().max(0.0) as u32,
            (w * self.scale).round().max(0.0) as u32,
            (h * self.scale).round().max(0.0) as u32,
        )
    }

    fn cell(&self) -> u32 {8 * self.glyph}
    fn pad(&self) -> u32 {4 * self.glyph}

    fn paint_quad(&mut self, x: u32, y: u32, w: u32, h: u32, bg: u32, quad: &Quad) {
        self.fill(x, y, w, h, bg);
        self.draw_text(x + self.pad(), y + self.pad(), w.saturating_sub(self.pad() * 2), self.cell(), quad.label, 0x00a0a0a0);
        let (cx, cy, cw, ch) = self.content(x, y, w, h);
        match quad.data.as_ref() {
            Some(Data::Image(image)) => self.blit(cx, cy, cw, ch, image),
            Some(Data::Video(video)) => self.blit(cx, cy, cw, ch, &video.thumbnail()),
            Some(Data::Text(text)) => self.draw_text(cx, cy, cw, ch, text, 0x00f2f2f2),
            Some(Data::File(path)) => self.draw_text(
                cx, cy, cw, ch, &path.to_string_lossy(), 0x00f2f2f2,
            ),
            None => self.draw_text(cx, cy, cw, ch, "empty", 0x00f2f2f2),
        }
    }

    fn content(&self, x: u32, y: u32, w: u32, h: u32) -> (u32, u32, u32, u32) {
        let pad = self.pad();
        let label = self.cell();
        (x + pad, y + pad + label + pad, w.saturating_sub(pad * 2), h.saturating_sub(pad * 3 + label))
    }

    fn fill(&mut self, x: u32, y: u32, w: u32, h: u32, color: u32) {
        let x1 = (x + w).min(self.width);
        let y1 = (y + h).min(self.height);
        if x >= self.width || y >= self.height || x >= x1 || y >= y1 {return}
        for row in y..y1 {
            let start = row as usize * self.width as usize + x as usize;
            let end = row as usize * self.width as usize + x1 as usize;
            self.pixels[start..end].fill(color);
        }
    }

    fn blit(&mut self, x: u32, y: u32, w: u32, h: u32, image: &RgbaImage) {
        let iw = image.width();
        let ih = image.height();
        if iw == 0 || ih == 0 || w == 0 || h == 0 {return}
        for row in 0..h {
            let sy = (row as u64 * ih as u64 / h as u64) as u32;
            let dst = (y + row) as usize * self.width as usize + x as usize;
            for col in 0..w {
                let sx = (col as u64 * iw as u64 / w as u64) as u32;
                let pixel = image.get_pixel(sx, sy).0;
                self.pixels[dst + col as usize] = (pixel[0] as u32) << 16 | (pixel[1] as u32) << 8 | pixel[2] as u32;
            }
        }
    }

    fn draw_text(&mut self, x: u32, y: u32, w: u32, h: u32, text: &str, color: u32) {
        let cell = self.cell();
        let cols = w / cell;
        let rows = h / cell;
        if cols == 0 || rows == 0 {return}
        let mut col = 0u32;
        let mut row = 0u32;
        for ch in text.chars() {
            if ch == '\n' || col >= cols {
                col = 0;
                row += 1;
                if row >= rows {break}
                if ch == '\n' {continue}
            }
            self.draw_glyph(x + col * cell, y + row * cell, ch, color);
            col += 1;
        }
    }

    fn draw_glyph(&mut self, x: u32, y: u32, ch: char, color: u32) {
        let Some(glyph) = font8x8::BASIC_FONTS.get(ch).or_else(|| font8x8::BASIC_FONTS.get('?')) else {return};
        let scale = self.glyph;
        for (row, bits) in glyph.into_iter().enumerate() {
            for col in 0..8u32 {
                if bits & (1 << col) == 0 {continue}
                for dy in 0..scale {
                    for dx in 0..scale {
                        let px = x + col * scale + dx;
                        let py = y + row as u32 * scale + dy;
                        if px >= self.width {continue}
                        let index = py as usize * self.width as usize + px as usize;
                        if index < self.pixels.len() {self.pixels[index] = color}
                    }
                }
            }
        }
    }
}