Skip to content
Snippets Groups Projects
chat.rs 3.34 KiB
use embedded_graphics::{
    geometry::{Point, Size},
    mono_font::{ascii::FONT_10X20, MonoTextStyle},
    pixelcolor::{Rgb565, RgbColor},
    primitives::{Primitive, PrimitiveStyleBuilder, Rectangle},
    text::renderer::TextRenderer,
    Drawable,
};
use embedded_text::{style::TextBoxStyleBuilder, TextBox};
use heapless::String;

use crate::{
    app::WIDTH,
    keyboard::KeyCode,
    storage::{Message, MessageDirection, Storage, MAX_CONTACT_CALLSIGN_LENGTH},
};

use super::{textbox::TextBox as MyTextBox, Element};

const MESSAGE_BOX_WIDTH: u32 = 200;
const MESSAGE_BOX_HEIGHT: u32 = 64;

pub struct Chat {
    callsign: String<MAX_CONTACT_CALLSIGN_LENGTH>,
    ssid: u8,
    textbox: MyTextBox,
}

impl Chat {
    pub fn new(callsign: String<MAX_CONTACT_CALLSIGN_LENGTH>, ssid: u8) -> Self {
        Self {
            callsign,
            ssid,
            textbox: MyTextBox::new(1, 218, 31, 1),
        }
    }

    pub fn render<
        E,
        DT: embedded_graphics::prelude::DrawTarget<
            Color = embedded_graphics::pixelcolor::Rgb565,
            Error = E,
        >,
    >(
        &mut self,
        dt: &mut DT,
        storage: &Storage,
    ) -> Result<(), E> {
        let textbox_style = TextBoxStyleBuilder::new().build();
        let character_style = MonoTextStyle::new(&FONT_10X20, Rgb565::BLACK);
        let rect_style = PrimitiveStyleBuilder::new()
            .fill_color(Rgb565::WHITE)
            .build();

        let mut y = 28;
        for msg in storage.messages(&self.callsign, self.ssid) {
            let bounds = Rectangle::new(
                Point::new(4, y),
                Size::new(MESSAGE_BOX_WIDTH, MESSAGE_BOX_HEIGHT),
            );

            let mut text_box =
                TextBox::with_textbox_style(msg.content(), bounds, character_style, textbox_style);

            let mut actual_bounds = calculate_bounding_box(&text_box);
            if msg.direction() == MessageDirection::To {
                let x = i32::try_from(WIDTH).unwrap()
                    - i32::try_from(actual_bounds.size.width).unwrap()
                    - 4;
                text_box.bounds.top_left.x = x;
                actual_bounds.top_left.x = x;
            }

            actual_bounds.into_styled(rect_style).draw(dt)?;

            text_box.draw(dt)?;

            y += i32::try_from(actual_bounds.size.height).unwrap() + 4;
        }

        self.textbox.render(dt)?;

        Ok(())
    }

    pub fn key_push(&mut self, k: KeyCode, storage: &mut Storage) {
        if k == KeyCode::Enter {
            let text = self.textbox.clear();
            let message =
                Message::new(self.callsign.clone(), self.ssid, text, MessageDirection::To);

            storage.push_message(message);
        } else {
            self.textbox.key_push(k);
        }
    }
}

fn calculate_bounding_box<S: TextRenderer>(tb: &TextBox<'_, S>) -> Rectangle {
    const CHAR_WIDTH: u32 = 10;
    const CHAR_HEIGHT: u32 = 20;

    let mut x = 0;
    let mut max_x = 0;
    let mut y = 1;
    for c in tb.text.chars() {
        if c == '\n' || CHAR_WIDTH * x >= tb.bounds.size.width {
            x = 0;
            y += 1;
        } else {
            x += 1;

            if x > max_x {
                max_x = x;
            }
        }
    }

    Rectangle::new(
        tb.bounds.top_left,
        Size::new(max_x * CHAR_WIDTH, y * CHAR_HEIGHT),
    )
}