sevenx_engine 0.2.11

Engine de jogos 2D/3D completa com suporte Android, física, áudio, partículas, tilemap, UI, eventos e sistema 3D avançado com PBR.
Documentation
/// Sistema básico de UI para texto e botões.

/// Representa um botão na UI.
#[derive(Debug, Clone)]
pub struct Button {
    pub x: i32,
    pub y: i32,
    pub width: u32,
    pub height: u32,
    pub text: String,
    pub color: [u8; 4],
    pub hover_color: [u8; 4],
    pub is_hovered: bool,
    pub is_pressed: bool,
}

impl Button {
    pub fn new(x: i32, y: i32, width: u32, height: u32, text: &str) -> Self {
        Self {
            x,
            y,
            width,
            height,
            text: text.to_string(),
            color: [100, 100, 100, 255],
            hover_color: [150, 150, 150, 255],
            is_hovered: false,
            is_pressed: false,
        }
    }

    pub fn with_color(mut self, color: [u8; 4]) -> Self {
        self.color = color;
        self
    }

    pub fn with_hover_color(mut self, color: [u8; 4]) -> Self {
        self.hover_color = color;
        self
    }

    /// Verifica se o mouse está sobre o botão.
    pub fn contains_point(&self, mouse_x: i32, mouse_y: i32) -> bool {
        mouse_x >= self.x
            && mouse_x <= self.x + self.width as i32
            && mouse_y >= self.y
            && mouse_y <= self.y + self.height as i32
    }

    /// Atualiza o estado do botão.
    pub fn update(&mut self, mouse_x: i32, mouse_y: i32, mouse_pressed: bool) -> bool {
        self.is_hovered = self.contains_point(mouse_x, mouse_y);

        let was_pressed = self.is_pressed;
        self.is_pressed = self.is_hovered && mouse_pressed;

        // Retorna true se o botão foi clicado (released)
        was_pressed && !self.is_pressed && self.is_hovered
    }
}

/// Renderiza um botão no buffer de pixels.
pub fn render_button(button: &Button, pixels: &mut [u8], viewport_width: u32) {
    let color = if button.is_hovered {
        button.hover_color
    } else {
        button.color
    };

    for y in 0..button.height {
        for x in 0..button.width {
            let px = button.x + x as i32;
            let py = button.y + y as i32;

            if px < 0 || py < 0 || px >= viewport_width as i32 {
                continue;
            }

            let index = ((py as u32 * viewport_width) + px as u32) as usize * 4;
            if index + 3 < pixels.len() {
                pixels[index..index + 4].copy_from_slice(&color);
            }
        }
    }
}

/// Renderiza texto simples (bitmap font 8x8).
pub fn render_text(text: &str, x: i32, y: i32, color: [u8; 4], pixels: &mut [u8], viewport_width: u32) {
    for (i, ch) in text.chars().enumerate() {
        let char_x = x + (i as i32 * 8);
        render_char(ch, char_x, y, color, pixels, viewport_width);
    }
}

fn render_char(ch: char, x: i32, y: i32, color: [u8; 4], pixels: &mut [u8], viewport_width: u32) {
    let pattern = get_char_pattern(ch);

    for row in 0..8 {
        for col in 0..8 {
            if pattern[row] & (1 << (7 - col)) != 0 {
                let px = x + col as i32;
                let py = y + row as i32;

                if px >= 0 && py >= 0 && px < viewport_width as i32 {
                    let index = ((py as u32 * viewport_width) + px as u32) as usize * 4;
                    if index + 3 < pixels.len() {
                        pixels[index..index + 4].copy_from_slice(&color);
                    }
                }
            }
        }
    }
}

fn get_char_pattern(ch: char) -> [u8; 8] {
    match ch {
        'A' => [0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00],
        'B' => [0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00],
        'C' => [0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00],
        'D' => [0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00],
        'E' => [0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00],
        'F' => [0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00],
        'G' => [0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00],
        'H' => [0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
        'I' => [0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00],
        'J' => [0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00],
        'K' => [0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00],
        'L' => [0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00],
        'M' => [0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00],
        'N' => [0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00],
        'O' => [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
        'P' => [0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00],
        'Q' => [0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E, 0x00],
        'R' => [0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00],
        'S' => [0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00],
        'T' => [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00],
        'U' => [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
        'V' => [0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00],
        'W' => [0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00],
        'X' => [0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00],
        'Y' => [0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00],
        'Z' => [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00],
        '0' => [0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00],
        '1' => [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00],
        '2' => [0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E, 0x00],
        '3' => [0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00],
        '4' => [0x0C, 0x1C, 0x3C, 0x6C, 0x7E, 0x0C, 0x0C, 0x00],
        '5' => [0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00],
        '6' => [0x3C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00],
        '7' => [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00],
        '8' => [0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00],
        '9' => [0x3C, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00],
        ' ' => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
        ':' => [0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00],
        '!' => [0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00],
        _ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
    }
}

/// Renderiza um retângulo preenchido.
pub fn render_rect(x: i32, y: i32, width: u32, height: u32, color: [u8; 4], pixels: &mut [u8], viewport_width: u32) {
    for dy in 0..height {
        for dx in 0..width {
            let px = x + dx as i32;
            let py = y + dy as i32;

            if px < 0 || py < 0 || px >= viewport_width as i32 {
                continue;
            }

            let index = ((py as u32 * viewport_width) + px as u32) as usize * 4;
            if index + 3 < pixels.len() {
                pixels[index..index + 4].copy_from_slice(&color);
            }
        }
    }
}

/// Renderiza uma borda de retângulo.
pub fn render_rect_outline(x: i32, y: i32, width: u32, height: u32, thickness: u32, color: [u8; 4], pixels: &mut [u8], viewport_width: u32) {
    // Top
    render_rect(x, y, width, thickness, color, pixels, viewport_width);
    // Bottom
    render_rect(x, y + height as i32 - thickness as i32, width, thickness, color, pixels, viewport_width);
    // Left
    render_rect(x, y, thickness, height, color, pixels, viewport_width);
    // Right
    render_rect(x + width as i32 - thickness as i32, y, thickness, height, color, pixels, viewport_width);
}