use crate::{
storage::{
asset::{
Asset,
output_device::{OutputDevice, routing::OutputRouting},
},
asset_id::AssetId,
collections::Collections,
},
ui::{
asset::CollectionsChangeButton,
window_common::{default_viewport_builder, gled_window_frame},
windows::channel_overwrites,
},
};
use egui::{
Color32, ComboBox, Context, Id, RichText, Vec2, ViewportId, mutex::Mutex,
scroll_area::ScrollBarVisibility::AlwaysVisible,
};
use once_cell::sync::Lazy;
use std::collections::BTreeMap;
pub struct ChannelOverwritesWindow {
open: bool,
device: Option<AssetId<OutputDevice>>,
universe: Option<u16>,
channel: u16,
value: u8,
}
impl Default for ChannelOverwritesWindow {
fn default() -> Self {
Self {
open: false,
device: Default::default(),
universe: Default::default(),
channel: 1,
value: Default::default(),
}
}
}
impl ChannelOverwritesWindow {
#[cfg_attr(feature = "profiling", profiling::function)]
pub fn update(&mut self, ctx: &Context, collections: &mut Collections) {
if !self.open {
return;
}
ctx.show_viewport_immediate(
ViewportId(Id::new("channel overwrites window")),
default_viewport_builder()
.with_inner_size(Vec2::new(600.0, 500.0))
.with_min_inner_size(Vec2::new(600.0, 500.0)),
|ctx, _viewport_class| {
ctx.input(|input| {
if input.viewport().close_requested() {
self.close();
}
});
gled_window_frame(ctx, "Channel Overwrites", |ui| {
let mut channel_overwrites = channel_overwrites::ChannelOverwrites::get();
ui.horizontal(|ui| {
if self.device.collections_change_button(ui, collections) {
self.universe.take();
}
if let Some(device) = self.device.and_then(|id|Asset::get(id, collections)) {
let device_universes = device.data.universes();
if !device_universes.is_empty() {
ComboBox::new("channel_overwrites_universe", "")
.selected_text(match self.universe {
None => "No universe selected".to_string(),
Some(universe) => format!("Universe {universe}"),
})
.width(150.0)
.show_ui(ui, |ui| {
for device_universe in device_universes {
let res = ui.selectable_value(
&mut self.universe,
Some(*device_universe),
format!("Universe {device_universe}"),
);
if res.changed() {
self.universe = Some(*device_universe);
}
}
});
}
ui.add(egui::DragValue::new(&mut self.channel).range(1..=512).prefix("Channel: "));
ui.add_enabled(device_universes.is_empty() || self.universe.is_some(), egui::DragValue::new(&mut self.value).range(0..=256).prefix("Value: "));
if ui.button("Add/Update").on_hover_text(
"Add or update the channel overwrite for the selected device, universe and channel",
).clicked() {
channel_overwrites.0.insert(
channel_overwrites::ChannelIdentifier {
device: device.id,
universe: self.universe,
channel: self.channel,
},
self.value,
);
channel_overwrites.clone().set();
}
}
});
ui.separator();
egui::ScrollArea::vertical()
.scroll_bar_visibility(AlwaysVisible)
.id_salt("channel_overwrites_scroll")
.show(ui, |ui| {
ui.set_min_width(ui.available_width());
for (channel_identifier, value) in channel_overwrites.0.iter() {
ui.horizontal(|ui| {
let device_name = Asset::get(channel_identifier.device, collections)
.map(|d| d.name().to_owned())
.unwrap_or_else(|| {
format!(
"Unknown Device ({})",
channel_identifier.device
)
});
if ui.label(if let Some(universe) = channel_identifier.universe { format!(
"{device_name} - Universe {universe} - Channel {}:",
channel_identifier.channel
)} else {
format!("{device_name} - Channel {}:", channel_identifier.channel)
}).clicked() {
self.device = Some(channel_identifier.device);
self.universe = channel_identifier.universe;
self.channel = channel_identifier.channel;
self.value = *value;
};
ui.label(RichText::new(value.to_string()).color(Color32::YELLOW));
if ui.button("❌").on_hover_text("Remove this channel overwrite").clicked() {
let mut channel_overwrites = channel_overwrites.clone();
channel_overwrites.0.remove(channel_identifier);
channel_overwrites.set();
}
});
}
});
});
},
);
}
pub fn open(&mut self) {
self.open = true;
}
pub fn close(&mut self) {
self.open = false;
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub struct ChannelOverwrites(
#[serde(with = "serde_channel_overwrites_map")] BTreeMap<ChannelIdentifier, u8>,
);
static CHANNEL_OVERWRITES: Lazy<Mutex<ChannelOverwrites>> =
Lazy::new(|| Mutex::new(ChannelOverwrites::default()));
impl ChannelOverwrites {
pub fn get() -> ChannelOverwrites {
CHANNEL_OVERWRITES.lock().clone()
}
pub fn set(self) {
*CHANNEL_OVERWRITES.lock() = self;
}
pub fn overwrite_data(
&mut self,
device: AssetId<OutputDevice>,
universe: Option<u16>,
data: &mut [u8],
) {
self.0.retain(|channel_identifier, value| {
if channel_identifier.device == device && channel_identifier.universe == universe {
data[channel_identifier.channel as usize - 1] = *value;
false
} else {
true
}
});
}
pub fn overwritten_universes(self) -> Vec<(OutputRouting, [u8; 512])> {
let mut overwritten_universes = BTreeMap::new();
for (channel_identifier, value) in self.0.into_iter() {
overwritten_universes
.entry(OutputRouting {
device: Some(channel_identifier.device),
universe: channel_identifier.universe,
})
.or_insert([0; 512])[channel_identifier.channel as usize - 1] = value;
}
overwritten_universes.into_iter().collect()
}
}
#[derive(
Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, serde::Deserialize, serde::Serialize,
)]
pub struct ChannelIdentifier {
pub device: AssetId<OutputDevice>,
pub universe: Option<u16>,
pub channel: u16,
}
mod serde_channel_overwrites_map {
use super::ChannelIdentifier;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;
pub fn serialize<S>(
map: &BTreeMap<ChannelIdentifier, u8>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let vec: Vec<(&ChannelIdentifier, &u8)> = map.iter().collect();
vec.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<ChannelIdentifier, u8>, D::Error>
where
D: Deserializer<'de>,
{
let vec: Vec<(ChannelIdentifier, u8)> = Vec::deserialize(deserializer)?;
Ok(vec.into_iter().collect())
}
}