diff --git a/Cargo.lock b/Cargo.lock index a08924a4c1174a79d17dada053a8a0d4de0b38d7..2d1a387bb0b323a48ec647443e362a09b6fa5dc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,7 @@ dependencies = [ "cortex-m-rtic", "embedded-graphics", "embedded-hal 0.2.7", + "embedded-text", "fugit", "heapless 0.8.0", "panic-rtt-target", @@ -216,6 +217,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +[[package]] +name = "embedded-text" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5899badc344754f785450bd64c04e03f6ceaacb5f3ae972fc1d93ae8473f4689" +dependencies = [ + "az", + "embedded-graphics", + "object-chain", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -429,6 +441,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "object-chain" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41af26158b0f5530f7b79955006c2727cd23d0d8e7c3109dc316db0a919784dd" + [[package]] name = "panic-rtt-target" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index 40fcb25016fbe8b217ce09967206576195290bcf..086be698b915f1a966e0de8d540f018c6b11b1dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ usbd-serial = "0.1" heapless = "0.8.0" rtt-target = "0.5.0" panic-rtt-target = "0.1.3" +embedded-text = "0.7.1" diff --git a/src/controller.rs b/src/controller.rs index 091881edc03c20a5b5adcce48507d3da69605662..1300f22683ad4f2e8e56838ca6027dba65ccfbc6 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -3,7 +3,10 @@ use heapless::{String, Vec}; use crate::{ contact::{Contact, ContactGroup, MAX_CONTACTS, MAX_CONTACT_NAME_LENGTH}, - gui::{chat::Chat, contact_view::ContactView, selector::Selector, status::StatusBar, Element}, + gui::{ + chat::Chat, chat_list::ChatList, contact_view::ContactView, selector::Selector, + status::StatusBar, Element, + }, keyboard::KeyCode, voltage::VoltMeter, }; @@ -19,6 +22,7 @@ pub enum View { ContactOptions(Selector<&'static str, [&'static str; 3]>, usize), #[allow(clippy::enum_variant_names)] ContactView(ContactView, usize), + ChatList(ChatList), Chat(Chat), } @@ -46,7 +50,7 @@ impl View { pub fn from_main_menu_id(id: usize, contacts: &ContactGroup) -> Option<Self> { match id { 0 => Some(Self::new_contacts(0, contacts)), - 1 => Some(Self::Chat(Chat::new())), + 1 => Some(Self::ChatList(ChatList::new())), _ => None, } } @@ -54,7 +58,7 @@ impl View { pub fn to_main_menu_id(&self) -> usize { match self { View::Contacts(_) => 0, - View::Chat(_) => 1, + View::ChatList(_) => 1, _ => unreachable!(), } } @@ -104,6 +108,7 @@ impl Element for View { View::Contacts(c) => c.render(dt), View::ContactOptions(c, _) => c.render(dt), View::ContactView(c, _) => c.render(dt), + View::ChatList(c) => c.render(dt), View::Chat(c) => c.render(dt), } } @@ -116,6 +121,7 @@ impl Element for View { View::ContactView(c, _) => { c.key_push(k); } + View::ChatList(c) => c.key_push(k), View::Chat(c) => c.key_push(k), } } @@ -126,6 +132,7 @@ impl Element for View { View::Contacts(c) => c.touchpad_scroll(x, y), View::ContactOptions(c, _) => c.touchpad_scroll(x, y), View::ContactView(c, _) => c.touchpad_scroll(x, y), + View::ChatList(c) => c.touchpad_scroll(x, y), View::Chat(c) => c.touchpad_scroll(x, y), } } @@ -194,6 +201,10 @@ impl Element for Controller { } } + (View::ChatList(_), KeyCode::Touchpad) => { + self.cur_view = View::Chat(Chat::new()); + } + (View::MainMenu(_), KeyCode::Back) => {} (View::ContactOptions(_, id), KeyCode::Back) => { @@ -205,6 +216,10 @@ impl Element for Controller { self.cur_view = View::new_contact_options(*contact_id) } + (View::Chat(_), KeyCode::Back) => { + self.cur_view = View::ChatList(ChatList::new()); + } + (_, KeyCode::Back) => { let id = self.cur_view.to_main_menu_id(); self.cur_view = View::new_main_menu(id); diff --git a/src/gui/chat.rs b/src/gui/chat.rs index 80e7afc2e4d0a57172c27ccdfec992606f226ea3..950bb19554221cb85ac7fdfe8fb66b9c875aecf7 100644 --- a/src/gui/chat.rs +++ b/src/gui/chat.rs @@ -1,44 +1,33 @@ -use core::fmt::Write; - use embedded_graphics::{ - mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, - pixelcolor::Rgb565, - prelude::{Point, RgbColor, Size}, + geometry::{Dimensions, Point, Size}, + mono_font::{ascii::FONT_10X20, MonoTextStyle}, + pixelcolor::{Rgb565, RgbColor}, primitives::{Primitive, PrimitiveStyleBuilder, Rectangle}, - text::{Baseline, Text}, + text::renderer::TextRenderer, Drawable, }; -use heapless::String; - -use crate::{app::WIDTH, message::MessageGroup}; +use embedded_text::{style::TextBoxStyleBuilder, TextBox}; -use super::{ - scroll_tracker::{ScrollAction, ScrollTracker}, - Element, +use crate::{ + app::WIDTH, + message::{MessageDirection, MessageGroup}, }; -const STATUS_BAR_HEIGHT: i32 = 16; -const GROUP_HEIGHT: i32 = 40; -const BORDER_WIDTH: u32 = 4; -const BACKGROUND_COLOR: Rgb565 = Rgb565::new(31, 41, 0); // #FFA500 -const BORDER_COLOR: Rgb565 = Rgb565::new(31, 26, 0); // 0xFF6600 +use super::{textbox::TextBox as MyTextBox, Element}; + +const MESSAGE_BOX_WIDTH: u32 = 200; +const MESSAGE_BOX_HEIGHT: u32 = 64; pub struct Chat { - groups: [MessageGroup; 3], - tracker: ScrollTracker, - selected: usize, + message_group: MessageGroup, + textbox: MyTextBox, } impl Chat { pub fn new() -> Self { Self { - groups: [ - MessageGroup::default(), - MessageGroup::default(), - MessageGroup::default(), - ], - tracker: ScrollTracker::new(), - selected: 0, + message_group: MessageGroup::default(), + textbox: MyTextBox::new(1, 218, 31, 1), } } } @@ -56,77 +45,68 @@ impl Element for Chat { &mut self, dt: &mut DT, ) -> Result<(), E> { - let unselected_rect_style = PrimitiveStyleBuilder::new() - .stroke_color(BORDER_COLOR) - .stroke_width(BORDER_WIDTH) - .fill_color(BACKGROUND_COLOR) - .build(); - - let selected_rect_style = PrimitiveStyleBuilder::new() - .stroke_color(BORDER_COLOR) - .stroke_width(BORDER_WIDTH) + 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(); - for (idx, group) in self.groups.iter().enumerate() { - let i = i32::try_from(idx).unwrap(); - - let x = 0; - let y = - i * (GROUP_HEIGHT + i32::try_from(BORDER_WIDTH).unwrap() * 2) + STATUS_BAR_HEIGHT; - - // Draw outer rectangle - let rect_style = if idx == self.selected { - selected_rect_style - } else { - unselected_rect_style - }; - - Rectangle::new( - Point::new(0, y), - Size::new( - u32::try_from(WIDTH).unwrap(), - u32::try_from(GROUP_HEIGHT).unwrap(), - ), - ) - .into_styled(rect_style) - .draw(dt)?; - - // Draw text - let text_x = x + i32::try_from(BORDER_WIDTH).unwrap(); - let text_y = y + i32::try_from(BORDER_WIDTH).unwrap(); - - let text_style = MonoTextStyleBuilder::new() - .font(&FONT_10X20) - .text_color(Rgb565::BLACK) - .build(); - - let mut text: String<25> = String::new(); - write!(&mut text, "{}", group).unwrap(); - Text::with_baseline(&text, Point::new(text_x, text_y), text_style, Baseline::Top) - .draw(dt)?; + let mut y = 28; + for msg in self.message_group.iter() { + 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(()) } - fn key_push(&mut self, _: crate::keyboard::KeyCode) { - // TODO need to change states or something + fn key_push(&mut self, k: crate::keyboard::KeyCode) { + self.textbox.key_push(k); } +} - fn touchpad_scroll(&mut self, _x: i8, y: i8) { - match self.tracker.scroll(y) { - ScrollAction::Previous => { - if self.selected > 0 { - self.selected -= 1; - } - } - ScrollAction::Next => { - if self.selected < self.groups.len() - 1 { - self.selected += 1; - } - } - ScrollAction::None => {} +fn calculate_bounding_box<'a, S: TextRenderer>(tb: &TextBox<'a, 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' || x >= CHAR_WIDTH * tb.bounds.size.width { + x = 0; + y += 1; + } else { + x += 1; + max_x = x; } } + + Rectangle::new( + tb.bounds.top_left, + Size::new(max_x * CHAR_WIDTH, y * CHAR_HEIGHT), + ) } diff --git a/src/gui/chat_list.rs b/src/gui/chat_list.rs new file mode 100644 index 0000000000000000000000000000000000000000..de2e236105dcf3076bf591d084d30c46e3b1fcef --- /dev/null +++ b/src/gui/chat_list.rs @@ -0,0 +1,129 @@ +use core::fmt::Write; + +use embedded_graphics::{ + mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, + pixelcolor::Rgb565, + prelude::{Point, RgbColor, Size}, + primitives::{Primitive, PrimitiveStyleBuilder, Rectangle}, + text::{Baseline, Text}, + Drawable, +}; +use heapless::String; + +use crate::{app::WIDTH, message::MessageGroup}; + +use super::{ + scroll_tracker::{ScrollAction, ScrollTracker}, + Element, +}; + +const STATUS_BAR_HEIGHT: i32 = 40; +const GROUP_HEIGHT: i32 = 40; +const BORDER_WIDTH: u32 = 4; +const BACKGROUND_COLOR: Rgb565 = Rgb565::new(31, 41, 0); // #FFA500 +const BORDER_COLOR: Rgb565 = Rgb565::new(31, 26, 0); // #FF6600 + +pub struct ChatList { + groups: [MessageGroup; 3], + tracker: ScrollTracker, + selected: usize, +} + +impl ChatList { + pub fn new() -> Self { + Self { + groups: [ + MessageGroup::default(), + MessageGroup::default(), + MessageGroup::default(), + ], + tracker: ScrollTracker::new(), + selected: 0, + } + } +} + +impl Element for ChatList { + type KeyPushReturn = (); + + fn render< + E, + DT: embedded_graphics::prelude::DrawTarget< + Color = embedded_graphics::pixelcolor::Rgb565, + Error = E, + >, + >( + &mut self, + dt: &mut DT, + ) -> Result<(), E> { + let unselected_rect_style = PrimitiveStyleBuilder::new() + .stroke_color(BORDER_COLOR) + .stroke_width(BORDER_WIDTH) + .fill_color(BACKGROUND_COLOR) + .build(); + + let selected_rect_style = PrimitiveStyleBuilder::new() + .stroke_color(BORDER_COLOR) + .stroke_width(BORDER_WIDTH) + .fill_color(Rgb565::WHITE) + .build(); + + for (idx, group) in self.groups.iter().enumerate() { + let i = i32::try_from(idx).unwrap(); + + let x = 0; + let y = i * (GROUP_HEIGHT + i32::try_from(BORDER_WIDTH).unwrap()) + STATUS_BAR_HEIGHT; + + // Draw outer rectangle + let rect_style = if idx == self.selected { + selected_rect_style + } else { + unselected_rect_style + }; + + Rectangle::new( + Point::new(0, y), + Size::new( + u32::try_from(WIDTH).unwrap(), + u32::try_from(GROUP_HEIGHT).unwrap(), + ), + ) + .into_styled(rect_style) + .draw(dt)?; + + // Draw text + let text_x = x + i32::try_from(BORDER_WIDTH).unwrap(); + let text_y = y + i32::try_from(BORDER_WIDTH).unwrap(); + + let text_style = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(Rgb565::BLACK) + .build(); + + let mut text: String<25> = String::new(); + write!(&mut text, "{}", group).unwrap(); + Text::with_baseline(&text, Point::new(text_x, text_y), text_style, Baseline::Top) + .draw(dt)?; + } + + Ok(()) + } + + fn key_push(&mut self, _: crate::keyboard::KeyCode) {} + + fn touchpad_scroll(&mut self, _x: i8, y: i8) { + match self.tracker.scroll(y) { + ScrollAction::Previous => { + if self.selected > 0 { + self.selected -= 1; + } + } + ScrollAction::Next => { + if self.selected < self.groups.len() - 1 { + self.selected += 1; + } + } + ScrollAction::None => {} + } + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index f37fdbbee8477c56c55a7505da7d355afdd9181c..ec4a5b10f3c31341611402569e195d0403a8f784 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -4,6 +4,7 @@ use crate::keyboard::KeyCode; pub mod button; pub mod chat; +pub mod chat_list; pub mod contact_view; pub mod scroll_tracker; pub mod selector; diff --git a/src/message.rs b/src/message.rs index 058dc38ebeb67e9e3b753b4660279f6d7909bcd5..111ac813b6b03956e618155298f639f6d15f1ddf 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,6 +1,6 @@ use core::fmt::Display; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub enum MessageDirection { From, To, @@ -8,8 +8,8 @@ pub enum MessageDirection { #[derive(Copy, Clone)] pub struct Message { - content: &'static str, - direction: MessageDirection, + pub content: &'static str, + pub direction: MessageDirection, } // A single column in the message list @@ -77,7 +77,7 @@ impl<'a> Iterator for MessageGroupIter<'a> { }, ]; - if self.i > stubbed.len() { + if self.i >= stubbed.len() { None } else { let out = stubbed[self.i];