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_laser::DacId;
use nannou_laser::{self as laser};
use serde_json::Result;
// use serde_json::Result;
use laserspace::trap::filters::{MappedPoint, PointFilters};
use laserspace::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX, Corner};
use laserspace::trap::tracks::{CoordinateSpace, RenderableLayers, renderable};
@ -66,7 +66,7 @@ struct GuiModel {
laser_model: LaserModel,
// A copy of the laser settings so that we can control them with the GUI.
laser_settings: LaserSettings,
per_laser_config: DacConfigMap,
per_laser_config: OutputConfigMap,
// For receiving newly detected DACs.
dac_rx: mpsc::Receiver<laser::DetectedDac>,
// The UI for control over laser parameters and settings.
@ -78,7 +78,7 @@ struct GuiModel {
// dimming_factor: f32,
lost_alpha: f32,
connected: bool,
selected_stream: Option<DacId>,
selected_stream: Option<OutputId>,
canvas_scale: f32,
canvas_translate: Vec2,
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(Debug, Serialize, Deserialize)]
#[serde(remote = "DacId")]
pub enum DacIdSerializable {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum OutputId{
EtherDream { mac_address: [u8; 6] },
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)]
pub struct SavedDacConfig{
#[serde(with = "DacIdSerializable")]
dac_id: DacId,
pub struct SavedOutputConfig{
// #[serde(with = "DacIdSerializable")]
dac_id: OutputId,
config: DacConfig,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SavedConfig {
dacs: Vec<SavedDacConfig>
dacs: Vec<SavedOutputConfig>
}
impl Into<DacConfigMap> for SavedConfig {
fn into(self) -> DacConfigMap {
impl Into<OutputConfigMap> for SavedConfig {
fn into(self) -> OutputConfigMap {
let mut configs = HashMap::new();
for dac in self.dacs {
configs.insert(dac.dac_id, dac.config);
@ -269,11 +299,11 @@ impl Into<DacConfigMap> for SavedConfig {
}
}
impl From<&mut DacConfigMap> for SavedConfig {
fn from(per_laser_config: &mut DacConfigMap) -> SavedConfig {
impl From<&mut OutputConfigMap> for SavedConfig {
fn from(per_output_config: &mut OutputConfigMap) -> SavedConfig {
let mut dacs = Vec::new();
for (dac_id, config) in per_laser_config.into_iter() {
dacs.push(SavedDacConfig{
for (dac_id, config) in per_output_config.into_iter() {
dacs.push(SavedOutputConfig{
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.
fn get_dac_configs(config_path: &PathBuf) -> DacConfigMap{
fn get_dac_configs(config_path: &PathBuf) -> OutputConfigMap{
match read_config_from_file(config_path) {
Err(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();
}
fn update(_app: &App, model: &mut GuiModel, update: Update) {
fn update(app: &App, model: &mut GuiModel, update: Update) {
// First, check for new laser DACs.
for dac in model.dac_rx.try_recv() {
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");
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
.laser_api
@ -557,9 +588,10 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
eprintln!("Stream closed due to an error: {}", err);
}
// TODO: keeps looping on disconnect.
println!("attempting to restart stream with DAC {:?}", 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
.laser_api
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer)
@ -728,48 +760,92 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
ui.separator();
ui.heading("Laser specific settings");
if per_laser_config.is_empty() {
ui.label("No dacs available");
} else {
ui.horizontal_wrapped(|ui| {
ui.selectable_value(
selected_stream,
None,
""
);
ui.horizontal_wrapped(|ui| {
ui.selectable_value(
selected_stream,
None,
""
);
if per_laser_config.is_empty() {
ui.label("No dacs available");
} else {
for (dac_id, _config) in per_laser_config.iter() {
let is_available = laser_streams.contains_key(&dac_id);
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
}
},
};
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};
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{""};
// egui::widgets::SelectableLabel cannot have border unless hovered/highlighted
// TODO: alternatively underscore with ui.painter().rect/hline(ui.selectable_value().rect.max/min, ....)
ui.selectable_value(
selected_stream,
Some(dac_id.clone()),
Some(output_id.clone()),
format!("{name}{indicator}")
);
}
// reset
ui.style_mut().visuals.override_text_color = None;
});
}
}
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_stream_value) = selected_stream {
if let Some(selected_output) = selected_stream {
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));
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");
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;
@ -791,7 +867,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
ui.horizontal_wrapped(|ui| {
for layer_nr in 1..=8 {
for layer_nr in 0..8 {
let mask = 1 << layer_nr;
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
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 {
stream.send(move |laser_model: &mut LaserModel| {
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
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 {
stream.send(move |laser_model: &mut LaserModel| {
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
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();
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
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();
if let Some(stream) = selected_laser_stream {