use super::*;
use crate::Color;
use std::collections::HashMap;
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum Rotation {
None,
Left,
Right,
UpsideDown
}
impl Default for Rotation {
fn default() -> Self { Self::None }
}
impl std::ops::Neg for Rotation {
type Output = Self;
fn neg(self) -> Self {
match self {
Self::None => Self::None,
Self::UpsideDown => Self::UpsideDown,
Self::Left => Self::Right,
Self::Right => Self::Left
}
}
}
impl Rotation {
pub fn translate(self, x: i32, y: i32) -> (i32, i32) {
match self {
Self::None => (x, y),
Self::UpsideDown => (-x, -y),
Self::Left => (-y, x),
Self::Right => (y, -x),
}
}
}
struct LayoutDevice<'a> {
canvas: Box<dyn Canvas + 'a>,
rotation: Rotation,
x: u32,
y: u32,
}
fn to_local(x: u32, y: u32, rot: Rotation, x_offset: u32, y_offset: u32) -> (u32, u32) {
let x = x as i32;
let y = y as i32;
let (x, y) = (-rot).translate(x - x_offset as i32, y - y_offset as i32);
(x as u32, y as u32)
}
fn to_global(x: u32, y: u32, rot: Rotation, x_offset: u32, y_offset: u32) -> (u32, u32) {
let x = x as i32;
let y = y as i32;
let (x, y) = rot.translate(x, y);
let (x, y) = (x + x_offset as i32, y + y_offset as i32);
(x as u32, y as u32)
}
impl LayoutDevice<'_> {
fn to_local(&self, x: u32, y: u32) -> (u32, u32) {
to_local(x, y, self.rotation, self.x, self.y)
}
}
pub struct CanvasLayoutPoller {
receiver: std::sync::mpsc::Receiver<CanvasMessage>,
}
impl crate::MsgPollingWrapper for CanvasLayoutPoller {
type Message = CanvasMessage;
fn receiver(&self) -> &std::sync::mpsc::Receiver<Self::Message> { &self.receiver }
}
struct Pixel {
device_index: usize,
color_new: Color,
color_old: Color,
}
fn transform_color(color: Color, source: f32, target: f32) -> Color {
(color - 1.0) * (1.0 - target) / (1.0 - source) + 1.0
}
pub struct CanvasLayout<'a> {
devices: Vec<LayoutDevice<'a>>,
coordinate_map: HashMap<(u32, u32), Pixel>, callback: std::sync::Arc<Box<dyn Fn(CanvasMessage) + Send + Sync + 'static>>,
light_threshold: f32,
}
impl<'a> CanvasLayout<'a> {
pub fn new(callback: impl Fn(CanvasMessage) + Send + Sync + 'static) -> Self {
return Self {
devices: Vec::new(),
coordinate_map: HashMap::new(),
callback: std::sync::Arc::new(Box::new(callback)),
light_threshold: 1.0 / 4.0, };
}
pub fn new_polling() -> (Self, CanvasLayoutPoller) {
let (sender, receiver) = std::sync::mpsc::sync_channel(50);
let canvas = Self::new(move |msg| sender.send(msg)
.expect("Message receiver has hung up (this shouldn't happen)"));
let poller = CanvasLayoutPoller { receiver };
(canvas, poller)
}
pub fn light_threshold(&self) -> f32 { self.light_threshold }
pub fn set_light_threshold(&mut self, value: f32) { self.light_threshold = value }
pub fn add<C: 'a + Canvas, F, E>(&mut self,
x_offset: u32,
y_offset: u32,
rotation: Rotation,
creator: F
) -> Result<(), E>
where F: FnOnce(Box<dyn Fn(CanvasMessage) + Send + Sync + 'static>) -> Result<C, E> {
let callback = self.callback.clone();
let canvas = (creator)(Box::new(move |msg| {
let (x, y) = to_global(msg.x(), msg.y(), rotation, x_offset, y_offset);
match msg {
CanvasMessage::Press { .. } => (callback)(CanvasMessage::Press { x, y }),
CanvasMessage::Release { .. } => (callback)(CanvasMessage::Release { x, y }),
}
}))?;
let index = self.devices.len();
for pad in canvas.iter() {
let translated_coords = to_global(pad.x as u32, pad.y as u32, rotation, x_offset, y_offset);
let old_value = self.coordinate_map.insert(translated_coords, Pixel {
device_index: index,
color_new: canvas.at_new(pad),
color_old: canvas[pad],
});
if let Some(Pixel { device_index: old_device_index, .. }) = old_value {
panic!(
"Found overlap at ({}|{})! with canvas {} while adding canvas {} to layout (zero-indexed)",
translated_coords.0, translated_coords.1, old_device_index, self.devices.len(),
);
}
}
let layout_device = LayoutDevice {
canvas: Box::new(canvas),
rotation, x: x_offset, y: y_offset
};
self.devices.push(layout_device);
return Ok(());
}
pub fn add_by_guess<E: 'a + DeviceCanvasTrait>(&mut self,
x: u32, y: u32,
) -> Result<(), crate::MidiError> {
self.add(x, y, Rotation::None, DeviceCanvas::<E::Spec>::guess)
}
pub fn add_by_guess_rotated<E: 'a + DeviceCanvasTrait>(&mut self,
x: u32, y: u32, rotation: Rotation,
) -> Result<(), crate::MidiError> {
self.add(x, y, rotation, DeviceCanvas::<E::Spec>::guess)
}
}
impl Canvas for CanvasLayout<'_> {
fn lowest_visible_brightness(&self) -> f32 { self.light_threshold }
fn bounding_box_width(&self) -> u32 {
return self.devices.iter()
.map(|device| device.x + device.canvas.bounding_box_width())
.max().unwrap_or(0);
}
fn bounding_box_height(&self) -> u32 {
return self.devices.iter()
.map(|device| device.y + device.canvas.bounding_box_height())
.max().unwrap_or(0);
}
fn is_valid(&self, x: u32, y: u32) -> bool {
return self.coordinate_map.contains_key(&(x, y));
}
fn get_new_unchecked_ref(&self, x: u32, y: u32) -> &Color {
let pixel = self.coordinate_map.get(&(x, y)).unwrap();
&pixel.color_new
}
fn get_new_unchecked_mut(&mut self, x: u32, y: u32) -> &mut Color {
let pixel = self.coordinate_map.get_mut(&(x, y)).unwrap();
&mut pixel.color_new
}
fn get_old_unchecked_ref(&self, x: u32, y: u32) -> &Color {
let pixel = self.coordinate_map.get(&(x, y)).unwrap();
&pixel.color_old
}
fn flush(&mut self) -> Result<(), crate::MidiError> {
for (&(global_x, global_y), pixel) in self.coordinate_map.iter_mut() {
let device = &mut self.devices[pixel.device_index];
let transformed_color = transform_color(
pixel.color_new,
self.light_threshold,
device.canvas.lowest_visible_brightness(),
);
let (local_x, local_y) = device.to_local(global_x, global_y);
device.canvas.set_unchecked(local_x, local_y, transformed_color);
pixel.color_old = pixel.color_new;
}
for device in &mut self.devices {
device.canvas.flush()?;
}
return Ok(());
}
}
impl_traits_for_canvas!(<'a>, CanvasLayout);