lector-browser 0.1.4

A high-performance graphical terminal web browser targeting Sixel first.
use crate::engine::{BrowserEngine, EngineEvent};
use crate::graphics::frame::{Frame, Rgba};

pub struct DemoEngine {
    url: String,
    scroll_y: i32,
    typed: String,
    cursor: Option<(u16, u16)>,
}

impl DemoEngine {
    pub fn new(url: String) -> Self {
        Self {
            url,
            scroll_y: 0,
            typed: String::new(),
            cursor: None,
        }
    }
}

impl BrowserEngine for DemoEngine {
    fn handle_event(&mut self, event: EngineEvent) {
        match event {
            EngineEvent::Scroll { dx, dy } => {
                self.scroll_y = (self.scroll_y + dy + dx / 4).max(0);
            }
            EngineEvent::Text(text) => {
                self.typed.push_str(&text);
                if self.typed.len() > 48 {
                    let keep_from = self.typed.len() - 48;
                    self.typed = self.typed[keep_from..].to_string();
                }
            }
            EngineEvent::Click { x, y } | EngineEvent::Drag { x, y } => {
                self.cursor = Some((x, y));
            }
            EngineEvent::PointerMove { x, y } => {
                self.cursor = Some((x as u16, y as u16));
            }
            EngineEvent::PointerDown { x, y, button } | EngineEvent::PointerUp { x, y, button } => {
                let _ = button;
                self.cursor = Some((x as u16, y as u16));
            }
            EngineEvent::Wheel { x, y, dx, dy } => {
                let _ = (dx, dy);
                self.cursor = Some((x as u16, y as u16));
            }
            EngineEvent::KeyPress(key) => {
                let _ = key;
            }
            EngineEvent::Navigate(url) => {
                let _ = url;
            }
            EngineEvent::SwitchTab(index) => {
                let _ = index;
            }
            EngineEvent::NewTab | EngineEvent::CloseTab => {}
            EngineEvent::Resize { width, height } => {
                let _ = (width, height);
            }
        }
    }

    fn render(&mut self, width: u32, height: u32) -> Frame {
        let mut frame = Frame::new(width, height, Rgba::rgb(244, 242, 237));
        paint_page(
            &mut frame,
            &self.url,
            self.scroll_y,
            &self.typed,
            self.cursor,
        );
        frame
    }
}

fn paint_page(
    frame: &mut Frame,
    url: &str,
    scroll_y: i32,
    typed: &str,
    cursor: Option<(u16, u16)>,
) {
    let width = frame.width();
    let height = frame.height();

    frame.fill_rect(0, 0, width, 54, Rgba::rgb(28, 32, 36));
    frame.fill_rect(18, 14, width.saturating_sub(36), 26, Rgba::rgb(54, 61, 68));
    frame.draw_text(30, 22, url, Rgba::rgb(222, 231, 235));

    let page_top = 74 - scroll_y;
    frame.draw_text(32, page_top, "Lector", Rgba::rgb(20, 24, 28));
    frame.draw_text(
        32,
        page_top + 28,
        "Graphical terminal browsing, rendered as pixels.",
        Rgba::rgb(63, 71, 79),
    );

    for i in 0..8 {
        let y = page_top + 78 + i * 132;
        let shade = 255_u8.saturating_sub((i as u8) * 9);
        frame.fill_rect(
            32,
            y,
            width.saturating_sub(64),
            96,
            Rgba::rgb(shade, shade, shade),
        );
        frame.stroke_rect(
            32,
            y,
            width.saturating_sub(64),
            96,
            Rgba::rgb(198, 194, 186),
        );
        frame.fill_rect(52, y + 22, 120, 12, Rgba::rgb(46, 105, 118));
        frame.fill_rect(
            52,
            y + 48,
            width.saturating_sub(150),
            9,
            Rgba::rgb(116, 126, 133),
        );
        frame.fill_rect(
            52,
            y + 66,
            width.saturating_sub(220),
            9,
            Rgba::rgb(148, 155, 160),
        );
    }

    let input_y = height as i32 - 62;
    frame.fill_rect(
        24,
        input_y,
        width.saturating_sub(48),
        38,
        Rgba::rgb(255, 255, 255),
    );
    frame.stroke_rect(
        24,
        input_y,
        width.saturating_sub(48),
        38,
        Rgba::rgb(40, 120, 132),
    );
    let label = if typed.is_empty() {
        "TYPE HERE; SERVO INPUT PLUMBING WILL REUSE THIS PATH"
    } else {
        typed
    };
    frame.draw_text(40, input_y + 15, label, Rgba::rgb(38, 43, 48));
    frame.draw_text(
        24,
        height as i32 - 14,
        "Q QUITS | ARROWS/WHEEL SCROLL",
        Rgba::rgb(63, 71, 79),
    );

    if let Some((x, y)) = cursor {
        let px = (x as i32).saturating_mul(8);
        let py = (y as i32).saturating_mul(16);
        frame.stroke_rect(px - 8, py - 8, 24, 24, Rgba::rgb(224, 75, 52));
    }
}