use crate::canvas::multi_value::{MultiValue, shown};
use crate::fixture_definition_editor::edit::{DefinitionEdit, Edit};
use crate::fixture_definition_editor::generators::DEFAULT_PIXEL_PREFIX;
use crate::fixture_definition_editor::state::EditorState;
use crate::fixture_definition_editor::units::millimetres;
use egui::{ComboBox, DragValue, Grid, Ui};
use gled_model::patching::definition::{ColorOrder, FixtureDefinition, Pixel};
use strum::IntoEnumIterator;
pub(crate) fn show(ui: &mut Ui, edit: &mut Edit<'_>, state: &mut EditorState) {
let selection: Vec<usize> = state.selection.iter().copied().collect();
let pixels: Vec<&Pixel> = selection
.iter()
.filter_map(|pixel| edit.value.pixels.get(*pixel))
.collect();
match pixels.as_slice() {
[] => {
ui.weak("Click a pixel, or drag a band around several.");
return;
}
[pixel] => ui.strong(&pixel.name),
_ => ui.strong(format!("{} pixels", pixels.len())),
};
let collect =
|value: fn(&Pixel) -> String| MultiValue::collect(pixels.iter().map(|pixel| value(pixel)));
let model = MultiValue::collect(pixels.iter().map(|pixel| pixel.model));
let order = collect(|pixel| {
pixel
.color_order()
.map_or("custom".to_string(), |order| order.to_string())
});
let differs = |values: Option<MultiValue<u32>>| matches!(values, Some(MultiValue::Differing));
let position_differs = [0, 1, 2].map(|axis| {
differs(MultiValue::collect(
pixels.iter().map(|pixel| pixel.position_m[axis].to_bits()),
))
});
let rotation_differs = differs(MultiValue::collect(
pixels.iter().map(|pixel| pixel.rotation.to_bits()),
));
let first = pixels[0].clone();
let dmx = dmx_span(edit.value, &selection);
Grid::new("pixel inspector").num_columns(2).show(ui, |ui| {
if selection.len() == 1 {
ui.label("Name");
let mut name = first.name.clone();
let response = ui.text_edit_singleline(&mut name);
edit.tracked(&response, |definition| {
if let Some(pixel) = definition.pixels.get_mut(selection[0]) {
pixel.name = name;
}
});
ui.end_row();
}
for (axis, label) in ["X", "Y", "Z"].into_iter().enumerate() {
ui.label(label);
let mut metres = first.position_m[axis];
let mut value = millimetres(&mut metres);
if position_differs[axis] {
value = value.custom_formatter(|_, _| "-".to_string());
}
let response = ui.add(value);
edit.tracked(&response, |definition| {
for pixel in &selection {
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.position_m[axis] = metres;
}
}
});
ui.end_row();
}
ui.label("Rotation");
let mut degrees = first.rotation.to_degrees();
let mut value = DragValue::new(&mut degrees).speed(1.0).suffix("°");
if rotation_differs {
value = value.custom_formatter(|_, _| "-".to_string());
}
let response = ui.add(value);
edit.tracked(&response, |definition| {
for pixel in &selection {
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.rotation = degrees.to_radians();
}
}
});
ui.end_row();
ui.label("Model");
let model_name = |index: usize| {
edit.value
.model(index)
.map_or(format!("missing {index}"), |model| model.name.clone())
};
let mut chosen = None;
ComboBox::from_id_salt("pixel model")
.selected_text(match &model {
Some(MultiValue::Same(index)) => model_name(*index),
_ => "-".to_string(),
})
.show_ui(ui, |ui| {
for index in 0..edit.value.models.len() {
if ui
.selectable_label(
model.as_ref().and_then(MultiValue::same) == Some(&index),
model_name(index),
)
.clicked()
{
chosen = Some(index);
}
}
});
if let Some(index) = chosen {
let selected = state.selection.clone();
edit.try_change(state, |definition| {
definition.set_model_of(&selected, index)
});
}
ui.end_row();
ui.label("Channel order");
let mut picked = None;
ComboBox::from_id_salt("pixel order")
.selected_text(shown(&order))
.show_ui(ui, |ui| {
for candidate in ColorOrder::iter() {
let current =
order.as_ref().and_then(MultiValue::same) == Some(&candidate.to_string());
if ui
.selectable_label(current, candidate.to_string())
.clicked()
{
picked = Some(candidate);
}
}
});
if let Some(candidate) = picked {
edit.change(state, |definition| {
for pixel in &selection {
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.set_color_order(candidate);
}
}
});
}
ui.end_row();
ui.label("DMX");
ui.label(dmx);
ui.end_row();
});
if selection.len() > 1 {
rename_all(ui, edit, state, &selection);
}
}
fn dmx_span(definition: &FixtureDefinition, selection: &[usize]) -> String {
let spans: Vec<(u16, u16)> = selection
.iter()
.filter_map(|pixel| {
let found = definition.pixels.get(*pixel)?;
let first = definition.channel_offset(*pixel).saturating_add(1);
let width = u16::try_from(found.channels.len().max(1)).unwrap_or(u16::MAX);
Some((first, first.saturating_add(width - 1)))
})
.collect();
match (
spans.iter().map(|span| span.0).min(),
spans.iter().map(|span| span.1).max(),
) {
(Some(first), Some(last)) => format!("ch {first}–{last}"),
_ => String::new(),
}
}
fn rename_all(ui: &mut Ui, edit: &mut Edit<'_>, state: &mut EditorState, selection: &[usize]) {
let id = ui.id().with("rename prefix");
let mut prefix = ui
.data(|data| data.get_temp::<String>(id))
.unwrap_or_else(|| DEFAULT_PIXEL_PREFIX.to_string());
ui.horizontal(|ui| {
ui.label("Rename as");
ui.add(egui::TextEdit::singleline(&mut prefix).desired_width(80.0));
if ui.button("1, 2, 3…").clicked() {
let prefix = prefix.clone();
edit.change(state, |definition| {
for (number, pixel) in selection.iter().enumerate() {
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.name = format!("{prefix}{}", number + 1);
}
}
});
}
});
ui.data_mut(|data| data.insert_temp(id, prefix));
}