#![cfg(feature = "osd")]
#[allow(unused)]
use num_traits::float::FloatCore;
use crate::{
config::SensorFlags,
display::{Display, DisplayPortLayer, DisplayPortSeverity},
osd::{
OsdConfig, OsdElementsConfig,
display::OsdDrawContext,
elements_draw::{OSD_ELEMENT_DISPLAY_ORDER, OsdElementId},
fixed_buf::FixedBuf,
},
};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum OsdElementType {
#[default]
Type1,
Type2,
Type3,
Type4,
}
impl OsdElementType {
fn from(v: u16) -> Self {
match v {
1 => Self::Type2,
2 => Self::Type3,
3 => Self::Type4,
_ => Self::Type1, }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum OsdStickOverlayRenderPhase {
#[default]
Vertical,
Horizontal,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum OsdStickCameraFrameRenderPhase {
#[default]
Top,
Middle,
Bottom,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OsdElementState {
pub horizon_x: i32,
pub stick_overlay_render_phase: OsdStickOverlayRenderPhase,
pub stick_overlay_y: u8,
pub sidebar_y: i8,
pub sidebar_render_level: bool,
pub camera_frame_render_phase: OsdStickCameraFrameRenderPhase,
pub camera_frame_i: u8,
pub rc_channel: u8,
}
impl Default for OsdElementState {
fn default() -> Self {
Self::new()
}
}
impl OsdElementState {
pub const fn new() -> Self {
Self {
horizon_x: -4,
stick_overlay_render_phase: OsdStickOverlayRenderPhase::Vertical,
stick_overlay_y: 0,
sidebar_y: 0,
sidebar_render_level: false,
camera_frame_render_phase: OsdStickCameraFrameRenderPhase::Top,
camera_frame_i: 0,
rc_channel: 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OsdElement {
pub fixed_buf: FixedBuf<{ OsdElement::BUFFER_LENGTH }>,
pub element_type: OsdElementType,
pub id: OsdElementId,
pub pos_x: u8,
pub pos_y: u8,
pub offset_x: u8,
pub offset_y: u8,
pub attr: DisplayPortSeverity,
pub rendered: bool,
pub draw_element: bool,
pub osd_cap_alarm: i16,
pub state: OsdElementState,
}
impl Default for OsdElement {
fn default() -> Self {
Self::new()
}
}
impl OsdElement {
pub const BUFFER_LENGTH: usize = 32;
pub const fn new() -> Self {
Self {
fixed_buf: FixedBuf::new(),
element_type: OsdElementType::Type1,
id: OsdElementId::Altitude,
pos_x: 0,
pos_y: 0,
offset_x: 0,
offset_y: 0,
attr: DisplayPortSeverity::Normal,
rendered: false,
draw_element: false,
osd_cap_alarm: 0,
state: OsdElementState::new(),
}
}
}
impl OsdElement {
pub fn write_string(&mut self, string: &str) {
let bytes = string.as_bytes();
self.write_slice(bytes);
}
pub fn write_slice(&mut self, slice: &[u8]) {
let len = slice.len().min(Self::BUFFER_LENGTH);
self.fixed_buf.bytes[..len].copy_from_slice(&slice[..len]);
self.fixed_buf.bytes[len..].fill(0);
}
}
#[allow(clippy::struct_field_names)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OsdElementsCache {
pub roll_angle_degrees: i32,
pub pitch_angle_degrees: i32,
pub yaw_angle_degrees: i32,
}
impl Default for OsdElementsCache {
fn default() -> Self {
Self::new()
}
}
impl OsdElementsCache {
pub const fn new() -> Self {
Self { roll_angle_degrees: 0, pitch_angle_degrees: 0, yaw_angle_degrees: 0 }
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OsdElements {
positions: [u16; OsdElements::COUNT],
current_element: OsdElement,
active_elements: [OsdElementId; Self::COUNT],
active_element_count: usize,
profile: u8,
background_rendered: bool,
display_pending_foreground: bool,
display_pending_background: bool,
background_layer_supported: bool,
cache: OsdElementsCache,
}
impl OsdElements {
pub const fn new(background_layer_supported: bool) -> Self {
Self {
positions: [0u16; OsdElements::COUNT],
current_element: OsdElement::new(),
active_elements: [OsdElementId::Rssi; Self::COUNT],
active_element_count: 0,
profile: 0,
background_rendered: false,
display_pending_foreground: false,
display_pending_background: false,
background_layer_supported,
cache: OsdElementsCache::new(),
}
}
}
#[rustfmt::skip]
impl OsdElements {
pub const COUNT: usize = 32;
pub const ELEMENT_BITS_POS: u16 = 14;
pub const PROFILE_BITS_POS: u16 = 12;
pub const XY_POSITION_BITS: u16 = 6; pub const ELEMENT_TYPE_MASK: u16 = 0b_1100_0000_0000_0000; pub const PROFILE_MASK: u16 = 0b_0011_0000_0000_0000;
pub const _Y_POSITION_MASK: u16 = 0b_0000_1111_1100_0000;
pub const X_POSITION_MASK: u16 = 0b_0000_0000_0011_1111;
}
impl OsdElements {
#[allow(unused)]
fn time_us() -> u32 {
0
}
pub fn element_type(x: u16) -> OsdElementType {
OsdElementType::from((x & Self::ELEMENT_TYPE_MASK) >> Self::ELEMENT_BITS_POS)
}
#[allow(unused)]
pub fn profile_flag(x: u16) -> u16 {
1 << (x - 1 + Self::PROFILE_BITS_POS)
}
#[allow(unused)]
pub fn set_profile(&mut self, profile: u8) {
self.profile = profile.clamp(0, 1);
}
pub fn element_visible(value: u16, profile: u8) -> bool {
((value & Self::PROFILE_MASK) >> Self::PROFILE_BITS_POS) & (1 << profile) == 0
}
pub fn pos_x(x: u16) -> u8 {
(x & Self::X_POSITION_MASK) as u8
}
#[allow(unused)]
pub fn pos_y(x: u16) -> u8 {
((x >> Self::XY_POSITION_BITS) & Self::X_POSITION_MASK) as u8
}
#[allow(unused)]
pub fn pos(x: u16, y: u16) -> u16 {
(x & Self::X_POSITION_MASK) | ((y & Self::X_POSITION_MASK) << Self::XY_POSITION_BITS)
}
#[allow(unused)]
pub fn set_positions(&mut self, config: OsdElementsConfig) {
self.positions = config.positions;
}
pub fn add_active_element(&mut self, element: OsdElementId) {
if Self::element_visible(self.positions[element as usize], self.profile) {
self.active_elements[self.active_element_count] = element;
self.active_element_count += 1;
}
}
pub fn set_current_element_by_index(&mut self, element_index: usize) -> bool {
if element_index > self.active_element_count || element_index >= self.positions.len() {
return false;
}
let element_id = self.active_elements[element_index];
self.set_current_element(element_id);
true
}
pub fn set_current_element(&mut self, element_id: OsdElementId) {
let position = self.positions[element_id as usize];
self.current_element = OsdElement {
element_type: Self::element_type(position),
id: element_id,
pos_x: Self::pos_x(position),
pos_y: Self::pos_y(position),
rendered: true,
draw_element: true,
..Default::default()
};
}
pub async fn draw_current_element<D: Display>(
&mut self,
draw_context: &OsdDrawContext,
display_port: &mut D,
osd_config: &OsdConfig,
) -> bool {
if !self.background_layer_supported && !self.background_rendered {
self.current_element.rendered = true;
let (drawn, rendered) =
Self::draw_element_background(display_port, &mut self.current_element, osd_config).await;
if drawn {
self.display_pending_background = true;
}
self.background_rendered = rendered;
}
self.current_element.rendered = true;
let (drawn, rendered) =
Self::draw_element_foreground(draw_context, &mut self.current_element, osd_config, self.cache).await;
if drawn {
self.display_pending_foreground = true;
}
rendered
}
pub fn display_current_element<D: Display>(&mut self, display_port: &mut D) -> bool {
if self.display_pending_background {
_ = display_port.write_slice(
self.current_element.pos_x + self.current_element.offset_x,
self.current_element.pos_y + self.current_element.offset_y,
&self.current_element.fixed_buf.bytes,
self.current_element.attr,
);
self.display_pending_background = false;
return self.display_pending_foreground;
}
if self.display_pending_foreground {
_ = display_port.write_slice(
self.current_element.pos_x + self.current_element.offset_x,
self.current_element.pos_y + self.current_element.offset_y,
&self.current_element.fixed_buf.bytes,
self.current_element.attr,
);
self.display_pending_foreground = false;
}
false
}
pub fn draw_spec(&self) -> bool {
_ = self;
true
}
pub async fn draw_background_for_all_active_elements<D: Display>(
&mut self,
display_port: &mut D,
osd_config: &OsdConfig,
) {
if self.background_layer_supported {
display_port.layer_select(DisplayPortLayer::Background);
for element_id in self.active_elements {
self.set_current_element(element_id);
let mut rendered = false;
while !rendered {
(_, rendered) =
Self::draw_element_background(display_port, &mut self.current_element, osd_config).await;
}
}
display_port.layer_select(DisplayPortLayer::Foreground);
}
}
pub fn add_active_elements(&mut self, sensors: SensorFlags) {
for element in OSD_ELEMENT_DISPLAY_ORDER {
self.add_active_element(*element);
}
_ = sensors;
#[cfg(feature = "gps")]
if sensors.is_set(SensorFlags::GPS) {
self.add_active_element(OsdElementId::GpsSats);
self.add_active_element(OsdElementId::GpsSpeed);
self.add_active_element(OsdElementId::GpsLat);
self.add_active_element(OsdElementId::GpsLon);
self.add_active_element(OsdElementId::HomeDistance);
self.add_active_element(OsdElementId::HomeDirection);
self.add_active_element(OsdElementId::FlightDistance);
self.add_active_element(OsdElementId::Efficiency);
}
}
#[allow(unused)]
pub fn analyze_active_elements<D: Display>(
&mut self,
sensors: SensorFlags,
display_port: &mut D,
osd_config: &OsdConfig,
) {
self.add_active_elements(sensors);
self.draw_background_for_all_active_elements(display_port, osd_config);
}
pub fn update_cache(&mut self, roll_angle_degrees: f32, pitch_angle_degrees: f32, yaw_angle_degrees: f32) {
#[allow(clippy::cast_possible_truncation)]
{
self.cache.roll_angle_degrees = (roll_angle_degrees + 0.5).floor() as i32;
self.cache.pitch_angle_degrees = (pitch_angle_degrees + 0.5).floor() as i32;
self.cache.yaw_angle_degrees = (yaw_angle_degrees + 0.5).floor() as i32;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn _is_normal<T: Sized + Send + Sync + Unpin>() {}
fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
#[test]
fn normal_types() {
is_full::<OsdElement>();
is_full::<OsdElementType>();
}
}