use ratatui::layout::Rect;
#[derive(Debug, Clone)]
pub struct ClickRegion<T: Clone> {
pub area: Rect,
pub data: T,
}
impl<T: Clone> ClickRegion<T> {
pub fn new(area: Rect, data: T) -> Self {
Self { area, data }
}
pub fn contains(&self, col: u16, row: u16) -> bool {
col >= self.area.x
&& col < self.area.x + self.area.width
&& row >= self.area.y
&& row < self.area.y + self.area.height
}
}
pub trait Clickable {
type ClickAction: Clone;
fn click_regions(&self) -> &[ClickRegion<Self::ClickAction>];
fn handle_click(&self, col: u16, row: u16) -> Option<Self::ClickAction> {
self.click_regions()
.iter()
.find(|r| r.contains(col, row))
.map(|r| r.data.clone())
}
}
#[derive(Debug, Clone)]
pub struct ClickRegionRegistry<T: Clone> {
regions: Vec<ClickRegion<T>>,
}
impl<T: Clone> Default for ClickRegionRegistry<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Clone> ClickRegionRegistry<T> {
pub fn new() -> Self {
Self {
regions: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
regions: Vec::with_capacity(capacity),
}
}
pub fn clear(&mut self) {
self.regions.clear();
}
pub fn register(&mut self, area: Rect, data: T) {
self.regions.push(ClickRegion::new(area, data));
}
pub fn handle_click(&self, col: u16, row: u16) -> Option<&T> {
self.regions
.iter()
.find(|r| r.contains(col, row))
.map(|r| &r.data)
}
pub fn regions(&self) -> &[ClickRegion<T>] {
&self.regions
}
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn len(&self) -> usize {
self.regions.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_click_region_contains() {
let region = ClickRegion::new(Rect::new(10, 5, 20, 3), "test");
assert!(region.contains(10, 5)); assert!(region.contains(29, 7)); assert!(region.contains(20, 6));
assert!(!region.contains(9, 5)); assert!(!region.contains(30, 5)); assert!(!region.contains(10, 4)); assert!(!region.contains(10, 8)); }
#[test]
fn test_click_region_zero_size() {
let region = ClickRegion::new(Rect::new(5, 5, 0, 0), "test");
assert!(!region.contains(5, 5));
}
#[test]
fn test_registry_basic_operations() {
let mut registry: ClickRegionRegistry<&str> = ClickRegionRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
registry.register(Rect::new(0, 0, 10, 1), "first");
registry.register(Rect::new(15, 0, 10, 1), "second");
assert!(!registry.is_empty());
assert_eq!(registry.len(), 2);
registry.clear();
assert!(registry.is_empty());
}
#[test]
fn test_registry_handle_click() {
let mut registry: ClickRegionRegistry<i32> = ClickRegionRegistry::new();
registry.register(Rect::new(0, 0, 10, 1), 1);
registry.register(Rect::new(15, 0, 10, 1), 2);
registry.register(Rect::new(0, 2, 25, 2), 3);
assert_eq!(registry.handle_click(5, 0), Some(&1));
assert_eq!(registry.handle_click(20, 0), Some(&2));
assert_eq!(registry.handle_click(12, 3), Some(&3));
assert_eq!(registry.handle_click(12, 0), None);
assert_eq!(registry.handle_click(100, 100), None);
}
#[test]
fn test_registry_overlapping_regions() {
let mut registry: ClickRegionRegistry<&str> = ClickRegionRegistry::new();
registry.register(Rect::new(0, 0, 20, 2), "back");
registry.register(Rect::new(5, 0, 10, 1), "front");
assert_eq!(registry.handle_click(7, 0), Some(&"back"));
assert_eq!(registry.handle_click(2, 1), Some(&"back"));
}
#[test]
fn test_clickable_trait() {
#[derive(Clone, PartialEq, Debug)]
enum Action {
Click,
}
struct ClickableWidget {
regions: Vec<ClickRegion<Action>>,
}
impl Clickable for ClickableWidget {
type ClickAction = Action;
fn click_regions(&self) -> &[ClickRegion<Self::ClickAction>] {
&self.regions
}
}
let widget = ClickableWidget {
regions: vec![ClickRegion::new(Rect::new(0, 0, 10, 1), Action::Click)],
};
assert_eq!(widget.handle_click(5, 0), Some(Action::Click));
assert_eq!(widget.handle_click(15, 0), None);
}
}