Add delta board

This commit is contained in:
hodasemi 2023-01-19 10:23:21 +01:00
parent 420b8fa914
commit 7cb07f1260
6 changed files with 365 additions and 213 deletions

View file

@ -0,0 +1,6 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<!-- max 5 entries, each entry gets 39 pixel (at 1440p) -->
<grid id="main_grid" x_dim="1" y_dim="5" x_offset="-410" y_offset="10" width="400" height="195"
vert_align="top" hori_align="right" margin="0" padding="0"> </grid>
</root>

View file

@ -0,0 +1,215 @@
use std::sync::Arc;
use anyhow::Result;
use ui::prelude::*;
use utilities::prelude::Color;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum BehindLeader {
Time(f64),
Laps(i32),
}
pub struct LeaderBoardEntry {
id: i32,
name: String,
place: u8,
behind: BehindLeader,
time_behind_next: f64,
best_lap: f64,
snippet: Arc<GuiSnippet>,
name_label: Arc<Label>,
place_label: Arc<Label>,
time_label: Arc<Label>,
place_updated: bool,
}
impl LeaderBoardEntry {
const ENTRY: &str = include_str!("leaderboard_entry.xml");
pub fn empty(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
Self::new(
gui_handler,
-1,
"".to_string(),
0,
BehindLeader::Laps(0),
0.0,
0.0,
)
}
pub fn new(
gui_handler: &Arc<GuiHandler>,
id: i32,
name: String,
place: u8,
behind: BehindLeader,
time_behind_next: f64,
best_lap: f64,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(gui_handler, Self::ENTRY)?;
let name_label: Arc<Label> = snippet.element("name")?;
let place_label: Arc<Label> = snippet.element("place")?;
let time_label: Arc<Label> = snippet.element("time")?;
name_label.set_text(&name)?;
place_label.set_text(place)?;
time_label.set_text("---")?;
Ok(Self {
id,
name,
place,
behind,
time_behind_next,
best_lap,
snippet,
name_label,
place_label,
time_label,
place_updated: true,
})
}
pub fn change_id(&mut self, id: i32) {
self.id = id;
}
pub fn id(&self) -> i32 {
self.id
}
pub fn place(&self) -> u8 {
self.place
}
pub fn name(&self) -> &str {
&self.name
}
pub fn change_name(&mut self, name: String) -> Result<()> {
self.name = name;
self.name_label.set_text(&self.name)
}
pub fn snippet(&self) -> Arc<GuiSnippet> {
self.snippet.clone()
}
pub fn change_background_color(&self, color: Color) -> Result<()> {
self.time_label.set_background(color)?;
self.name_label.set_background(color)?;
self.place_label.set_background(color)?;
Ok(())
}
pub fn _highlight(&self, color: Color) -> Result<()> {
self.name_label.set_background(color)
}
pub fn update_place(&mut self, place: u8) -> Result<()> {
if self.place != place {
self.place_updated = true;
}
self.place = place;
self.place_label.set_text(self.place)
}
pub fn update_time_behind_leader(&mut self, behind: BehindLeader) -> Result<()> {
if self.behind != behind {
self.behind = behind;
match self.behind {
BehindLeader::Time(time_behind) => {
// check if we are leader
if time_behind == 0.0 {
self.time_label.set_text("---")?;
} else {
let text = if time_behind > 60.0 {
let full_minutes = (self.best_lap / 60.0).floor();
let remainder = self.best_lap - (full_minutes * 60.0);
format!("+{:.0}:{:.0}", full_minutes, remainder)
} else {
format!("+{:.3}", time_behind)
};
self.time_label.set_text(text)?;
}
}
BehindLeader::Laps(laps_behind) => {
let text = if laps_behind == 1 {
format!("+{} Lap", laps_behind)
} else {
format!("+{} Laps", laps_behind)
};
self.time_label.set_text(text)?;
}
}
}
Ok(())
}
pub fn update_best_lap(&mut self, time: f64) -> Result<()> {
if self.best_lap != time {
self.best_lap = time;
if self.best_lap < 0.0 {
self.time_label.set_text("---")?;
} else {
let text = if self.best_lap > 60.0 {
let full_minutes = (self.best_lap / 60.0).floor();
let remainder = self.best_lap - (full_minutes * 60.0);
format!("{:.0}:{:.3}", full_minutes, remainder)
} else {
format!("{:.3}", self.best_lap)
};
self.time_label.set_text(text)?;
}
}
Ok(())
}
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
self.time_behind_next = time;
let text = if self.time_behind_next > 60.0 {
let full_minutes = (self.time_behind_next / 60.0).floor();
let remainder = self.time_behind_next - (full_minutes * 60.0);
format!("+{:.0}:{:.3}", full_minutes, remainder)
} else {
format!("+{:.3}", self.time_behind_next)
};
self.time_label.set_text(text)
}
pub fn needs_resorting(&self) -> bool {
self.place_updated
}
pub fn resorting_finished(&mut self) {
self.place_updated = false;
}
}

View file

@ -1,5 +1,6 @@
<?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?> <?xml-model href="../gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440"> <root reference_width="2560" reference_height="1440">
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="400" height="875" <!-- max 25 entries, each entry gets 39 pixel (at 1440p) -->
<grid id="main_grid" x_dim="1" y_dim="25" x_offset="10" y_offset="10" width="400" height="975"
vert_align="top" hori_align="left" margin="0" padding="0"> </grid> vert_align="top" hori_align="left" margin="0" padding="0"> </grid>
</root> </root>

View file

@ -1,3 +1,7 @@
mod leaderboard_entry;
use leaderboard_entry::*;
use std::{ use std::{
ffi::{c_char, CStr}, ffi::{c_char, CStr},
sync::Arc, sync::Arc,
@ -17,32 +21,53 @@ use crate::write_log;
pub struct LeaderBoard { pub struct LeaderBoard {
gui_handler: Arc<GuiHandler>, gui_handler: Arc<GuiHandler>,
leaderboard: Arc<GuiBuilder>, leaderboard: Arc<GuiBuilder>,
main_grid: Arc<Grid>, deltaboard: Arc<GuiBuilder>,
entries: Vec<LeaderBoardEntry>, leaderboard_grid: Arc<Grid>,
deltaboard_grid: Arc<Grid>,
leaderboard_entries: Vec<LeaderBoardEntry>,
deltaboard_entries: [LeaderBoardEntry; 5],
leaderboard_redraw: bool,
last_player_id: i32,
entry_backgrounds: [Color; 2], entry_backgrounds: [Color; 2],
player_background: Color, _player_background: Color,
} }
impl LeaderBoard { impl LeaderBoard {
const GRID: &str = include_str!("leaderboard_grid.xml"); const LEADERBOARD: &str = include_str!("leaderboard_grid.xml");
const ENTRY: &str = include_str!("leaderboard_entry.xml"); const DELTABOARD: &str = include_str!("deltaboard_grid.xml");
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> { pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?; let leaderboard = GuiBuilder::from_str(gui_handler, Self::LEADERBOARD)?;
let deltaboard = GuiBuilder::from_str(gui_handler, Self::DELTABOARD)?;
let main_grid = gui.element("main_grid")?; let leaderboard_grid = leaderboard.element("main_grid")?;
let deltaboard_grid = deltaboard.element("main_grid")?;
Ok(Self { Ok(Self {
gui_handler: gui_handler.clone(), gui_handler: gui_handler.clone(),
leaderboard: gui, leaderboard,
main_grid, deltaboard,
leaderboard_grid,
deltaboard_grid,
entries: Vec::new(), leaderboard_entries: Vec::new(),
deltaboard_entries: [
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
LeaderBoardEntry::empty(gui_handler)?,
],
leaderboard_redraw: false,
last_player_id: -1,
entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?], entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
player_background: Color::try_from("#b4bf26")?, _player_background: Color::try_from("#b4bf26")?,
}) })
} }
@ -66,7 +91,7 @@ impl LeaderBoard {
// check driver list // check driver list
match self match self
.entries .leaderboard_entries
.iter_mut() .iter_mut()
.find(|entry| vehicle_scoring.mID == entry.id()) .find(|entry| vehicle_scoring.mID == entry.id())
{ {
@ -98,7 +123,7 @@ impl LeaderBoard {
vehicle_scoring.mBestLapTime, vehicle_scoring.mBestLapTime,
)?; )?;
self.entries.push(entry); self.leaderboard_entries.push(entry);
} }
} }
} }
@ -106,14 +131,15 @@ impl LeaderBoard {
write_log!("create entries"); write_log!("create entries");
// check if entry count in grid is the same as the gathered entries // check if entry count in grid is the same as the gathered entries
let force_update = if !self.entries.is_empty() let force_update = if !self.leaderboard_entries.is_empty()
&& self && self
.main_grid .leaderboard_grid
.child_at(0, self.entries.len() - 1)? .child_at(0, self.leaderboard_entries.len() - 1)?
.is_none() .is_none()
{ {
for i in 0..self.entries.len() { for (i, entry) in self.leaderboard_entries.iter().enumerate() {
self.main_grid.detach(0, i)?; self.leaderboard_grid.detach(0, i)?;
entry.snippet().set_visibility(false)?;
} }
true true
@ -122,17 +148,80 @@ impl LeaderBoard {
}; };
// check if any entry needs resorting // check if any entry needs resorting
if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) { if force_update
|| self
.leaderboard_entries
.iter()
.any(|entry| entry.needs_resorting())
{
write_log!("leader board update required"); write_log!("leader board update required");
self.entries self.leaderboard_entries
.sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place())); .sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
for (i, entry) in self.entries.iter_mut().enumerate() { for (i, entry) in self.leaderboard_entries.iter_mut().enumerate() {
entry.resorting_finished(); entry.resorting_finished();
entry.change_background_color(self.entry_backgrounds[i % 2])?; entry.change_background_color(self.entry_backgrounds[i % 2])?;
self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?; self.leaderboard_grid.attach(entry.snippet(), 0, i, 1, 1)?;
}
self.leaderboard_redraw = true;
}
// update delta board
if self.last_player_id != -1 {
write_log!("update delta board");
if let Some((index, _player_entry)) = self
.leaderboard_entries
.iter()
.enumerate()
.find(|(_index, entry)| entry.id() == self.last_player_id)
{
let mut start_index = if index >= 2 { index - 2 } else { 0 };
let max = self.leaderboard_entries.len().min(5);
write_log!(format!(
"Delta Board: start {} - count {}",
start_index, max
));
// clear old entries
for i in 0..5 {
if let Some(child) = self.deltaboard_grid.detach(0, i)? {
if let Some(visiblity) = child.visibility() {
visiblity.set_visibility(false)?;
}
}
}
// add new entries
for i in 0..max {
if let Some(leaderboard_entry) = self.leaderboard_entries.get(start_index) {
if let Some(entry) = self.deltaboard_entries.get_mut(i) {
entry.change_id(leaderboard_entry.id());
entry.update_place(leaderboard_entry.place())?;
entry.change_background_color(self.entry_backgrounds[i % 2])?;
if entry.name() != leaderboard_entry.name() {
entry.change_name(leaderboard_entry.name().to_string())?;
}
if let Some(vehicle_scoring) = vehicle_scorings
.iter()
.find(|scoring| scoring.mID == entry.id())
{
entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
}
self.deltaboard_grid.attach(entry.snippet(), 0, i, 1, 1)?;
}
start_index += 1;
}
}
} }
} }
@ -164,21 +253,27 @@ impl DataReceiver for LeaderBoard {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> { fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase { match phase {
GamePhase::Practice | GamePhase::Qualifying => { GamePhase::Practice | GamePhase::Qualifying => {
for entry in self.entries.iter_mut() { for entry in self.leaderboard_entries.iter_mut() {
entry.update_best_lap(-1.0)?; entry.update_best_lap(-1.0)?;
} }
self.leaderboard.enable()? self.leaderboard.enable()?;
self.deltaboard.enable()?;
} }
GamePhase::Race => { GamePhase::Race => {
for entry in self.entries.iter_mut() { for entry in self.leaderboard_entries.iter_mut() {
entry.update_time_behind_leader(BehindLeader::Time(0.0))?; entry.update_time_behind_leader(BehindLeader::Time(0.0))?;
} }
self.leaderboard.enable()?; self.leaderboard.enable()?;
self.deltaboard.enable()?;
}
_ => {
self.last_player_id = -1;
self.leaderboard.disable()?;
self.deltaboard.disable()?;
} }
_ => self.leaderboard.disable()?,
} }
Ok(()) Ok(())
@ -200,10 +295,12 @@ impl DataReceiver for LeaderBoard {
match phase { match phase {
GamePhase::Practice | GamePhase::Qualifying => { GamePhase::Practice | GamePhase::Qualifying => {
self.quali_leaderboard(vehicle_scorings)? self.quali_leaderboard(vehicle_scorings)?;
} }
GamePhase::Race => self.race_leaderboard(vehicle_scorings)?, GamePhase::Race => {
self.race_leaderboard(vehicle_scorings)?;
}
_ => (), _ => (),
} }
@ -218,191 +315,24 @@ impl DataReceiver for LeaderBoard {
player_id: Option<i32>, player_id: Option<i32>,
_telemetries: &[rF2VehicleTelemetry], _telemetries: &[rF2VehicleTelemetry],
) -> Result<()> { ) -> Result<()> {
if self.leaderboard_redraw {
self.leaderboard_redraw = false;
// if let Some(player_id) = player_id {
// if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
// write_log!(format!(
// "Update player entry background color: {:?}",
// self.player_background
// ));
// entry.highlight(self.player_background)?;
// }
// }
}
if let Some(player_id) = player_id { if let Some(player_id) = player_id {
if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) { self.last_player_id = player_id;
entry.change_background_color(self.player_background)?;
}
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
enum BehindLeader {
Time(f64),
Laps(i32),
}
struct LeaderBoardEntry {
id: i32,
name: String,
place: u8,
behind: BehindLeader,
time_behind_next: f64,
best_lap: f64,
snippet: Arc<GuiSnippet>,
name_label: Arc<Label>,
place_label: Arc<Label>,
time_label: Arc<Label>,
place_updated: bool,
}
impl LeaderBoardEntry {
pub fn new(
gui_handler: &Arc<GuiHandler>,
id: i32,
name: String,
place: u8,
behind: BehindLeader,
time_behind_next: f64,
best_lap: f64,
) -> Result<Self> {
let snippet = GuiSnippet::from_str(gui_handler, LeaderBoard::ENTRY)?;
let name_label: Arc<Label> = snippet.element("name")?;
let place_label: Arc<Label> = snippet.element("place")?;
let time_label: Arc<Label> = snippet.element("time")?;
name_label.set_text(&name)?;
place_label.set_text(place)?;
time_label.set_text("---")?;
Ok(Self {
id,
name,
place,
behind,
time_behind_next,
best_lap,
snippet,
name_label,
place_label,
time_label,
place_updated: true,
})
}
pub fn id(&self) -> i32 {
self.id
}
pub fn place(&self) -> u8 {
self.place
}
pub fn name(&self) -> &str {
&self.name
}
pub fn change_name(&mut self, name: String) -> Result<()> {
self.name = name;
self.name_label.set_text(&self.name)
}
pub fn snippet(&self) -> Arc<GuiSnippet> {
self.snippet.clone()
}
pub fn change_background_color(&self, color: Color) -> Result<()> {
self.name_label.set_background(color)?;
self.place_label.set_background(color)?;
self.time_label.set_background(color)?;
Ok(())
}
pub fn update_place(&mut self, place: u8) -> Result<()> {
if self.place != place {
self.place_updated = true;
}
self.place = place;
self.place_label.set_text(self.place)
}
pub fn update_time_behind_leader(&mut self, behind: BehindLeader) -> Result<()> {
if self.behind != behind {
self.behind = behind;
match self.behind {
BehindLeader::Time(time_behind) => {
// check if we are leader
if time_behind == 0.0 {
self.time_label.set_text("---")?;
} else {
let text = if time_behind > 60.0 {
let full_minutes = (self.best_lap / 60.0).floor();
let remainder = self.best_lap - (full_minutes * 60.0);
format!("+{:.0}:{:.0}", full_minutes, remainder)
} else {
format!("+{:.3}", time_behind)
};
self.time_label.set_text(text)?;
}
}
BehindLeader::Laps(laps_behind) => {
let text = if laps_behind == 1 {
format!("+{} Lap", laps_behind)
} else {
format!("+{} Laps", laps_behind)
};
self.time_label.set_text(text)?;
}
}
}
Ok(())
}
pub fn update_best_lap(&mut self, time: f64) -> Result<()> {
if self.best_lap != time {
self.best_lap = time;
if self.best_lap < 0.0 {
self.time_label.set_text("---")?;
} else {
let text = if self.best_lap > 60.0 {
let full_minutes = (self.best_lap / 60.0).floor();
let remainder = self.best_lap - (full_minutes * 60.0);
format!("{:.0}:{:.3}", full_minutes, remainder)
} else {
format!("{:.3}", self.best_lap)
};
self.time_label.set_text(text)?;
}
}
Ok(())
}
pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
self.time_behind_next = time;
self.time_label
.set_text(format!("+{:.3}", self.time_behind_next))
}
pub fn needs_resorting(&self) -> bool {
self.place_updated
}
pub fn resorting_finished(&mut self) {
self.place_updated = false;
}
}

View file

@ -237,7 +237,7 @@ impl UiOverlay for Pedals {}
impl DataReceiver for Pedals { impl DataReceiver for Pedals {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> { fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase { match phase {
GamePhase::None => { GamePhase::None | GamePhase::TestDay => {
self.enable = false; self.enable = false;
self.gui.disable()?; self.gui.disable()?;
} }

View file

@ -274,7 +274,7 @@ impl UiOverlay for Radar {}
impl DataReceiver for Radar { impl DataReceiver for Radar {
fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> { fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
match phase { match phase {
GamePhase::None => self.enable = false, GamePhase::None | GamePhase::TestDay => self.enable = false,
_ => self.enable = true, _ => self.enable = true,
} }