Output to a window for projectors

This commit is contained in:
Ruben van de Ven 2025-11-10 21:07:06 +01:00
parent 3b5e023da3
commit 9602b46839

View file

@ -11,7 +11,7 @@ use bevy::prelude::Mat3; // for glam::f32::Mat3, which is distinct from nannou::
use nannou_egui::{self, egui, Egui}; use nannou_egui::{self, egui, Egui};
use nannou_laser::DacId; use nannou_laser::DacId;
use nannou_laser::{self as laser}; use nannou_laser::{self as laser};
use serde_json::Result; // use serde_json::Result;
use laserspace::trap::filters::{MappedPoint, PointFilters}; use laserspace::trap::filters::{MappedPoint, PointFilters};
use laserspace::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX, Corner}; use laserspace::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX, Corner};
use laserspace::trap::tracks::{CoordinateSpace, RenderableLayers, renderable}; use laserspace::trap::tracks::{CoordinateSpace, RenderableLayers, renderable};
@ -66,7 +66,7 @@ struct GuiModel {
laser_model: LaserModel, laser_model: LaserModel,
// A copy of the laser settings so that we can control them with the GUI. // A copy of the laser settings so that we can control them with the GUI.
laser_settings: LaserSettings, laser_settings: LaserSettings,
per_laser_config: DacConfigMap, per_laser_config: OutputConfigMap,
// For receiving newly detected DACs. // For receiving newly detected DACs.
dac_rx: mpsc::Receiver<laser::DetectedDac>, dac_rx: mpsc::Receiver<laser::DetectedDac>,
// The UI for control over laser parameters and settings. // The UI for control over laser parameters and settings.
@ -78,7 +78,7 @@ struct GuiModel {
// dimming_factor: f32, // dimming_factor: f32,
lost_alpha: f32, lost_alpha: f32,
connected: bool, connected: bool,
selected_stream: Option<DacId>, selected_stream: Option<OutputId>,
canvas_scale: f32, canvas_scale: f32,
canvas_translate: Vec2, canvas_translate: Vec2,
canvas_dragging_corner: Option<Corner>, canvas_dragging_corner: Option<Corner>,
@ -235,32 +235,62 @@ fn zmq_receive(model: &mut GuiModel) {
} }
type DacConfigMap = HashMap<DacId, DacConfig>; type OutputConfigMap = HashMap<OutputId, DacConfig>;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum OutputId{
#[derive(Debug, Serialize, Deserialize)]
#[serde(remote = "DacId")]
pub enum DacIdSerializable {
EtherDream { mac_address: [u8; 6] }, EtherDream { mac_address: [u8; 6] },
Helios { id: u32 }, Helios { id: u32 },
Display { monitor: u32, enabled: bool }
} }
impl From<DacId> for OutputId {
fn from(dac_id: DacId) -> OutputId {
match dac_id {
DacId::EtherDream { mac_address } => OutputId::EtherDream { mac_address },
DacId::Helios {id } => OutputId::Helios { id },
}
}
}
impl TryInto<DacId> for OutputId {
type Error = OutputId;
fn try_into(self) -> Result<DacId, Self::Error> {
match self {
OutputId::EtherDream { mac_address } => Ok(DacId::EtherDream { mac_address }),
OutputId::Helios { id } => Ok(DacId::Helios { id }),
// Return the original OutputId in the Err variant
// if it doesn't match any DacId variant
other => Err(other),
}
}
}
// #[derive(Debug, Serialize, Deserialize)]
// #[serde(remote = "DacId")]
// pub enum DacIdSerializable {
// EtherDream { mac_address: [u8; 6] },
// Helios { id: u32 },
// }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SavedDacConfig{ pub struct SavedOutputConfig{
#[serde(with = "DacIdSerializable")] // #[serde(with = "DacIdSerializable")]
dac_id: DacId, dac_id: OutputId,
config: DacConfig, config: DacConfig,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SavedConfig { pub struct SavedConfig {
dacs: Vec<SavedDacConfig> dacs: Vec<SavedOutputConfig>
} }
impl Into<DacConfigMap> for SavedConfig { impl Into<OutputConfigMap> for SavedConfig {
fn into(self) -> DacConfigMap { fn into(self) -> OutputConfigMap {
let mut configs = HashMap::new(); let mut configs = HashMap::new();
for dac in self.dacs { for dac in self.dacs {
configs.insert(dac.dac_id, dac.config); configs.insert(dac.dac_id, dac.config);
@ -269,11 +299,11 @@ impl Into<DacConfigMap> for SavedConfig {
} }
} }
impl From<&mut DacConfigMap> for SavedConfig { impl From<&mut OutputConfigMap> for SavedConfig {
fn from(per_laser_config: &mut DacConfigMap) -> SavedConfig { fn from(per_output_config: &mut OutputConfigMap) -> SavedConfig {
let mut dacs = Vec::new(); let mut dacs = Vec::new();
for (dac_id, config) in per_laser_config.into_iter() { for (dac_id, config) in per_output_config.into_iter() {
dacs.push(SavedDacConfig{ dacs.push(SavedOutputConfig{
dac_id: dac_id.clone(), config: config.clone() dac_id: dac_id.clone(), config: config.clone()
}); });
} }
@ -305,7 +335,7 @@ fn save_config_file<P: AsRef<Path>>(path: P, config: SavedConfig) -> std::result
// Some hardcoded config. Not spending time on reading/writing config atm. // Some hardcoded config. Not spending time on reading/writing config atm.
fn get_dac_configs(config_path: &PathBuf) -> DacConfigMap{ fn get_dac_configs(config_path: &PathBuf) -> OutputConfigMap{
match read_config_from_file(config_path) { match read_config_from_file(config_path) {
Err(err) => { Err(err) => {
eprintln!("Could not load config {}", err); eprintln!("Could not load config {}", err);
@ -501,16 +531,17 @@ fn double_send_settings_at_connect(laser_settings: &LaserSettings, stream: &lase
stream.set_radians_per_point(laser_settings.radians_per_point).ok(); stream.set_radians_per_point(laser_settings.radians_per_point).ok();
} }
fn update(_app: &App, model: &mut GuiModel, update: Update) { fn update(app: &App, model: &mut GuiModel, update: Update) {
// First, check for new laser DACs. // First, check for new laser DACs.
for dac in model.dac_rx.try_recv() { for dac in model.dac_rx.try_recv() {
println!("Detected DAC {:?}!", dac.id()); println!("Detected DAC {:?}!", dac.id());
if !model.per_laser_config.contains_key(&dac.id()) { let output_id: OutputId = dac.id().into();
if !model.per_laser_config.contains_key(&output_id) {
println!("Found unknown DAC, register with defaults"); println!("Found unknown DAC, register with defaults");
model.per_laser_config.insert(dac.id(), DacConfig::default()); model.per_laser_config.insert(output_id.clone(), DacConfig::default());
} }
let config = &model.per_laser_config[&dac.id()]; let config = &model.per_laser_config[&output_id];
let stream = model let stream = model
.laser_api .laser_api
@ -557,9 +588,10 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
eprintln!("Stream closed due to an error: {}", err); eprintln!("Stream closed due to an error: {}", err);
} }
// TODO: keeps looping on disconnect. // TODO: keeps looping on disconnect.
println!("attempting to restart stream with DAC {:?}", dac.id());
let dac_id = dac.id(); let dac_id = dac.id();
let config = &model.per_laser_config[&dac.id()]; println!("attempting to restart stream with DAC {:?}", &dac_id);
let output_id: OutputId = dac_id.clone().into();
let config = &model.per_laser_config[&output_id];
match model match model
.laser_api .laser_api
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) .new_frame_stream(model.laser_model.with_config(config), laser_frame_producer)
@ -728,49 +760,93 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
ui.separator(); ui.separator();
ui.heading("Laser specific settings"); ui.heading("Laser specific settings");
if per_laser_config.is_empty() { ui.horizontal_wrapped(|ui| {
ui.label("No dacs available"); ui.selectable_value(
} else { selected_stream,
ui.horizontal_wrapped(|ui| { None,
ui.selectable_value( ""
selected_stream, );
None, if per_laser_config.is_empty() {
"" ui.label("No dacs available");
); } else {
for (output_id, _config) in per_laser_config.iter() {
let is_available = match &output_id.clone().try_into() {
Ok(dac_id) => laser_streams.contains_key(dac_id),
Err(_output_id) => {
if let OutputId::Display { enabled, .. } = output_id {
*enabled
} else {
false
}
},
};
for (dac_id, _config) in per_laser_config.iter() {
let is_available = laser_streams.contains_key(&dac_id);
ui.style_mut().visuals.override_text_color = if is_available {Some(egui::Color32::GREEN)} else {None}; ui.style_mut().visuals.override_text_color = if is_available {Some(egui::Color32::GREEN)} else {None};
ui.style_mut().visuals.widgets.inactive.bg_stroke = if is_available {egui::Stroke::new(2.0, egui::Color32::GREEN)} else {egui::Stroke::NONE}; ui.style_mut().visuals.widgets.inactive.bg_stroke = if is_available {egui::Stroke::new(2.0, egui::Color32::GREEN)} else {egui::Stroke::NONE};
let name = if let Some(config) = per_laser_config.get(&dac_id) { config.name.clone() } else { "DAC".into() }; let name = if let Some(config) = per_laser_config.get(&output_id) { config.name.clone() } else { "DAC".into() };
let indicator = if is_available{" 🔌"}else{""}; let indicator = if is_available{" 🔌"}else{""};
// egui::widgets::SelectableLabel cannot have border unless hovered/highlighted // egui::widgets::SelectableLabel cannot have border unless hovered/highlighted
// TODO: alternatively underscore with ui.painter().rect/hline(ui.selectable_value().rect.max/min, ....) // TODO: alternatively underscore with ui.painter().rect/hline(ui.selectable_value().rect.max/min, ....)
ui.selectable_value( ui.selectable_value(
selected_stream, selected_stream,
Some(dac_id.clone()), Some(output_id.clone()),
format!("{name}{indicator}") format!("{name}{indicator}")
); );
} }
// reset // reset
ui.style_mut().visuals.override_text_color = None; ui.style_mut().visuals.override_text_color = None;
}); }
}
if let Some(selected_stream_value) = selected_stream { if ui.button("+").clicked() {
let output_id = OutputId::Display { monitor: 0, enabled: false };
per_laser_config.insert(output_id, DacConfig::default());
}
});
if let Some(selected_output) = selected_stream {
ui.separator(); ui.separator();
ui.add(egui::Label::new(format!("{:?}", selected_stream_value))); ui.add(egui::Label::new(format!("{:?}", selected_output)));
let selected_config: &mut DacConfig = per_laser_config.get_mut(&selected_stream_value).unwrap(); let selected_config: &mut DacConfig = per_laser_config.get_mut(&selected_output).unwrap();
ui.add(egui::TextEdit::singleline(&mut selected_config.name)); ui.add(egui::TextEdit::singleline(&mut selected_config.name));
let selected_laser_stream = laser_streams.get(&selected_stream_value); let selected_laser_stream = match selected_output.clone().try_into() {
Ok(dac_id) => laser_streams.get(&dac_id),
Err(_output_id) => None,
} ;
// let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs"); // let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs");
if let OutputId::Display { monitor, enabled: fullscreen } = selected_output {
let monitors = app.available_monitors();
// monitors.get(0).unwrap().name()
// fullscreen = FullScreen::Borderless(Some(monitor_handle));
// app.new_window().fullscreen_with(fullscreen)
let mut selected_monitor_name = String::from("haha");
egui::ComboBox::from_label("Monitor")
.selected_text(format!("MONITOR -- {selected_monitor_name:?}"))
.show_ui(ui, |ui| {
for monitor_option in monitors {
if let Some(monitor_name) = monitor_option.name() {
if ui.selectable_value(&mut selected_monitor_name, monitor_name.clone(), format!("{:?}", &monitor_name)).clicked() {
if let Some(stream) = selected_laser_stream {
// TODO: make this for monitor: switch it
}
};
}
}
});
}
let source = &mut selected_config.source; let source = &mut selected_config.source;
egui::ComboBox::from_label("Source") egui::ComboBox::from_label("Source")
@ -791,7 +867,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
for layer_nr in 1..=8 { for layer_nr in 0..8 {
let mask = 1 << layer_nr; let mask = 1 << layer_nr;
let mut enabled_bool = mask & selected_config.layers_enabled != 0; let mut enabled_bool = mask & selected_config.layers_enabled != 0;
@ -1284,7 +1360,11 @@ fn laser_mouse_moved(app: &App, model: &mut GuiModel, pos: Point2) {
// 3. update config in laser stream threat // 3. update config in laser stream threat
let homography = config.filters.homography.clone(); let homography = config.filters.homography.clone();
let selected_laser_stream = model.laser_streams.get(&dac_id); let selected_laser_stream = match dac_id.clone().try_into() {
Ok(dac_id) => model.laser_streams.get(&dac_id),
Err(_output_id) => None,
} ;
if let Some(stream) = selected_laser_stream { if let Some(stream) = selected_laser_stream {
stream.send(move |laser_model: &mut LaserModel| { stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.homography = homography laser_model.config.filters.homography = homography
@ -1305,7 +1385,11 @@ fn laser_mouse_moved(app: &App, model: &mut GuiModel, pos: Point2) {
// 3. update config in laser stream threat // 3. update config in laser stream threat
let mask = config.filters.clip.mask.clone(); let mask = config.filters.clip.mask.clone();
let selected_laser_stream = model.laser_streams.get(&dac_id); let selected_laser_stream = match dac_id.clone().try_into() {
Ok(dac_id) => model.laser_streams.get(&dac_id),
Err(_output_id) => None,
} ;
if let Some(stream) = selected_laser_stream { if let Some(stream) = selected_laser_stream {
stream.send(move |laser_model: &mut LaserModel| { stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.clip.mask = mask; laser_model.config.filters.clip.mask = mask;
@ -1431,7 +1515,11 @@ fn map_mouse_moved(_app: &App, model: &mut GuiModel, pos: Point2) {
} }
// 3. propagate to laser stream threat // 3. propagate to laser stream threat
let selected_laser_stream = model.laser_streams.get(&dac_id); let selected_laser_stream = match dac_id.clone().try_into() {
Ok(dac_id) => model.laser_streams.get(&dac_id),
Err(_output_id) => None,
} ;
let homography = config.filters.homography.clone(); let homography = config.filters.homography.clone();
if let Some(stream) = selected_laser_stream { if let Some(stream) = selected_laser_stream {
@ -1540,7 +1628,11 @@ fn map_mouse_pressed(app: &App, model: &mut GuiModel, button: MouseButton) {
} }
// propagate to laser dac streamer // propagate to laser dac streamer
let selected_laser_stream = model.laser_streams.get(&dac_id); let selected_laser_stream = match dac_id.clone().try_into() {
Ok(dac_id) => model.laser_streams.get(&dac_id),
Err(_output_id) => None,
} ;
let homography = config.filters.homography.clone(); let homography = config.filters.homography.clone();
if let Some(stream) = selected_laser_stream { if let Some(stream) = selected_laser_stream {