#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PanelPosition {
pub col: u32,
pub row: u32,
}
impl PanelPosition {
#[must_use]
pub const fn new(col: u32, row: u32) -> Self {
Self { col, row }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PanelOrientation {
#[default]
Normal,
Rotated90,
Rotated180,
Rotated270,
}
#[derive(Debug, Clone)]
pub struct TopologyPanel {
pub panel_id: u32,
pub position: PanelPosition,
pub orientation: PanelOrientation,
pub width_px: u32,
pub height_px: u32,
pub active: bool,
}
impl TopologyPanel {
#[must_use]
pub fn new(panel_id: u32, position: PanelPosition, width_px: u32, height_px: u32) -> Self {
Self {
panel_id,
position,
orientation: PanelOrientation::Normal,
width_px,
height_px,
active: true,
}
}
#[must_use]
pub fn with_orientation(mut self, orientation: PanelOrientation) -> Self {
self.orientation = orientation;
self
}
#[must_use]
pub fn inactive(mut self) -> Self {
self.active = false;
self
}
#[must_use]
pub fn pixel_count(&self) -> u64 {
u64::from(self.width_px) * u64::from(self.height_px)
}
#[must_use]
pub fn is_portrait(&self) -> bool {
matches!(
self.orientation,
PanelOrientation::Rotated90 | PanelOrientation::Rotated270
)
}
}
#[derive(Debug, Default)]
pub struct PanelTopology {
panels: HashMap<PanelPosition, TopologyPanel>,
tile_width_px: u32,
tile_height_px: u32,
}
impl PanelTopology {
#[must_use]
pub fn new(tile_width_px: u32, tile_height_px: u32) -> Self {
Self {
panels: HashMap::new(),
tile_width_px,
tile_height_px,
}
}
#[must_use]
pub fn rectangular(cols: u32, rows: u32, tile_width_px: u32, tile_height_px: u32) -> Self {
let mut topology = Self::new(tile_width_px, tile_height_px);
let mut id = 0u32;
for row in 0..rows {
for col in 0..cols {
let pos = PanelPosition::new(col, row);
topology.add_panel(TopologyPanel::new(id, pos, tile_width_px, tile_height_px));
id += 1;
}
}
topology
}
pub fn add_panel(&mut self, panel: TopologyPanel) -> bool {
if self.panels.contains_key(&panel.position) {
return false;
}
self.panels.insert(panel.position, panel);
true
}
#[must_use]
pub fn get(&self, position: &PanelPosition) -> Option<&TopologyPanel> {
self.panels.get(position)
}
#[must_use]
pub fn panel_count(&self) -> usize {
self.panels.len()
}
#[must_use]
pub fn active_panel_count(&self) -> usize {
self.panels.values().filter(|p| p.active).count()
}
#[must_use]
pub fn total_pixel_count(&self) -> u64 {
self.panels
.values()
.filter(|p| p.active)
.map(TopologyPanel::pixel_count)
.sum()
}
#[must_use]
pub fn max_col(&self) -> Option<u32> {
self.panels.keys().map(|p| p.col).max()
}
#[must_use]
pub fn max_row(&self) -> Option<u32> {
self.panels.keys().map(|p| p.row).max()
}
#[must_use]
pub fn wall_width_px(&self) -> u32 {
match self.max_col() {
Some(c) => (c + 1) * self.tile_width_px,
None => 0,
}
}
#[must_use]
pub fn wall_height_px(&self) -> u32 {
match self.max_row() {
Some(r) => (r + 1) * self.tile_height_px,
None => 0,
}
}
#[must_use]
pub fn tile_dimensions(&self) -> (u32, u32) {
(self.tile_width_px, self.tile_height_px)
}
#[must_use]
pub fn global_to_local(
&self,
global_x: u32,
global_y: u32,
) -> Option<(PanelPosition, u32, u32)> {
if self.tile_width_px == 0 || self.tile_height_px == 0 {
return None;
}
let col = global_x / self.tile_width_px;
let row = global_y / self.tile_height_px;
let local_x = global_x % self.tile_width_px;
let local_y = global_y % self.tile_height_px;
let pos = PanelPosition::new(col, row);
if self.panels.contains_key(&pos) {
Some((pos, local_x, local_y))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_panel_position_eq() {
let p1 = PanelPosition::new(2, 3);
let p2 = PanelPosition::new(2, 3);
assert_eq!(p1, p2);
}
#[test]
fn test_topology_panel_pixel_count() {
let panel = TopologyPanel::new(0, PanelPosition::new(0, 0), 256, 128);
assert_eq!(panel.pixel_count(), 256 * 128);
}
#[test]
fn test_topology_panel_is_portrait() {
let panel = TopologyPanel::new(0, PanelPosition::new(0, 0), 256, 128)
.with_orientation(PanelOrientation::Rotated90);
assert!(panel.is_portrait());
let panel2 = TopologyPanel::new(1, PanelPosition::new(1, 0), 256, 128);
assert!(!panel2.is_portrait());
}
#[test]
fn test_topology_panel_inactive() {
let panel = TopologyPanel::new(0, PanelPosition::new(0, 0), 256, 128).inactive();
assert!(!panel.active);
}
#[test]
fn test_topology_rectangular_panel_count() {
let topo = PanelTopology::rectangular(4, 3, 256, 128);
assert_eq!(topo.panel_count(), 12);
}
#[test]
fn test_topology_rectangular_wall_size() {
let topo = PanelTopology::rectangular(4, 3, 256, 128);
assert_eq!(topo.wall_width_px(), 4 * 256);
assert_eq!(topo.wall_height_px(), 3 * 128);
}
#[test]
fn test_topology_active_panel_count() {
let mut topo = PanelTopology::new(256, 128);
topo.add_panel(TopologyPanel::new(0, PanelPosition::new(0, 0), 256, 128));
topo.add_panel(TopologyPanel::new(1, PanelPosition::new(1, 0), 256, 128).inactive());
assert_eq!(topo.active_panel_count(), 1);
}
#[test]
fn test_topology_total_pixel_count() {
let topo = PanelTopology::rectangular(2, 2, 100, 100);
assert_eq!(topo.total_pixel_count(), 40000);
}
#[test]
fn test_topology_add_panel_duplicate_position() {
let mut topo = PanelTopology::new(256, 128);
let p1 = TopologyPanel::new(0, PanelPosition::new(0, 0), 256, 128);
let p2 = TopologyPanel::new(1, PanelPosition::new(0, 0), 256, 128);
assert!(topo.add_panel(p1));
assert!(!topo.add_panel(p2)); }
#[test]
fn test_topology_get_existing() {
let mut topo = PanelTopology::new(256, 128);
let pos = PanelPosition::new(2, 1);
topo.add_panel(TopologyPanel::new(5, pos, 256, 128));
assert!(topo.get(&pos).is_some());
assert_eq!(topo.get(&pos).expect("should succeed in test").panel_id, 5);
}
#[test]
fn test_topology_get_nonexistent() {
let topo = PanelTopology::new(256, 128);
assert!(topo.get(&PanelPosition::new(99, 99)).is_none());
}
#[test]
fn test_topology_global_to_local() {
let topo = PanelTopology::rectangular(4, 3, 256, 128);
let result = topo.global_to_local(300, 150);
assert!(result.is_some());
let (pos, lx, ly) = result.expect("should succeed in test");
assert_eq!(pos, PanelPosition::new(1, 1));
assert_eq!(lx, 300 % 256);
assert_eq!(ly, 150 % 128);
}
#[test]
fn test_topology_global_to_local_out_of_bounds() {
let topo = PanelTopology::rectangular(2, 2, 256, 128);
let result = topo.global_to_local(5 * 256, 0);
assert!(result.is_none());
}
#[test]
fn test_topology_empty() {
let topo = PanelTopology::new(256, 128);
assert_eq!(topo.panel_count(), 0);
assert_eq!(topo.wall_width_px(), 0);
assert_eq!(topo.wall_height_px(), 0);
}
#[test]
fn test_panel_orientation_default() {
assert_eq!(PanelOrientation::default(), PanelOrientation::Normal);
}
#[test]
fn test_topology_tile_dimensions() {
let topo = PanelTopology::new(320, 180);
assert_eq!(topo.tile_dimensions(), (320, 180));
}
}