Merge initial implementation of Pedal and Leader Board widgets #3

Merged
hodasemi merged 16 commits from ui into master 2023-01-18 11:50:37 +00:00
11 changed files with 191 additions and 75 deletions
Showing only changes of commit cab4701289 - Show all commits

5
.vscode/tasks.json vendored
View file

@ -4,11 +4,14 @@
{
"type": "cargo",
"command": "build",
"args": [
"--release"
],
"problemMatcher": [
"$rustc"
],
"group": "build",
"label": "rust: cargo build --release"
"label": "rust: cargo build"
},
{
"type": "shell",

BIN
font.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View file

@ -1,6 +1,8 @@
mod pedals;
mod pipeline;
mod radar;
mod watermark;
pub use pedals::*;
pub use radar::*;
pub use watermark::*;

View file

@ -11,6 +11,8 @@ pub struct Pedals {
brake: Arc<ProgressBar>,
throttle: Arc<ProgressBar>,
fuel: Arc<Label>,
gear: Arc<Label>,
}
impl Pedals {
@ -21,11 +23,15 @@ impl Pedals {
let brake = gui.element("brake")?;
let throttle = gui.element("throttle")?;
let fuel = gui.element("fuel")?;
let gear = gui.element("gear")?;
Ok(Self {
gui,
brake,
throttle,
fuel,
gear,
})
}
}
@ -36,11 +42,7 @@ impl Drop for Pedals {
}
}
impl UiOverlay for Pedals {
fn enable_ui(&mut self) -> Result<()> {
self.gui.enable()
}
}
impl UiOverlay for Pedals {}
impl DataReceiver for Pedals {
fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> {
@ -52,12 +54,22 @@ impl DataReceiver for Pedals {
player_id: Option<i32>,
telemetries: &[rF2VehicleTelemetry],
) -> Result<()> {
if let Some(id) = player_id {
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
self.brake
.set_progress(telemetry.unfiltered_throttle as f32)?;
self.throttle
.set_progress(telemetry.unfiltered_throttle as f32)?;
match player_id {
Some(id) => {
self.gui.enable()?;
if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) {
self.brake
.set_progress(telemetry.unfiltered_throttle as f32)?;
self.throttle
.set_progress(telemetry.unfiltered_throttle as f32)?;
self.gear.set_text(telemetry.gear)?;
let fuel = telemetry.fuel;
self.fuel.set_text(format!("{:.2}", fuel))?;
}
}
None => {
self.gui.disable()?;
}
}

View file

@ -92,9 +92,6 @@ pub struct Radar {
// buffer car objects, to prevent recreating them every update
car_handles: Vec<RadarObject>,
// game info
player_id: Option<i32>,
// math objects
radar_center: Vector2<f32>,
ortho: Matrix4<f32>,
@ -109,8 +106,6 @@ pub struct Radar {
pipeline: SingleColorPipeline,
render_target: RenderTarget,
enabled: bool,
}
impl Radar {
@ -120,8 +115,6 @@ impl Radar {
queue: Arc<Mutex<Queue>>,
rendering: &Rendering,
) -> Result<Self> {
write_log!(" =================== create RFactorData ===================");
let radar_extent = rendering.swapchain().width() as f32 * 0.075 * config.radar_scale;
let car_height = radar_extent * config.height_scale;
let car_width = car_height * config.width_scale;
@ -198,8 +191,6 @@ impl Radar {
cars: Vec::new(),
car_handles: Vec::new(),
player_id: None,
radar_center,
ortho,
_window_width: rendering.swapchain().width(),
@ -213,8 +204,6 @@ impl Radar {
render_target,
pipeline,
enabled: false,
})
}
@ -285,33 +274,26 @@ impl Radar {
let mut objects = Vec::new();
// only draw radar when player is loaded into a map
if let Some(_player_id) = &self.player_id {
// only draw radar when any car is near enough
if !self.cars.is_empty() {
if let Some(background) = &self.background {
objects.push(background);
}
for other_player_cars in &self.cars {
objects.push(other_player_cars);
}
objects.push(&self.player_car);
// only draw radar when any car is near enough
if !self.cars.is_empty() {
if let Some(background) = &self.background {
objects.push(background);
}
for other_player_cars in &self.cars {
objects.push(other_player_cars);
}
objects.push(&self.player_car);
}
write_log!(format!("obj count {}", objects.len()));
objects
}
}
impl UiOverlay for Radar {
fn enable_ui(&mut self) -> Result<()> {
self.enabled = true;
Ok(())
}
}
impl UiOverlay for Radar {}
impl DataReceiver for Radar {
fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> {
@ -323,11 +305,10 @@ impl DataReceiver for Radar {
player_id: Option<i32>,
telemetries: &[rF2VehicleTelemetry],
) -> Result<()> {
self.cars.clear();
write_log!(" ============================ Radar telemetry udpate ======================");
write_log!(format!("player id {:?}", player_id));
if !self.enabled {
return Ok(());
}
self.cars.clear();
if let Some(player_id) = player_id {
// make sure there are enough cars in buffer
@ -389,6 +370,8 @@ impl DataReceiver for Radar {
}
}
write_log!(format!("other cars: {:?}", self.cars.len()));
Ok(())
}
}

View file

@ -61,6 +61,14 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="direction">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="left_to_right"></xs:enumeration>
<xs:enumeration value="bottom_to_top"></xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="background" type="xs:string"></xs:attribute>
<xs:attribute name="foreground" type="xs:string"></xs:attribute>
<xs:attribute name="button_normal" type="xs:string"></xs:attribute>
@ -200,6 +208,7 @@
<xs:attribute ref="text_alignment"></xs:attribute>
<xs:attribute ref="background"></xs:attribute>
<xs:attribute ref="foreground"></xs:attribute>
<xs:attribute ref="direction"></xs:attribute>
</xs:complexType>
</xs:element>

View file

@ -1,11 +1,16 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<grid x_dim="5" y_dim="2" x_offset="-600" y_offset="-350" width="250" height="300"
vert_align="bottom" hori_align="right" margin="10" padding="10" background="#686868">
<grid x_dim="7" y_dim="2" x_offset="-750" y_offset="-250" width="150" height="200"
vert_align="bottom" hori_align="right" margin="3" padding="3" background="#686868">
<progressbar id="brake" x_slot="0" y_slot="0" y_size="2" background="#f44444"
foreground="#e30000"></progressbar>
<progressbar id="throttle" x_slot="1" y_slot="0"
y_size="2" background="#51fd51"
direction="bottom_to_top" foreground="#e30000"></progressbar>
<progressbar id="throttle"
x_slot="1" y_slot="0" y_size="2" background="#51fd51" direction="bottom_to_top"
foreground="#00b900"></progressbar>
<label id="fuel" x_slot="2"
x_size="5" y_slot="0"
text_color="black"></label>
<label id="gear" x_slot="2" x_size="5" y_slot="1"
text_color="black"></label>
</grid>
</root>

View file

@ -0,0 +1,8 @@
<?xml-model href="gui.xsd" type="application/xml" schematypens="http://www.w3.org/2001/XMLSchema"?>
<root reference_width="2560" reference_height="1440">
<grid x_dim="1" y_dim="1" x_offset="10" y_offset="10" width="300" height="50"
vert_align="top" hori_align="left" margin="2" padding="2" background="#c9c9c9">
<label x_slot="0" y_slot="0" text_color="#c9c9c9" text_ratio="1.0"
background="black">Vulkan Overlay Enabled</label>
</grid>
</root>

View file

@ -0,0 +1,45 @@
use std::sync::Arc;
use anyhow::Result;
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
use ui::prelude::*;
use crate::overlay::{rfactor_data::DataReceiver, UiOverlay};
pub struct Watermark {
gui: Arc<GuiBuilder>,
}
impl Watermark {
pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
const DESC: &str = include_str!("ui_files/watermark.xml");
let gui = GuiBuilder::from_str(gui_handler, DESC)?;
gui.enable()?;
Ok(Self { gui })
}
}
impl Drop for Watermark {
fn drop(&mut self) {
self.gui.disable().unwrap()
}
}
impl UiOverlay for Watermark {}
impl DataReceiver for Watermark {
fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> {
Ok(())
}
fn telemetry_update(
&mut self,
_player_id: Option<i32>,
_telemetries: &[rF2VehicleTelemetry],
) -> Result<()> {
Ok(())
}
}

View file

@ -23,12 +23,30 @@ use elements::*;
use serde::{Deserialize, Serialize};
pub trait UiOverlay: DataReceiver {
fn enable_ui(&mut self) -> Result<()>;
pub trait UiOverlay: DataReceiver {}
#[derive(Deserialize, Serialize)]
pub struct UiSelectorConfig {
pub enable_watermark: bool,
pub enable_radar: bool,
pub enable_pedals: bool,
}
impl UiSelectorConfig {
pub const fn new() -> Self {
Self {
enable_watermark: true,
enable_radar: true,
enable_pedals: true,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct OverlayConfig {
pub ui_config: UiSelectorConfig,
pub radar_config: RadarConfig,
pub font_path: String,
}
@ -36,6 +54,7 @@ pub struct OverlayConfig {
impl OverlayConfig {
pub const fn new() -> Self {
Self {
ui_config: UiSelectorConfig::new(),
radar_config: RadarConfig::new(),
font_path: String::new(),
}
@ -74,6 +93,8 @@ impl Overlay {
pub fn set_config(&mut self, config: OverlayConfig) {
self.config = config;
self.config.font_path = "/opt/sata_ssd/Workspace/vk_layer_rs/font.png".to_string();
}
pub fn set_instance(&mut self, instance: Arc<Instance>) {
@ -123,8 +144,12 @@ impl Overlay {
// only font is used
let mut create_info = GuiHandlerCreateInfo::default();
create_info.font_path = AssetPath::from(self.config.font_path.clone());
// create_info.font_path = AssetPath::from(self.config.font_path.clone());
create_info.font_path = AssetPath::from("/opt/sata_ssd/Workspace/vk_layer_rs/font.png");
create_info.font_path.assume_prefix_free();
// required to not crash
create_info.resource_directory = AssetPath::from("");
create_info.resource_directory.assume_prefix_free();
// provide trait required by GuiHandler
let ctx = Arc::new(ContextImpl::new(
@ -135,28 +160,51 @@ impl Overlay {
));
// create GuiHandler
write_log!(format!("Create Info: \n{:#?}", create_info));
let gui_handler = GuiHandler::new(create_info, &(ctx as Arc<dyn ContextInterface>))?;
write_log!("GuiHandler successfully created");
// create ui elements
// create watermark
if self.config.ui_config.enable_watermark {
let watermark = Rc::new(RefCell::new(Watermark::new(&gui_handler)?));
self.ui_elements.push(watermark);
write_log!("Watermark successfully created");
}
// create radar
let radar = Rc::new(RefCell::new(Radar::new(
self.config.radar_config,
self.device(),
self.queue(),
&rendering,
)?));
if self.config.ui_config.enable_radar {
let radar = Rc::new(RefCell::new(Radar::new(
self.config.radar_config,
self.device(),
self.queue(),
&rendering,
)?));
rendering.add_render_callback({
let radar = radar.clone();
move |index| radar.borrow().render(index)
});
self.ui_elements.push(radar);
write_log!("Radar successfully created");
}
// create pedals
let pedals = Rc::new(RefCell::new(Pedals::new(&gui_handler)?));
if self.config.ui_config.enable_pedals {
let pedals = Rc::new(RefCell::new(Pedals::new(&gui_handler)?));
self.ui_elements.push(pedals);
write_log!("Pedals successfully created");
}
// add rendering callbacks
rendering.add_render_callback({
let radar = radar.clone();
move |index| radar.borrow().render(index)
});
rendering.add_render_callback({
let gui_handler = gui_handler.clone();
let device = self.device();
@ -179,15 +227,13 @@ impl Overlay {
}
});
// add ui elements to local list
self.ui_elements.push(radar);
self.ui_elements.push(pedals);
write_log!("-> create rendering: end");
write_log!("render callbacks added");
self.rendering = Some(rendering);
self.gui_handler = Some(gui_handler);
write_log!("-> create rendering: end");
Ok(())
}
@ -199,13 +245,11 @@ impl Overlay {
write_log!("created RFactorData");
for receiver in self.ui_elements.iter() {
receiver.borrow_mut().enable_ui()?;
data.add_receiver(receiver.clone());
}
}
}
// check twice for rfactor data, because of borrowing rules
if let Some(rfactor) = &mut self.rfactor_data {
rfactor.update()?;
}

View file

@ -72,6 +72,11 @@ impl Rendering {
.map(|c| c(image_index))
.collect::<Result<Vec<Arc<CommandBuffer>>>>()?;
write_log!(format!(
"submitting {} commandbuffer(s)",
command_buffers.len()
));
let queue = self.queue.lock().unwrap();
queue.minimal_submit(Duration::from_secs(10), &command_buffers)?;