Output to a window
This commit is contained in:
parent
9602b46839
commit
38baeacd01
2 changed files with 297 additions and 56 deletions
343
src/main.rs
343
src/main.rs
|
|
@ -2,6 +2,7 @@
|
|||
//! A clone of the `laser_frame_stream.rs` example that allows for configuring laser settings via a
|
||||
//! UI.
|
||||
|
||||
use nannou::winit::monitor::MonitorHandle;
|
||||
// use bevy_nannou::prelude::DARK_GRAY;
|
||||
// use nannou::lyon::geom::euclid::Transform2D;
|
||||
use nannou::{geom::Rect, math::map_range as nannou_map_range};
|
||||
|
|
@ -53,6 +54,7 @@ pub struct StreamConfig{
|
|||
}
|
||||
type StreamConfigMap = HashMap<DacId, StreamConfig>;
|
||||
type StreamMap = HashMap<DacId, laser::FrameStream<LaserModel>>;
|
||||
type DisplayMap = HashMap<OutputId, WindowId>;
|
||||
|
||||
|
||||
|
||||
|
|
@ -61,7 +63,8 @@ struct GuiModel {
|
|||
config_file_path: PathBuf,
|
||||
laser_api: Arc<laser::Api>,
|
||||
// All of the live stream handles.
|
||||
laser_streams: StreamMap,
|
||||
laser_streams: StreamMap, // outputId -> laserstreams
|
||||
display_outputs: DisplayMap, // outputId -> windows
|
||||
// A copy of the state that will live on the laser thread so we can present a GUI.
|
||||
laser_model: LaserModel,
|
||||
// A copy of the laser settings so that we can control them with the GUI.
|
||||
|
|
@ -207,7 +210,7 @@ fn zmq_receive(model: &mut GuiModel) {
|
|||
model.lost_alpha = 0.;
|
||||
}
|
||||
layers = model.current_layers.with_alpha(model.lost_alpha);
|
||||
} else {
|
||||
} else {
|
||||
layers = RenderableLayers::new()
|
||||
}
|
||||
} else {
|
||||
|
|
@ -241,7 +244,7 @@ type OutputConfigMap = HashMap<OutputId, DacConfig>;
|
|||
pub enum OutputId{
|
||||
EtherDream { mac_address: [u8; 6] },
|
||||
Helios { id: u32 },
|
||||
Display { monitor: u32, enabled: bool }
|
||||
Display { id: u32 }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -384,6 +387,20 @@ fn model(app: &App) -> GuiModel {
|
|||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
||||
|
||||
// We'll use a `Vec` to collect laser streams as they appear.
|
||||
let laser_streams = HashMap::new(); //vec![];
|
||||
let mut display_outputs = HashMap::new(); //vec![];
|
||||
|
||||
let per_laser_config = get_dac_configs(&config_file_path);
|
||||
|
||||
for (output_id, dac_config) in &per_laser_config {
|
||||
create_window_for_config(app, &output_id, &dac_config, &mut display_outputs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Initialise the state that we want to live on the laser thread and spawn the stream.
|
||||
let laser_settings = LaserSettings::default();
|
||||
let laser_model = LaserModel::new();
|
||||
|
|
@ -435,8 +452,6 @@ fn model(app: &App) -> GuiModel {
|
|||
}
|
||||
});
|
||||
|
||||
// We'll use a `Vec` to collect laser streams as they appear.
|
||||
let laser_streams = HashMap::new(); //vec![];
|
||||
|
||||
// A user-interface to tweak the settings.
|
||||
let window = app.window(w_id_lasersettings).unwrap();
|
||||
|
|
@ -447,7 +462,6 @@ fn model(app: &App) -> GuiModel {
|
|||
|
||||
let current_layers = RenderableLayers::new(); //Vec::new();
|
||||
|
||||
let per_laser_config = get_dac_configs(&config_file_path);
|
||||
|
||||
GuiModel {
|
||||
config_file_path,
|
||||
|
|
@ -455,6 +469,7 @@ fn model(app: &App) -> GuiModel {
|
|||
laser_settings,
|
||||
laser_model,
|
||||
laser_streams,
|
||||
display_outputs,
|
||||
dac_rx,
|
||||
egui,
|
||||
zmq,
|
||||
|
|
@ -475,27 +490,81 @@ fn model(app: &App) -> GuiModel {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_window_for_config(app: &App, output_id: &OutputId, config: &DacConfig, display_outputs: &mut DisplayMap) -> Option<WindowId> {
|
||||
|
||||
// alreday created? Make visible
|
||||
if let Some(win_id) = display_outputs.get(&output_id) {
|
||||
if let Some(window) = app.window(win_id.clone()) {
|
||||
// let win = &*window;
|
||||
window.set_visible(config.window_enabled);
|
||||
return Some(win_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// make window, even if disabled (but set to invisible)
|
||||
if let OutputId::Display { id } = output_id {
|
||||
let handle = monitor_handle_for_name(app, &config.window_monitor);
|
||||
|
||||
let fs = match (config.window_fullscreen, handle) {
|
||||
(true, Some(handle)) => Some(Fullscreen::Borderless(Some(handle))),
|
||||
_ => None,
|
||||
};
|
||||
// let fs = None;
|
||||
let w_id = app
|
||||
.new_window()
|
||||
.fullscreen_with(fs)
|
||||
.view(view_display)
|
||||
.visible(config.window_enabled)
|
||||
.build()
|
||||
.unwrap();
|
||||
display_outputs.insert(output_id.clone(), w_id.clone());
|
||||
return Some(w_id);
|
||||
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn monitor_handle_for_name(app: &App, name: &String) -> Option<MonitorHandle> {
|
||||
app.available_monitors()
|
||||
.iter()
|
||||
.find(|w| w.name().unwrap() == *name)
|
||||
.map(|w| w.clone())
|
||||
}
|
||||
|
||||
fn layers_to_points_for_config(current_layers: &RenderableLayers, config: &DacConfig ) -> Vec<Vec<laser::Point>> {
|
||||
let mut new_laser_points: Vec<laser::Point> = Vec::new();
|
||||
for (layer_nr, layer) in current_layers.0.iter() {
|
||||
let mask = 1 << layer_nr;
|
||||
if (mask & config.layers_enabled) != 0 {
|
||||
let current_points: LaserPoints = layer.into();
|
||||
|
||||
// check which source should be used, and get points accordingly.
|
||||
// potentially ignoring the points coming from the stream
|
||||
let points = config.source.get_shape(current_points, config);
|
||||
|
||||
// let pointno = points.points.len();
|
||||
|
||||
let new_points = match points.space {
|
||||
CoordinateSpace::RawLaser => points,
|
||||
_ => config.filters.apply(&points)
|
||||
};
|
||||
match config.source {
|
||||
StreamSource::CurrentLayers => {
|
||||
for (layer_nr, layer) in current_layers.0.iter() {
|
||||
let mask = 1 << layer_nr;
|
||||
if (mask & config.layers_enabled) != 0 {
|
||||
let points: LaserPoints = layer.into();
|
||||
|
||||
// check which source should be used, and get points accordingly.
|
||||
// potentially ignoring the points coming from the stream
|
||||
// let points = config.source.get_shape(current_points, config);
|
||||
|
||||
// let pointno = points.points.len();
|
||||
|
||||
let new_points = match points.space {
|
||||
CoordinateSpace::RawLaser => points,
|
||||
_ => config.filters.apply(&points)
|
||||
};
|
||||
// let new_laser_points = new_points.points;
|
||||
new_laser_points.extend(new_points.points);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let points = config.source.get_shape(LaserPoints::default(), config);
|
||||
let new_points = config.filters.apply(&points);
|
||||
// let new_laser_points = new_points.points;
|
||||
new_laser_points.extend(new_points.points);
|
||||
}
|
||||
}
|
||||
|
||||
split_on_blank(new_laser_points)
|
||||
}
|
||||
|
||||
|
|
@ -621,6 +690,7 @@ fn update(app: &App, model: &mut GuiModel, update: Update) {
|
|||
ref mut egui,
|
||||
ref mut laser_streams,
|
||||
ref mut laser_model,
|
||||
ref mut display_outputs,
|
||||
ref mut laser_settings,
|
||||
ref mut per_laser_config,
|
||||
ref mut selected_stream,
|
||||
|
|
@ -770,18 +840,18 @@ fn update(app: &App, model: &mut GuiModel, update: Update) {
|
|||
ui.label("No dacs available");
|
||||
} else {
|
||||
|
||||
for (output_id, _config) in per_laser_config.iter() {
|
||||
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
|
||||
if let OutputId::Display { .. } = output_id {
|
||||
config.window_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(&output_id) { config.name.clone() } else { "DAC".into() };
|
||||
|
|
@ -799,8 +869,27 @@ fn update(app: &App, model: &mut GuiModel, update: Update) {
|
|||
}
|
||||
|
||||
if ui.button("+").clicked() {
|
||||
let output_id = OutputId::Display { monitor: 0, enabled: false };
|
||||
per_laser_config.insert(output_id, DacConfig::default());
|
||||
let name = app.available_monitors().first().unwrap().name().expect("A monitor should be connected");
|
||||
// find highest id:
|
||||
|
||||
let max_id = per_laser_config
|
||||
.keys()
|
||||
.filter_map(|c| match c {
|
||||
OutputId::Display{id} => Some(id),
|
||||
_ => None,
|
||||
})
|
||||
.max().cloned().unwrap_or(0 as u32);
|
||||
|
||||
|
||||
let output_id = OutputId::Display { id: max_id+1 };
|
||||
let new_config = DacConfig{
|
||||
window_monitor: name,
|
||||
window_enabled: false,
|
||||
window_fullscreen: false,
|
||||
..DacConfig::default()
|
||||
};
|
||||
create_window_for_config(app, &output_id, &new_config, display_outputs);
|
||||
per_laser_config.insert(output_id, new_config);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -820,31 +909,90 @@ fn update(app: &App, model: &mut GuiModel, update: Update) {
|
|||
|
||||
// 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 sel_output = selected_output.clone();
|
||||
if let OutputId::Display { id } = 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");
|
||||
// let mut selected_monitor_name = String::from("haha");
|
||||
|
||||
let win_id = display_outputs.get(&sel_output);
|
||||
|
||||
|
||||
egui::ComboBox::from_label("Monitor")
|
||||
.selected_text(format!("MONITOR -- {selected_monitor_name:?}"))
|
||||
.selected_text(format!("MONITOR -- {:?}", &selected_config.window_monitor))
|
||||
.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
|
||||
if ui.selectable_value(&mut selected_config.window_monitor, monitor_name.clone(), format!("{:?}", &monitor_name)).clicked() {
|
||||
let handle = monitor_handle_for_name(app, &selected_config.window_monitor);
|
||||
|
||||
let fs = match (selected_config.window_fullscreen, handle) {
|
||||
(true, Some(handle)) => Some(Fullscreen::Borderless(Some(handle))),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(window_id) = win_id {
|
||||
if let Some(window) = app.window(*window_id) {
|
||||
// let win = &*window;
|
||||
// win.set_fullscreen_with(fs);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
if ui
|
||||
.add(egui::Checkbox::new(&mut selected_config.window_enabled,"Enable output"))
|
||||
.changed()
|
||||
{
|
||||
if let Some(win_id) = display_outputs.get(&sel_output) {
|
||||
if let Some(window) = app.window(win_id.clone()) {
|
||||
// let win = &*window;
|
||||
window.set_visible(selected_config.window_enabled);
|
||||
}
|
||||
}
|
||||
// match enabled {
|
||||
// false => {
|
||||
// if let Some(window_id) = win_id {
|
||||
// if let Some(window) = app.window(*window_id) {
|
||||
// let win = &*window;
|
||||
// win.set_visible(false);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// true => {
|
||||
// create_window_for_config(app, &sel_output, display_outputs);
|
||||
// }
|
||||
// };
|
||||
}
|
||||
if ui
|
||||
.add(egui::Checkbox::new(&mut selected_config.window_fullscreen,"Fullscreen"))
|
||||
.changed()
|
||||
{
|
||||
|
||||
let handle = monitor_handle_for_name(app, &selected_config.window_monitor);
|
||||
|
||||
let fs = match (selected_config.window_fullscreen, handle) {
|
||||
(true, Some(handle)) => Some(Fullscreen::Borderless(Some(handle))),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(window_id) = win_id {
|
||||
if let Some(window) = app.window(*window_id) {
|
||||
// let win = &*window;
|
||||
window.set_fullscreen_with(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source = &mut selected_config.source;
|
||||
|
|
@ -1015,6 +1163,7 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
|
|||
};
|
||||
draw.background().color(bgcolor);
|
||||
|
||||
|
||||
let win = app.window_rect();
|
||||
|
||||
let scale = model.canvas_scale;
|
||||
|
|
@ -1123,6 +1272,86 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
|
|||
draw.to_frame(app, &frame).unwrap();
|
||||
}
|
||||
|
||||
fn view_display(app: &App, model: &GuiModel, frame: Frame) {
|
||||
// get canvas to draw on
|
||||
let draw = app.draw();
|
||||
|
||||
let win_id = frame.window_id();
|
||||
let frame_rect = frame.rect();
|
||||
// let win = app.window(win_id).unwrap();
|
||||
|
||||
let output_id = model.display_outputs.keys()
|
||||
.find(|key| model.display_outputs.get(key) == Some(&win_id))
|
||||
.cloned();
|
||||
|
||||
if output_id.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let output_id = output_id.unwrap();
|
||||
let drawing_config = model.per_laser_config.get(&output_id);
|
||||
// if config.is_none() {
|
||||
// return;
|
||||
// }
|
||||
// let config = config.unwrap();
|
||||
|
||||
|
||||
// draw.background().color(srgba(0.3,0.3,0.3,1.));
|
||||
draw.background().color(srgba(0.,0.,0., 1.));
|
||||
|
||||
let (w, h) = (frame_rect.w() as f32, frame_rect.h() as f32);
|
||||
|
||||
|
||||
// let w = win.w();
|
||||
// let h = win.h();
|
||||
let hh = h as f32 / 2.;
|
||||
let hw = w as f32 / 2.;
|
||||
|
||||
let thickness = 3.0;
|
||||
|
||||
let win_rect = frame_rect.pad(20.0);
|
||||
|
||||
match &drawing_config {
|
||||
None => {
|
||||
draw.text("No config available. This should not happen?")
|
||||
.color(WHITE)
|
||||
.font_size(24)
|
||||
.wh(win_rect.wh());
|
||||
},
|
||||
Some(config) => {
|
||||
|
||||
// 3. current shape of the laser
|
||||
let lines = layers_to_points_for_config(&model.current_layers, &config);
|
||||
|
||||
// draw as distinct lines (this is how it is send to post-processing)
|
||||
// TODO: alternatively, if the optimisation becomes an actual filter
|
||||
// this should be drawn as a single line, and we can have an option to
|
||||
// visualise the intermediate lines to make the draw order apparent
|
||||
for line in lines {
|
||||
// similar to map code:
|
||||
|
||||
let vertices = line.iter().map(|p| {
|
||||
let color = srgba(p.color[0], p.color[1], p.color[2], 1.);
|
||||
let pos = [p.position[0] * hw, p.position[1] * hh];
|
||||
|
||||
(pos, color)
|
||||
|
||||
});
|
||||
|
||||
draw.polyline()
|
||||
.weight(thickness)
|
||||
.join_round()
|
||||
.points_colored(vertices);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
draw.to_frame(app, &frame).unwrap();
|
||||
|
||||
}
|
||||
|
||||
|
||||
const LASER_PREVIEW_WIDTH: f32 = 1024.;
|
||||
const LASER_PREVIEW_HEIGHT: f32 = 1024.;
|
||||
|
||||
|
|
@ -1135,16 +1364,19 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
|
|||
// draw.background().color(srgba(0.3,0.3,0.3,1.));
|
||||
draw.background().color(srgba(0.,0.,0., 1.));
|
||||
|
||||
let win = app.window_rect();
|
||||
|
||||
let win_id = frame.window_id();
|
||||
let win = app.window(win_id).unwrap();
|
||||
|
||||
let w = LASER_PREVIEW_WIDTH;
|
||||
let h = LASER_PREVIEW_HEIGHT;
|
||||
let hh = h / 2.;
|
||||
let hw = w / 2.;
|
||||
let (w, h) = win.inner_size_pixels();
|
||||
;
|
||||
let hh = (h as f32) / 2.;
|
||||
let hw = (w as f32) / 2.;
|
||||
|
||||
let thickness = 3.0;
|
||||
|
||||
let win_rect = app.main_window().rect().pad(20.0);
|
||||
|
||||
let win_rect = win.rect().pad(20.0);
|
||||
|
||||
match &model.selected_stream {
|
||||
None => {
|
||||
|
|
@ -1157,7 +1389,7 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
|
|||
// let stream = model.laser_streams.get(&dac_id); //.expect("Selected stream not found in configs");
|
||||
|
||||
// 1. get config for laser
|
||||
let config: &DacConfig = model.per_laser_config.get(&dac_id).expect("Selected stream not found in configs");
|
||||
let config: &DacConfig = model.per_laser_config.get(&dac_id).expect(&format!("Selected stream not found in configs {:?} {:?}", &dac_id, &model.per_laser_config));
|
||||
|
||||
// 2. clipping mask + its anchor points
|
||||
|
||||
|
|
@ -1249,11 +1481,11 @@ fn laser_preview_mouse_pressed(app: &App, model: &mut GuiModel, button: MouseBut
|
|||
Some(d) => d,
|
||||
};
|
||||
|
||||
|
||||
let config = model.per_laser_config.get_mut(&dac_id).expect("Dac config unavailable");
|
||||
|
||||
let half_w = LASER_PREVIEW_WIDTH / 2.;
|
||||
let half_h = LASER_PREVIEW_HEIGHT / 2.;
|
||||
|
||||
let (w, h) = app.main_window().inner_size_pixels();
|
||||
let half_w = w as f32 / 2.;
|
||||
let half_h = h as f32 / 2.;
|
||||
|
||||
let laser_x = app.mouse.x / half_w;
|
||||
let laser_y = app.mouse.y / half_h;
|
||||
|
|
@ -1342,8 +1574,9 @@ fn laser_mouse_moved(app: &App, model: &mut GuiModel, pos: Point2) {
|
|||
Some(d) => d,
|
||||
};
|
||||
|
||||
let half_w = LASER_PREVIEW_WIDTH / 2.;
|
||||
let half_h = LASER_PREVIEW_HEIGHT / 2.;
|
||||
let (w, h) = app.main_window().inner_size_pixels();
|
||||
let half_w = w as f32 / 2.;
|
||||
let half_h = h as f32 / 2.;
|
||||
|
||||
let laser_x = app.mouse.x / half_w;
|
||||
let laser_y = app.mouse.y / half_h;
|
||||
|
|
@ -1645,16 +1878,16 @@ fn map_mouse_pressed(app: &App, model: &mut GuiModel, button: MouseButton) {
|
|||
}
|
||||
|
||||
|
||||
let half_w = (1024 / 2) as f32;
|
||||
let half_h = (1024 / 2) as f32;
|
||||
// let half_w = (1024 / 2) as f32;
|
||||
// let half_h = (1024 / 2) as f32;
|
||||
|
||||
let x = app.mouse.x / half_w;
|
||||
let y = app.mouse.y / half_h;
|
||||
// let x = app.mouse.x / half_w;
|
||||
// let y = app.mouse.y / half_h;
|
||||
|
||||
if x > 1. || x < -1. || y > 1. || y < -1. {
|
||||
println!("Click outside of canvas: {} {}", x, y);
|
||||
return
|
||||
}
|
||||
// if x > 1. || x < -1. || y > 1. || y < -1. {
|
||||
// println!("Click outside of canvas: {} {}", x, y);
|
||||
// return
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,11 @@ pub struct DacConfig{
|
|||
pub filters: PointFilters,
|
||||
#[serde(default = "default_layers_enabled")]
|
||||
pub layers_enabled: u8,
|
||||
|
||||
// window specific settings
|
||||
pub window_monitor: String,
|
||||
pub window_enabled: bool,
|
||||
pub window_fullscreen: bool,
|
||||
}
|
||||
|
||||
fn default_layers_enabled() -> u8 {
|
||||
|
|
@ -159,7 +164,10 @@ impl Default for DacConfig{
|
|||
fn default() -> DacConfig{
|
||||
//DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY }
|
||||
// DacConfig { name: "Unknown".into(), homography: LASER_H_CM }
|
||||
DacConfig { name: "Unknown".into(), source: StreamSource::CurrentLayers, filters: PointFilters::default().with_homography(LASER_H), layers_enabled: 0b1111_1111 }
|
||||
DacConfig {
|
||||
name: "Unknown".into(), source: StreamSource::CurrentLayers, filters: PointFilters::default().with_homography(LASER_H), layers_enabled: 0b1111_1111 ,
|
||||
window_monitor: "".into(), window_enabled: false, window_fullscreen: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue