Add delta board
This commit is contained in:
parent
420b8fa914
commit
7cb07f1260
6 changed files with 365 additions and 213 deletions
6
src/overlay/elements/leaderboard/deltaboard_grid.xml
Normal file
6
src/overlay/elements/leaderboard/deltaboard_grid.xml
Normal 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>
|
215
src/overlay/elements/leaderboard/leaderboard_entry.rs
Normal file
215
src/overlay/elements/leaderboard/leaderboard_entry.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue