diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs index 95b9b932c42ba5785e1040600f7b28174266a454..3d91ce0deaa0e06a8cc3d22558988dd4071cbc6b 100644 --- a/src/ui/panes/pid_drawing_tool.rs +++ b/src/ui/panes/pid_drawing_tool.rs @@ -113,7 +113,7 @@ impl PaneBehavior for PidPane { if let Some(end) = self.find_hovered_element_idx(&pointer_pos) { if response.clicked() { if start != end { - self.connections.push(Connection::new(start, end)); + self.connections.push(Connection::new(start, 0, end, 0)); println!("Added connection from {} to {}", start, end); } self.action.take(); @@ -243,8 +243,9 @@ impl PidPane { let mut points = Vec::new(); // Append start point - let start = &self.elements[connection.start]; - points.push(start.position.into_pos2(&self.grid)); + points.push( + self.elements[connection.start].get_ancor_point(&self.grid, connection.start_ancor), + ); // Append all midpoints connection @@ -254,8 +255,9 @@ impl PidPane { .for_each(|p| points.push(p)); // Append end point - let end = &self.elements[connection.end]; - points.push(end.position.into_pos2(&self.grid)); + points.push( + self.elements[connection.end].get_ancor_point(&self.grid, connection.end_ancor), + ); // Draw line segments for i in 0..(points.len() - 1) { @@ -299,7 +301,7 @@ impl PidPane { } fn draw_context_menu(&mut self, pointer_pos: &Pos2, ui: &mut Ui) { - ui.set_max_width(100.0); // To make sure we wrap long text + ui.set_max_width(120.0); // To make sure we wrap long text if self.is_hovering_element(&pointer_pos) { let hovered_element = self.find_hovered_element_idx(&pointer_pos); @@ -342,6 +344,18 @@ impl PidPane { self.connections[conn_idx].split(segm_idx, Pos::from_pos2(&self.grid, pointer_pos)); ui.close_menu(); } + if ui.button("Change start ancor").clicked() { + let conn = &mut self.connections[conn_idx]; + conn.start_ancor = (conn.start_ancor + 1) + % self.elements[conn.start].symbol.get_ancor_points().len(); + ui.close_menu(); + } + if ui.button("Change end ancor").clicked() { + let conn = &mut self.connections[conn_idx]; + conn.end_ancor = + (conn.end_ancor + 1) % self.elements[conn.end].symbol.get_ancor_points().len(); + ui.close_menu(); + } } else { ui.menu_button("Symbols", |ui| { for symbol in Symbol::iter() { diff --git a/src/ui/panes/pid_drawing_tool/connections.rs b/src/ui/panes/pid_drawing_tool/connections.rs index e1c9a3b9f3fa1e72c3e74e5003f9de828b794113..43a79ad18ef733c2370b1f9360da57619c9b4ebb 100644 --- a/src/ui/panes/pid_drawing_tool/connections.rs +++ b/src/ui/panes/pid_drawing_tool/connections.rs @@ -7,19 +7,23 @@ use super::{grid::LINE_DISTANCE_THRESHOLD, pos::Pos, PidPane}; pub struct Connection { /// Index of the start element pub start: usize, + pub start_ancor: usize, /// Index of the end element pub end: usize, + pub end_ancor: usize, /// Coordinates of middle points pub middle_points: Vec<Pos>, } impl Connection { - pub fn new(start: usize, end: usize) -> Self { + pub fn new(start: usize, start_ancor: usize, end: usize, end_ancor: usize) -> Self { Self { start, + start_ancor, end, + end_ancor, middle_points: Vec::new(), } } @@ -29,8 +33,7 @@ impl Connection { let mut points = Vec::new(); // Append start point - let start = &pid.elements[self.start]; - points.push(start.position.into_pos2(&pid.grid)); + points.push(pid.elements[self.start].get_ancor_point(&pid.grid, self.start_ancor)); // Append all midpoints self.middle_points @@ -39,8 +42,7 @@ impl Connection { .for_each(|p| points.push(p)); // Append end point - let end = &pid.elements[self.end]; - points.push(end.position.into_pos2(&pid.grid)); + points.push(pid.elements[self.end].get_ancor_point(&pid.grid, self.end_ancor)); // Check each segment for i in 0..(points.len() - 1) { diff --git a/src/ui/panes/pid_drawing_tool/elements.rs b/src/ui/panes/pid_drawing_tool/elements.rs index 97f11363b7d26d088d442b05c5d663a71f433f7b..9b7fdbc805df04c339bffc446fcdaa2691477603 100644 --- a/src/ui/panes/pid_drawing_tool/elements.rs +++ b/src/ui/panes/pid_drawing_tool/elements.rs @@ -1,6 +1,6 @@ use super::symbols::Symbol; use super::{grid::GridInfo, pos::Pos}; -use egui::Pos2; +use egui::{Pos2, Vec2}; use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize, PartialEq)] @@ -34,4 +34,11 @@ impl Element { (start.x <= pos.x && pos.x < end.x) && (start.y <= pos.y && pos.y < end.y) } + + pub fn get_ancor_point(&self, grid: &GridInfo, idx: usize) -> Pos2 { + let ancor = self.symbol.get_ancor_points()[idx]; + let ancor = Vec2::from(ancor) * self.size as f32 * grid.size; + + self.position.into_pos2(grid) + ancor + } } diff --git a/src/ui/panes/pid_drawing_tool/symbols.rs b/src/ui/panes/pid_drawing_tool/symbols.rs new file mode 100644 index 0000000000000000000000000000000000000000..1b7b62849f91845bb4feb3f2e5c2e8f7a4779270 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols.rs @@ -0,0 +1,52 @@ +use egui::{ImageSource, Theme}; +use serde::{Deserialize, Serialize}; +use strum_macros::{Display, EnumIter}; + +#[derive(Clone, Serialize, Deserialize, PartialEq, EnumIter, Display)] +pub enum Symbol { + ManualValve, + CheckValve, + // ReliefValve, + // ControlValve, + // PressureRegulator, + // BurstDisk, + // QuickConnector, + // PressureTransducer, + // PressureGauge, + // FlexibleConnection, + // ThreeWayValve, + PressurizedVessel, +} + +impl Symbol { + pub fn get_image(&self, theme: Theme) -> ImageSource { + match (&self, theme) { + (Symbol::ManualValve, Theme::Light) => { + egui::include_image!("../../../../icons/ball_valve_light.svg") + } + (Symbol::ManualValve, Theme::Dark) => { + egui::include_image!("../../../../icons/ball_valve_dark.svg") + } + (Symbol::CheckValve, Theme::Light) => { + egui::include_image!("../../../../icons/check_valve_light.svg") + } + (Symbol::CheckValve, Theme::Dark) => { + egui::include_image!("../../../../icons/check_valve_dark.svg") + } + (Symbol::PressurizedVessel, Theme::Light) => { + egui::include_image!("../../../../icons/pressurized_vessel_light.svg") + } + (Symbol::PressurizedVessel, Theme::Dark) => { + egui::include_image!("../../../../icons/pressurized_vessel_dark.svg") + } + } + } + + pub fn get_ancor_points(&self) -> Vec<(f32, f32)> { + match self { + Symbol::ManualValve => [(-0.5, 0.0), (0.5, 0.0)].into(), + Symbol::CheckValve => [(-0.5, 0.0), (0.5, 0.0)].into(), + Symbol::PressurizedVessel => [(0.0, -0.5), (0.0, 0.5)].into(), + } + } +} diff --git a/src/ui/panes/pid_drawing_tool/symbols/icons.rs b/src/ui/panes/pid_drawing_tool/symbols/icons.rs new file mode 100644 index 0000000000000000000000000000000000000000..66192cc3f40199b4f83ecec56c3592415186713c --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols/icons.rs @@ -0,0 +1,234 @@ +mod motor_valve; + +use egui::{ImageSource, Theme, Ui}; +use glam::Vec2; +use motor_valve::MotorValve; +use serde::{Deserialize, Serialize}; +use strum_macros::{Display, EnumIter}; + +use crate::ui::utils::glam_to_egui; + +use super::SymbolBehavior; + +#[derive(Clone, Serialize, Deserialize, PartialEq, EnumIter, Display, Debug, Default)] +pub enum Icon { + #[default] + Arrow, + BurstDisk, + CheckValve, + FlexibleConnection, + ManualValve, + MotorValve(MotorValve), + PressureGauge, + PressureRegulator, + PressureTransducer, + QuickConnector, + ReliefValve, + ThreeWayValve, + Vessel, +} + +impl Icon { + pub fn get_image(&self, theme: Theme) -> ImageSource { + match (&self, theme) { + (Icon::Arrow, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/arrow.svg") + } + (Icon::Arrow, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/arrow.svg") + } + (Icon::BurstDisk, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/burst_disk.svg") + } + (Icon::BurstDisk, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/burst_disk.svg") + } + (Icon::ManualValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/manual_valve.svg") + } + (Icon::ManualValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/manual_valve.svg") + } + (Icon::CheckValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/check_valve.svg") + } + (Icon::CheckValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/check_valve.svg") + } + (Icon::ReliefValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/relief_valve.svg") + } + (Icon::ReliefValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/relief_valve.svg") + } + (Icon::MotorValve(state), Theme::Light) => match state.last_value { + None => { + egui::include_image!("../../../../../icons/pid_symbols/light/motor_valve.svg") + } + Some(true) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/motor_valve_green.svg" + ) + } + Some(false) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/motor_valve_red.svg" + ) + } + }, + (Icon::MotorValve(state), Theme::Dark) => match state.last_value { + None => { + egui::include_image!("../../../../../icons/pid_symbols/dark/motor_valve.svg") + } + Some(true) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/motor_valve_green.svg" + ) + } + Some(false) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/motor_valve_red.svg" + ) + } + }, + (Icon::ThreeWayValve, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/three_way_valve.svg") + } + (Icon::ThreeWayValve, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/three_way_valve.svg") + } + (Icon::PressureRegulator, Theme::Light) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/pressure_regulator.svg" + ) + } + (Icon::PressureRegulator, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/pressure_regulator.svg") + } + (Icon::QuickConnector, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/quick_connector.svg") + } + (Icon::QuickConnector, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/quick_connector.svg") + } + (Icon::PressureTransducer, Theme::Light) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/pressure_transducer.svg" + ) + } + (Icon::PressureTransducer, Theme::Dark) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/pressure_transducer.svg" + ) + } + (Icon::PressureGauge, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/pressure_gauge.svg") + } + (Icon::PressureGauge, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/pressure_gauge.svg") + } + (Icon::FlexibleConnection, Theme::Light) => { + egui::include_image!( + "../../../../../icons/pid_symbols/light/flexible_connection.svg" + ) + } + (Icon::FlexibleConnection, Theme::Dark) => { + egui::include_image!( + "../../../../../icons/pid_symbols/dark/flexible_connection.svg" + ) + } + (Icon::Vessel, Theme::Light) => { + egui::include_image!("../../../../../icons/pid_symbols/light/vessel.svg") + } + (Icon::Vessel, Theme::Dark) => { + egui::include_image!("../../../../../icons/pid_symbols/dark/vessel.svg") + } + } + } +} + +impl SymbolBehavior for Icon { + fn paint( + &mut self, + ui: &egui::Ui, + theme: egui::Theme, + pos: glam::Vec2, + size: f32, + rotation: f32, + ) { + let center = glam_to_egui(pos).to_pos2(); + let image_rect = egui::Rect::from_min_size(center, glam_to_egui(self.size() * size)); + egui::Image::new(self.get_image(theme)) + .rotate(rotation, egui::Vec2::splat(0.0)) + .paint_at(ui, image_rect); + } + + fn anchor_points(&self) -> Option<Vec<glam::Vec2>> { + Some( + match self { + Icon::Arrow => vec![(0.0, 2.0), (4.0, 2.0)], + Icon::BurstDisk => vec![(0.0, 3.0), (4.0, 3.0)], + Icon::CheckValve => vec![(0.0, 2.5), (10.0, 2.5)], + Icon::FlexibleConnection => vec![(0.0, 3.0), (10.0, 3.0)], + Icon::ManualValve => vec![(0.0, 2.5), (10.0, 2.5)], + Icon::MotorValve(_) => vec![(0.0, 5.0), (10.0, 5.0)], + Icon::PressureGauge => vec![(3.5, 7.0)], + Icon::PressureRegulator => vec![(0.0, 7.0), (10.0, 7.0)], + Icon::PressureTransducer => vec![(3.5, 7.0)], + Icon::QuickConnector => vec![(0.0, 2.5), (6.0, 2.5)], + Icon::ReliefValve => vec![(3.0, 10.0)], + Icon::ThreeWayValve => vec![(0.0, 3.0), (10.0, 3.0), (5.0, 8.0)], + Icon::Vessel => vec![(0.0, 7.6), (8.2, 7.6), (4.1, 0.0), (4.1, 15.1)], + } + .iter() + .map(|&p| p.into()) + .collect(), + ) + } + + fn size(&self) -> Vec2 { + match self { + Icon::Arrow => (4.0, 4.0), + Icon::BurstDisk => (4.0, 6.0), + Icon::CheckValve => (10.0, 5.0), + Icon::FlexibleConnection => (10.0, 6.0), + Icon::ManualValve => (10.0, 5.0), + Icon::MotorValve(_) => (10.0, 8.0), + Icon::PressureGauge => (7.0, 7.0), + Icon::PressureRegulator => (10.0, 10.0), + Icon::PressureTransducer => (7.0, 7.0), + Icon::QuickConnector => (6.0, 5.0), + Icon::ReliefValve => (6.0, 10.0), + Icon::ThreeWayValve => (10.0, 8.0), + Icon::Vessel => (8.2, 15.2), + } + .into() + } +} + +/// Single MavLink value source info +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +#[serde(from = "SerialMavlinkValue")] +struct MavlinkValue { + msg_id: u32, + field: String, + + #[serde(skip)] + view_id: egui::Id, +} + +#[derive(Deserialize)] +struct SerialMavlinkValue { + msg_id: u32, + field: String, +} + +impl From<SerialMavlinkValue> for MavlinkValue { + fn from(value: SerialMavlinkValue) -> Self { + Self { + msg_id: value.msg_id, + field: value.field, + view_id: egui::Id::new(""), + } + } +} diff --git a/src/ui/panes/pid_drawing_tool/symbols/icons/motor_valve.rs b/src/ui/panes/pid_drawing_tool/symbols/icons/motor_valve.rs new file mode 100644 index 0000000000000000000000000000000000000000..af4f38f55f56eef934762ba41ab9218be32d8111 --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols/icons/motor_valve.rs @@ -0,0 +1,60 @@ +use crate::mavlink::{extract_from_message, MavlinkResult, MessageView, TimedMessage}; + +use super::MavlinkValue; + +use serde::{Deserialize, Serialize}; +use skyward_mavlink::{mavlink::MessageData, orion}; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct MotorValve { + source: MavlinkValue, + + /// false = closed, true = open + pub last_value: Option<bool>, +} + +impl Default for MotorValve { + fn default() -> Self { + Self { + source: MavlinkValue { + msg_id: orion::GSE_TM_DATA::ID, + field: "n2o_filling_valve_state".to_string(), + view_id: egui::Id::new(""), + }, + last_value: None, + } + } +} + +impl MessageView for MotorValve { + fn widget_id(&self) -> &egui::Id { + &self.source.view_id + } + + fn id_of_interest(&self) -> u32 { + self.source.msg_id + } + + fn is_valid(&self) -> bool { + self.last_value.is_some() + } + + fn populate_view(&mut self, msg_slice: &[TimedMessage]) -> MavlinkResult<()> { + self.update_view(msg_slice) + } + + fn update_view(&mut self, msg_slice: &[TimedMessage]) -> MavlinkResult<()> { + if let Some(msg) = msg_slice.last() { + let values: MavlinkResult<Vec<Option<u8>>> = + extract_from_message(&msg.message, [&self.source.field]); + if let Ok(values) = values { + if !values.is_empty() { + if let Some(value) = values[0].map(|v| v != 0) { + self.last_value = Some(value); + } + } + } + } + Ok(()) + } +} diff --git a/src/ui/panes/pid_drawing_tool/symbols/labels.rs b/src/ui/panes/pid_drawing_tool/symbols/labels.rs new file mode 100644 index 0000000000000000000000000000000000000000..03c974fa193317cf2695ea64783c076477a012ee --- /dev/null +++ b/src/ui/panes/pid_drawing_tool/symbols/labels.rs @@ -0,0 +1,71 @@ +use serde::{Deserialize, Serialize}; + +use crate::ui::utils::glam_to_egui; + +use super::SymbolBehavior; +use egui::{Align2, Color32, FontId, Rounding, Stroke, Theme, Ui}; +use glam::Vec2; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +pub struct Label { + last_value: Option<f32>, + format_string: String, + #[serde(skip)] + show_window: bool, +} + +impl SymbolBehavior for Label { + fn paint(&mut self, ui: &Ui, theme: Theme, pos: Vec2, size: f32, _: f32) { + let painter = ui.painter(); + let color = match theme { + Theme::Light => Color32::BLACK, + Theme::Dark => Color32::WHITE, + }; + + painter.text( + glam_to_egui(pos).to_pos2(), + Align2::LEFT_TOP, + &self.format_string, + FontId::monospace(self.size().y * size), + color, + ); + painter.rect( + egui::Rect::from_min_size( + glam_to_egui(pos).to_pos2(), + glam_to_egui(self.size()) * size, + ), + Rounding::ZERO, + Color32::TRANSPARENT, + Stroke::new(1.0, color), + ); + + println!("Drawing label edit window {}", self.show_window); + let mut show_window = self.show_window; + egui::Window::new("Label") + .id(ui.id()) + .auto_sized() + .collapsible(false) + .movable(true) + .open(&mut show_window) + .show(ui.ctx(), |ui| { + ui.text_edit_singleline(&mut self.format_string); + }); + self.show_window = show_window; + } + + fn anchor_points(&self) -> Option<Vec<Vec2>> { + None + } + + fn size(&self) -> Vec2 { + let font_size = 2.0; + Vec2::new(font_size * 0.6 * self.format_string.len() as f32, font_size) + } + + fn context_menu(&mut self, ui: &mut Ui) { + if ui.button("Edit").clicked() { + self.show_window = true; + ui.close_menu(); + } + } +}