rs-dublinbus/src/renderer/mod.rs

165 lines
5.6 KiB
Rust

pub mod structs;
use std::{cmp::min};
use chrono::{Datelike, Local, Timelike};
use log::{error, trace, warn};
use sdl3::{Sdl, pixels::Color, rect::Rect};
use structs::{Prefs, DisplayData};
use crate::{config::Error, 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_mins: i32, departure_time: u32) -> String {
trace!("Due in mins: {:02}", due_in_mins);
if due_in_mins <= 1 {
return String::from("due");
}
if due_in_mins < 60 {
return due_in_mins.to_string() + "min";
}
return format!("{:02}:{:02}", (departure_time / 3600) as i32, ((departure_time / 60) % 60) as i32);
}
pub fn update_information(&mut self, display_data: &DisplayData) {
let local_time = Local::now();
let seconds_since_midnight = local_time.num_seconds_from_midnight();
self.do_clear();
// Print status first
self.color = COLOR_LCD_AMBER;
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!("Current time: {:02}/{:02}/{:4} {:02}:{:02}",
local_time.day(), local_time.month(), local_time.year(),
local_time.hour(), local_time.minute()
).to_string(), 0);
}
// Then data lines from the bottom
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 - seconds_since_midnight) / 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) -> Result<Screen<'_>, Error> {
// Initialize the screen
let sdl_context = sdl3::init().unwrap();
let video_subsys = sdl_context.video()?;
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)?;
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 Ok(screen);
}
}