use crate::coord::RuVec2;
use crate::geom::Viewport;
use crate::paint;
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PaneId(u32);
#[derive(Clone, Copy, Debug)]
pub struct Pane {
id: PaneId,
z: u8,
pub center: RuVec2,
pub extent: RuVec2,
pub background: u32,
}
impl Pane {
pub fn id(&self) -> PaneId {
self.id
}
pub fn z(&self) -> u8 {
self.z
}
pub fn contains(&self, p: RuVec2) -> bool {
let dx = (p.x - self.center.x).abs();
let dy = (p.y - self.center.y).abs();
dx <= self.extent.x && dy <= self.extent.y
}
pub fn min_corner(&self) -> RuVec2 {
self.center - self.extent
}
pub fn max_corner(&self) -> RuVec2 {
self.center + self.extent
}
}
pub struct Compositor {
panes: Vec<Pane>,
next_id: u32,
focused: Option<PaneId>,
viewport: Viewport,
}
impl Compositor {
pub fn new(viewport: Viewport) -> Self {
Self {
panes: Vec::new(),
next_id: 0,
focused: None,
viewport,
}
}
pub fn viewport(&self) -> Viewport {
self.viewport
}
pub fn resize(&mut self, width_px: u32, height_px: u32) {
let ru = self.viewport.ru;
self.viewport = Viewport::new(width_px, height_px).with_ru(ru);
}
pub fn insert(&mut self, center: RuVec2, extent: RuVec2, background: u32) -> PaneId {
let id = PaneId(self.next_id);
self.next_id += 1;
let z = self.panes.len() as u8;
self.panes.push(Pane {
id,
z,
center,
extent,
background,
});
id
}
pub fn remove(&mut self, id: PaneId) {
if let Some(pos) = self.panes.iter().position(|p| p.id == id) {
self.panes.remove(pos);
self.renumber_z();
if self.focused == Some(id) {
self.focused = None;
}
}
}
pub fn get(&self, id: PaneId) -> Option<&Pane> {
self.panes.iter().find(|p| p.id == id)
}
pub fn get_mut(&mut self, id: PaneId) -> Option<&mut Pane> {
self.panes.iter_mut().find(|p| p.id == id)
}
pub fn iter(&self) -> impl Iterator<Item = &Pane> {
self.panes.iter()
}
pub fn len(&self) -> usize {
self.panes.len()
}
pub fn is_empty(&self) -> bool {
self.panes.is_empty()
}
pub fn hit_test(&self, p: RuVec2) -> Option<PaneId> {
self.panes
.iter()
.rev()
.find(|pane| pane.contains(p))
.map(|pane| pane.id)
}
pub fn focus(&mut self, id: PaneId) {
if self.panes.iter().any(|p| p.id == id) {
self.focused = Some(id);
}
}
pub fn focused(&self) -> Option<PaneId> {
self.focused
}
pub fn bring_to_front(&mut self, id: PaneId) {
if let Some(pos) = self.panes.iter().position(|p| p.id == id) {
let pane = self.panes.remove(pos);
self.panes.push(pane);
self.renumber_z();
}
}
pub fn send_to_back(&mut self, id: PaneId) {
if let Some(pos) = self.panes.iter().position(|p| p.id == id) {
let pane = self.panes.remove(pos);
self.panes.insert(0, pane);
self.renumber_z();
}
}
pub fn render(&self, target: &mut [u32], buf_w: usize, buf_h: usize) {
for pane in self.panes.iter().rev() {
let (cx, cy) = self.viewport.ru_to_px(pane.center);
let ex = self.viewport.ru_to_px_d(pane.extent.x);
let ey = self.viewport.ru_to_px_d(pane.extent.y);
let x = cx - ex;
let y = cy - ey;
let w = ex + ex;
let h = ey + ey;
let mut pane_damage = crate::canvas::Damage::new();
let mut canvas = crate::canvas::Canvas::new(target, buf_w, buf_h, &mut pane_damage);
paint::fill_rect(&mut canvas, x, y, w, h, pane.background, None, None);
}
}
fn renumber_z(&mut self) {
for (i, pane) in self.panes.iter_mut().enumerate() {
pane.z = i as u8;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::paint::{pack_argb, unpack_argb};
fn make_compositor(w: u32, h: u32) -> Compositor {
Compositor::new(Viewport::new(w, h))
}
#[test]
fn insert_assigns_unique_ids() {
let mut c = make_compositor(800, 600);
let a = c.insert(RuVec2::ZERO, RuVec2::splat(0.1), 0xFF000000);
let b = c.insert(RuVec2::ZERO, RuVec2::splat(0.1), 0xFF000000);
assert_ne!(a, b);
assert_eq!(c.len(), 2);
}
#[test]
fn contains_inclusive_on_edges() {
let mut c = make_compositor(800, 600);
let id = c.insert(RuVec2::new(0.0, 0.0), RuVec2::new(0.2, 0.1), 0xFF000000);
let pane = c.get(id).unwrap();
assert!(pane.contains(RuVec2::new(0.0, 0.0)));
assert!(pane.contains(RuVec2::new(0.2, 0.1)));
assert!(pane.contains(RuVec2::new(-0.2, -0.1)));
assert!(!pane.contains(RuVec2::new(0.21, 0.0)));
assert!(!pane.contains(RuVec2::new(0.0, 0.11)));
}
#[test]
fn hit_test_picks_topmost() {
let mut c = make_compositor(800, 600);
let bottom = c.insert(RuVec2::new(0.0, 0.0), RuVec2::splat(0.5), 0xFF000000);
let top = c.insert(RuVec2::new(0.0, 0.0), RuVec2::splat(0.5), 0xFF000000);
assert_eq!(c.hit_test(RuVec2::ZERO), Some(top));
c.remove(top);
let small_top = c.insert(RuVec2::new(0.0, 0.0), RuVec2::splat(0.1), 0xFF000000);
assert_eq!(c.hit_test(RuVec2::ZERO), Some(small_top));
assert_eq!(c.hit_test(RuVec2::new(0.3, 0.0)), Some(bottom));
}
#[test]
fn hit_test_returns_none_for_empty_space() {
let mut c = make_compositor(800, 600);
c.insert(RuVec2::new(0.4, 0.0), RuVec2::splat(0.05), 0xFF000000);
assert_eq!(c.hit_test(RuVec2::ZERO), None);
}
#[test]
fn bring_to_front_moves_pane_to_top() {
let mut c = make_compositor(800, 600);
let a = c.insert(RuVec2::ZERO, RuVec2::splat(0.5), 0xFF000000);
let b = c.insert(RuVec2::ZERO, RuVec2::splat(0.5), 0xFF000000);
let cc = c.insert(RuVec2::ZERO, RuVec2::splat(0.5), 0xFF000000);
assert_eq!(c.hit_test(RuVec2::ZERO), Some(cc));
c.bring_to_front(a);
assert_eq!(c.hit_test(RuVec2::ZERO), Some(a));
assert_eq!(c.get(a).unwrap().z(), 2);
assert_eq!(c.get(b).unwrap().z(), 0);
assert_eq!(c.get(cc).unwrap().z(), 1);
}
#[test]
fn send_to_back_moves_pane_to_bottom() {
let mut c = make_compositor(800, 600);
let a = c.insert(RuVec2::ZERO, RuVec2::splat(0.5), 0xFF000000);
let b = c.insert(RuVec2::ZERO, RuVec2::splat(0.5), 0xFF000000);
c.send_to_back(b);
assert_eq!(c.get(b).unwrap().z(), 0);
assert_eq!(c.get(a).unwrap().z(), 1);
assert_eq!(c.hit_test(RuVec2::ZERO), Some(a));
}
#[test]
fn focus_clears_on_remove() {
let mut c = make_compositor(800, 600);
let id = c.insert(RuVec2::ZERO, RuVec2::splat(0.5), 0xFF000000);
c.focus(id);
assert_eq!(c.focused(), Some(id));
c.remove(id);
assert_eq!(c.focused(), None);
}
#[test]
fn render_fills_pane_pixels() {
let mut c = make_compositor(8, 8);
c.insert(
RuVec2::new(0.0, 0.0),
RuVec2::splat(0.25),
pack_argb(255, 0, 0, 255),
);
let mut buf = vec![0u32; 8 * 8];
c.render(&mut buf, 8, 8);
let center = buf[4 * 8 + 4];
let (r, g, b, _) = unpack_argb(center);
assert!(
r >= 0xFE && g <= 1 && b <= 1,
"center pixel = {:#010x}",
center
);
}
#[test]
fn resize_preserves_pane_ru_layout() {
let mut c = make_compositor(800, 600);
let id = c.insert(RuVec2::new(0.1, -0.2), RuVec2::splat(0.15), 0xFF112233);
let center_before = c.get(id).unwrap().center;
c.resize(1920, 1080);
let center_after = c.get(id).unwrap().center;
assert_eq!(center_before, center_after);
assert_eq!(c.viewport().width_px, 1920);
}
}