pub mod structs; use std::{cmp::min}; use log::{error, warn}; use sdl3::{Sdl, pixels::Color, rect::Rect}; use structs::{Prefs, DisplayData}; use crate::renderer::structs::Screen; const LINE_COUNT: i32 = 5; const COLOR_LCD_AMBER : Color = Color::RGB(0xf4, 0xcb, 0x60); const COLOR_LCD_GREEN : Color = Color::RGB(0xb3, 0xff, 0x00); const COLOR_LCD_RED : Color = Color::RGB(0xff, 0x3a, 0x4a); const COLOR_BACKGROUND : Color = Color::RGB(0x0, 0x0, 0x0 ); const COLOR_TEXT_BG : Color = Color::RGBA(0x0, 0x0, 0x0, 0x0); const TEXT_SIZE: f32 = 160.0; // Size of the font in pixels // Offsets of each part within a line const XOFFSET_ROUTE: u32 = 24; const XOFFSET_DESTINATION: u32 = 300; const XOFFSEET_TIME_LEFT: u32 = 1606; const INTER_LINE_OVERLAP: u32 = 15; impl Screen<'_> { pub fn get_context(&self) -> &Sdl { return &self.context; } fn color_for(&self, due_in: i32) -> Color { if due_in > 15 { return COLOR_LCD_GREEN }; if due_in > 10 { return COLOR_LCD_AMBER }; return COLOR_LCD_RED; } fn format_due_for(&self, due_in: i32, departure_time: u32) -> String { if due_in < 60 { return String::from("due"); } if due_in < 3600 { return ((due_in / 60) as i32).to_string() + "min"; } return format!("{:02}:{:02}", (departure_time / 3600) as i32, ((departure_time % 3600) / 60) as i32); } pub fn update_information(&mut self, display_data: &DisplayData) { self.do_clear(); // Print status first if display_data.status.is_some() { // If the update has some information text, show it self.do_print_at(5, display_data.status.as_ref().unwrap(), 0); } else { // Display date and time otherwise self.do_print_at(5, &format!("TODO: DATE/TIME GOES HERE").to_string(), 0); } // Then data lines from the bottom up let num_arrivals: i32 = min(LINE_COUNT, display_data.lines.len() as i32); for index in 0..num_arrivals { let line: u32 = ((num_arrivals - 1) - index) as u32; // Compose a line of text with all the information let entry = display_data.lines.get(line as usize).unwrap(); let due_in_mins = (entry.departure_time / 60) as i32; let due_color: Color = self.color_for(due_in_mins); let due_text = self.format_due_for(due_in_mins, entry.departure_time); self.color = COLOR_LCD_AMBER; self.do_print_at(line, &entry.route, XOFFSET_ROUTE); self.do_print_at(line, &entry.destination, XOFFSET_DESTINATION); self.color = due_color; self.do_print_at(line, &due_text, XOFFSEET_TIME_LEFT); }; self.do_update(); } pub fn _print(&mut self, line: u32, text: &str) { self.do_print_at(line, text, 0); self.do_update(); } fn do_print_at(&mut self, line: u32, text: &str, left: u32) { if text.len() == 0 { warn!("do_print_at called with a 0-length string"); return; } let render_result = self.font.render(text).lcd(self.color, COLOR_BACKGROUND); if render_result.is_err() { error!("Error rendering text \"{}\": {:#?}", text, render_result.err()); return; } let rendered_text = render_result.unwrap(); let texture_creator = self.canvas.texture_creator(); let texture = rendered_text.as_texture(&texture_creator); if texture.is_err() { error!("Error creating texture from rendered text: {:#?}", texture.err()); return; } let _= self.canvas.copy(&texture.unwrap(), Rect::new(0, 0, rendered_text.width(), rendered_text.height()), Rect::new(left.try_into().unwrap(), (line * (rendered_text.height() - INTER_LINE_OVERLAP)).try_into().unwrap(), rendered_text.width(), rendered_text.height())); } pub fn clear(&mut self) { self.do_clear(); self.do_update(); } fn do_update (&mut self) { self.canvas.present(); } fn do_clear(&mut self) { self.canvas.set_draw_color(COLOR_BACKGROUND); self.canvas.clear(); } /// Initialize video, allocate buffers and load fonts /// Based on https://github.com/vhspace/sdl3-rs/blob/master/examples/ttf-demo.rs pub fn init(prefs: &Prefs) -> Screen<'_> { // Initialize the screen let sdl_context = sdl3::init().unwrap(); let video_subsys = sdl_context.video().unwrap(); let window = video_subsys .window("Dublin Bus", prefs.screen_width, prefs.screen_height) .position_centered() .borderless() //.fullscreen() .build().unwrap(); // Load font let ttf_context = sdl3::ttf::init().unwrap(); let font = ttf_context.load_font(&prefs.font_path, TEXT_SIZE).unwrap(); let mut screen: Screen = Screen { canvas: Box::new(window.into_canvas()), font: Box::new(font), color: COLOR_LCD_AMBER, context: Box::new(sdl_context), }; screen.clear(); return screen; } }