use std::{ ffi::{c_char, CStr}, sync::Arc, }; use anyhow::Result; use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01}; use ui::prelude::*; use utilities::prelude::Color; use crate::overlay::{rfactor_data::DataReceiver, UiOverlay}; pub struct LeaderBoard { gui_handler: Arc, gui: Arc, main_grid: Arc, entries: Vec, entry_backgrounds: [Color; 2], } impl LeaderBoard { const GRID: &str = include_str!("ui_files/leaderboard_grid.xml"); const ENTRY: &str = include_str!("ui_files/leaderboard_entry.xml"); pub fn new(gui_handler: &Arc) -> Result { let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?; let main_grid = gui.element("main_grid")?; Ok(Self { gui_handler: gui_handler.clone(), gui, main_grid, entries: Vec::new(), entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?], }) } } impl UiOverlay for LeaderBoard {} impl DataReceiver for LeaderBoard { fn scoring_update(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> { for vehicle_scoring in vehicle_scorings { // check driver list match self .entries .iter_mut() .find(|entry| vehicle_scoring.mID == entry.id) { Some(entry) => { entry.update_place(vehicle_scoring.mPlace)?; entry.update_time_behind_leader(vehicle_scoring.mTimeBehindLeader)?; entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?; } None => { let entry = LeaderBoardEntry::new( &self.gui_handler, vehicle_scoring.mID, unsafe { CStr::from_ptr(&vehicle_scoring.mDriverName as *const c_char) } .to_str() .unwrap() .to_string(), vehicle_scoring.mPlace, vehicle_scoring.mTimeBehindLeader, vehicle_scoring.mTimeBehindNext, )?; self.entries.push(entry); } } } // check if entry count in grid is the same as the gathered entries let force_update = if self .main_grid .child_at(0, self.entries.len() - 1)? .is_none() { for i in 0..self.entries.len() { self.main_grid.detach(0, i)?; } true } else { false }; // check if any entry needs resorting if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) { self.entries .sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place())); for (i, entry) in self.entries.iter_mut().enumerate() { entry.resorting_finished(); entry.change_background_color(self.entry_backgrounds[i % 2])?; self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?; } } if self.entries.is_empty() { self.gui.disable()?; } else { self.gui.enable()?; } Ok(()) } fn telemetry_update( &mut self, _player_id: Option, _telemetries: &[rF2VehicleTelemetry], ) -> Result<()> { Ok(()) } } struct LeaderBoardEntry { id: i32, name: String, place: u8, time_behind_leader: f64, time_behind_next: f64, snippet: Arc, grid: Arc, name_label: Arc