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<dyn Fn(CanvasMessage) + Send + Sync + 'static>,
light_threshold: f32,
}
impl<'a> CanvasLayout<'a> {
pub fn new(callback: impl Fn(CanvasMessage) + Send + Sync + 'static) -> Self {
Self {
devices: Vec::new(),
coordinate_map: HashMap::new(),
callback: std::sync::Arc::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.get_pending(pad).unwrap(),
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);
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(&self) -> (u32, u32) {
let mut width = 0;
let mut height = 0;
for device in &self.devices {
let (device_width, device_height) = device.canvas.bounding_box();
width = u32::max(width, device_width);
height = u32::max(height, device_height);
}
(width, height)
}
fn low_level_get_pending(&self, x: u32, y: u32) -> Option<&Color> {
let pixel = self.coordinate_map.get(&(x, y))?;
Some(&pixel.color_new)
}
fn low_level_get_pending_mut(&mut self, x: u32, y: u32) -> Option<&mut Color> {
let pixel = self.coordinate_map.get_mut(&(x, y))?;
Some(&mut pixel.color_new)
}
fn low_level_get(&self, x: u32, y: u32) -> Option<&Color> {
let pixel = self.coordinate_map.get(&(x, y))?;
Some(&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
.low_level_get_pending_mut(local_x, local_y)
.unwrap() = transformed_color;
pixel.color_old = pixel.color_new;
}
for device in &mut self.devices {
device.canvas.flush()?;
}
Ok(())
}
}
impl_traits_for_canvas!(CanvasLayout['a]);