use ratatui::layout::Rect;
use std::fmt;
use std::time::{Duration, Instant};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ElementId(Uuid);
impl ElementId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
pub fn as_uuid(&self) -> Uuid {
self.0
}
}
impl Default for ElementId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ElementId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Region {
Top,
Center,
Bottom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Visibility {
Visible,
Hidden,
}
#[derive(Debug, Clone)]
pub struct ElementMetadata {
pub id: ElementId,
pub region: Region,
pub visibility: Visibility,
pub z_order: u32,
pub focusable: bool,
pub rect: Rect,
pub fixed_height: Option<u16>,
pub mouse_capture: Option<ElementId>,
}
impl ElementMetadata {
pub fn new(id: ElementId, region: Region) -> Self {
Self {
id,
region,
visibility: Visibility::Visible,
z_order: 0,
focusable: false,
rect: Rect::default(),
fixed_height: None,
mouse_capture: None,
}
}
pub fn with_visibility(mut self, visibility: Visibility) -> Self {
self.visibility = visibility;
self
}
pub fn with_z_order(mut self, z_order: u32) -> Self {
self.z_order = z_order;
self
}
pub fn with_focusable(mut self, focusable: bool) -> Self {
self.focusable = focusable;
self
}
pub fn with_fixed_height(mut self, height: u16) -> Self {
self.fixed_height = Some(height);
self
}
pub fn is_visible(&self) -> bool {
self.visibility == Visibility::Visible
}
pub fn can_receive_focus(&self) -> bool {
self.is_visible() && self.focusable
}
}
#[derive(Debug, Clone)]
pub struct LayoutState {
pub terminal_area: Rect,
pub top_area: Rect,
pub center_area: Rect,
pub bottom_area: Rect,
pub top_height: u16,
pub bottom_height: u16,
}
impl LayoutState {
pub fn new(terminal_area: Rect) -> Self {
Self {
terminal_area,
top_area: Rect::default(),
center_area: Rect::default(),
bottom_area: Rect::default(),
top_height: 0,
bottom_height: 0,
}
}
}
impl Default for LayoutState {
fn default() -> Self {
Self::new(Rect::default())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DirtyFlags {
pub layout_dirty: bool,
pub elements_dirty: bool,
}
impl DirtyFlags {
pub fn clean() -> Self {
Self {
layout_dirty: false,
elements_dirty: false,
}
}
pub fn all_dirty() -> Self {
Self {
layout_dirty: true,
elements_dirty: true,
}
}
pub fn set_layout_dirty(&mut self) {
self.layout_dirty = true;
}
pub fn set_elements_dirty(&mut self) {
self.elements_dirty = true;
}
pub fn needs_redraw(&self) -> bool {
self.layout_dirty || self.elements_dirty
}
pub fn clear(&mut self) {
self.layout_dirty = false;
self.elements_dirty = false;
}
}
impl Default for DirtyFlags {
fn default() -> Self {
Self::clean()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseCaptureState {
None,
Captured {
element_id: ElementId,
captured_at: Instant,
timeout: Option<Duration>,
},
}
impl MouseCaptureState {
pub fn is_captured(&self) -> bool {
matches!(self, MouseCaptureState::Captured { .. })
}
pub fn element_id(&self) -> Option<ElementId> {
match self {
MouseCaptureState::Captured { element_id, .. } => Some(*element_id),
MouseCaptureState::None => None,
}
}
pub fn is_expired(&self) -> bool {
match self {
MouseCaptureState::Captured {
captured_at,
timeout: Some(duration),
..
} => captured_at.elapsed() > *duration,
MouseCaptureState::Captured { timeout: None, .. } => false,
MouseCaptureState::None => false,
}
}
pub fn remaining_time(&self) -> Option<Duration> {
match self {
MouseCaptureState::Captured {
captured_at,
timeout: Some(duration),
..
} => {
let elapsed = captured_at.elapsed();
if elapsed >= *duration {
Some(Duration::ZERO)
} else {
Some(*duration - elapsed)
}
}
MouseCaptureState::Captured { timeout: None, .. } => None,
MouseCaptureState::None => None,
}
}
}
impl Default for MouseCaptureState {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone)]
pub struct MouseSnapshot {
pub captured_element: Option<ElementId>,
pub z_order_hits: Vec<(ElementId, Rect)>,
pub captured_at: Instant,
}
impl MouseSnapshot {
pub fn new(captured_element: Option<ElementId>, z_order_hits: Vec<(ElementId, Rect)>) -> Self {
Self {
captured_element,
z_order_hits,
captured_at: Instant::now(),
}
}
pub fn is_stale(&self, max_age: Duration) -> bool {
self.captured_at.elapsed() > max_age
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticInfo {
pub total_elements: usize,
pub visible_elements: usize,
pub focusable_elements: usize,
pub focused_element: Option<ElementId>,
pub captured_element: Option<ElementId>,
pub terminal_size: (u16, u16),
pub region_areas: Vec<(Region, Rect)>,
pub z_order_top: Vec<(ElementId, Region, u32)>,
pub dirty_flags: DirtyFlags,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeDebounceState {
Idle,
Pending {
pending_width: u16,
pending_height: u16,
scheduled_at: Instant,
},
}