use {
crossterm::event::MouseEvent,
ratatui::layout::{Constraint, Direction, Layout, Rect},
std::collections::HashMap,
};
#[derive(Debug, Default)]
pub struct MouseArea {
zones: HashMap<String, Rect>,
}
pub struct MouseDispatcher<'a> {
zones: Vec<(Rect, Box<dyn Fn(&MouseEvent) + 'a>)>,
}
impl<'a> MouseDispatcher<'a> {
pub fn new() -> Self { Self { zones: Vec::new() } }
pub fn track<F: Fn(&MouseEvent) + 'a>(&mut self, area: Rect, handler: F) { self.zones.push((area, Box::new(handler))); }
pub fn hit(&self, col: u16, row: u16) -> bool {
self.zones.iter().any(|(r, _)| r.left() <= col && col < r.right() && r.top() <= row && row < r.bottom())
}
pub fn dispatch(&self, ev: &MouseEvent) {
for (area, handler) in &self.zones {
if ev.column >= area.left() && ev.column < area.right() && ev.row >= area.top() && ev.row < area.bottom() {
handler(ev);
}
}
}
pub fn dispatch_top(&self, ev: &MouseEvent) {
for (area, handler) in &self.zones {
if ev.column >= area.left() && ev.column < area.right() && ev.row >= area.top() && ev.row < area.bottom() {
handler(ev);
return;
}
}
}
}
impl MouseArea {
pub fn new() -> Self { Self::default() }
pub fn track(&mut self, name: impl Into<String>, rect: Rect) { self.zones.insert(name.into(), rect); }
pub fn hit(&self, name: &str, ev: &MouseEvent) -> bool {
self.zones
.get(name)
.map(|r| {
let x = ev.column;
let y = ev.row;
x >= r.left() && x < r.right() && y >= r.top() && y < r.bottom()
})
.unwrap_or(false)
}
pub fn zones(&self) -> &HashMap<String, Rect> { &self.zones }
pub fn clear(&mut self) { self.zones.clear(); }
}
pub struct ResponsiveLayout {
constraints: Vec<Constraint>,
direction: Direction,
margin: u16,
spacing: u16,
}
impl ResponsiveLayout {
pub fn new() -> Self { Self { constraints: Vec::new(), direction: Direction::Vertical, margin: 0, spacing: 0 } }
pub fn direction(mut self, d: Direction) -> Self {
self.direction = d;
self
}
pub fn margin(mut self, m: u16) -> Self {
self.margin = m;
self
}
pub fn spacing(mut self, s: u16) -> Self {
self.spacing = s;
self
}
pub fn push(&mut self, c: Constraint) { self.constraints.push(c); }
pub fn lengths<const N: usize>(self, constraints: [Constraint; N]) -> Self {
let mut me = self;
for c in &constraints {
me.constraints.push(*c);
}
me
}
pub fn split(&self, area: Rect) -> Vec<Rect> {
let mut l = Layout::new(self.direction, self.constraints.clone()).margin(self.margin);
if self.spacing > 0 {
l = l.spacing(self.spacing);
}
l.split(area).to_vec()
}
pub fn horizontal(area: Rect, constraints: &[Constraint]) -> Vec<Rect> {
Layout::horizontal(constraints).split(area).to_vec()
}
pub fn vertical(area: Rect, constraints: &[Constraint]) -> Vec<Rect> {
Layout::vertical(constraints).split(area).to_vec()
}
pub fn flex_row(&self, area: Rect, item_width: u16, item_height: u16) -> Vec<Rect> {
let mut rects = vec![];
let cols = area.width / item_width.max(1);
let rows = area.height / item_height.max(1);
for row in 0..rows {
for col in 0..cols {
let x = area.x + col * item_width;
let y = area.y + row * item_height;
if x + item_width <= area.right() && y + item_height <= area.bottom() {
rects.push(Rect { x, y, width: item_width, height: item_height });
}
}
}
rects
}
}
use ratatui::layout::Constraint as C;
pub fn fixed(n: u16) -> C { C::Length(n) }
pub fn fill(n: u16) -> C { C::Fill(n) }
pub fn min(n: u16) -> C { C::Min(n) }
pub fn max(n: u16) -> C { C::Max(n) }
pub fn percent(n: u16) -> C { C::Percentage(n) }
pub fn ratio(n: u32, d: u32) -> C { C::Ratio(n, d) }