icmd 0.1.1

A command-line software framework
Documentation
//! Module handling events and listeners.

use crossterm::event;

use crate::{core::message::RawEvent, node::Node, utils::NodeRef};

/// Trait for representing an event that can be converted from a RawEvent.
pub trait Event: TryFrom<RawEvent> {}

/// Listener trait for handling events.
/// Returns a boolean indicating if event propagation should continue.
pub trait Listener {
    type EventType: Event;
    fn on_event(&mut self, node: NodeRef, event: Self::EventType) -> bool;
}

/// A NodeUpdate listener that updates a node based on a callback.
pub struct NodeUpdate(pub Box<dyn FnMut(NodeRef, u64) -> bool + Send + Sync>);

impl Listener for NodeUpdate {
    type EventType = u64;

    fn on_event(&mut self, node: NodeRef, t: Self::EventType) -> bool {
        (self.0)(node, t)
    }
}

/// A listener for mouse click events.
pub struct MouseClick(pub Box<dyn FnMut(NodeRef, MouseEvent) -> bool + Send + Sync>);

impl Listener for MouseClick {
    type EventType = MouseEvent;

    fn on_event(&mut self, node: NodeRef, event: Self::EventType) -> bool {
        // is within the node
        if (event.x as i32) >= Node::get_left(node.as_ref())
            && (event.x as i32) <= Node::get_right(node.as_ref())
            && (event.y as i32) >= Node::get_top(node.as_ref())
            && (event.y as i32) <= Node::get_bottom(node.as_ref())
        {
            let mut event = event.clone();
            event.window_x = event.x;
            event.window_y = event.y;
            event.x = event.x.saturating_sub(Node::get_left(node.as_ref()) as u16);
            event.y = event.y.saturating_sub(Node::get_top(node.as_ref()) as u16);
            (self.0)(node, event)
        } else {
            true
        }
    }
}

/// A listener for keyboard press events.
pub struct KeyboardPress(pub Box<dyn FnMut(NodeRef, KeyPressEvent) -> bool + Send + Sync>);

impl Listener for KeyboardPress {
    type EventType = KeyPressEvent;

    fn on_event(&mut self, node: NodeRef, event: Self::EventType) -> bool {
        (self.0)(node, event)
    }
}

impl Event for u64 {}

impl TryFrom<RawEvent> for u64 {
    type Error = ();

    /// Attempts to convert a RawEvent into a u64 update event.
    fn try_from(value: RawEvent) -> Result<Self, Self::Error> {
        if let RawEvent::Update(t) = value {
            Ok(t)
        } else {
            Err(())
        }
    }
}

/// Represents a keyboard press event.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KeyPressEvent {
    pub key: Key,
}

impl KeyPressEvent {
    /// Creates a new KeyPressEvent with the provided key.
    pub fn new(key: Key) -> Self {
        KeyPressEvent { key }
    }
}

impl Event for KeyPressEvent {}

impl TryFrom<RawEvent> for KeyPressEvent {
    type Error = ();

    /// Attempts to convert a RawEvent into a KeyPressEvent.
    fn try_from(value: RawEvent) -> Result<Self, Self::Error> {
        if let RawEvent::Key(code, modifier) = value {
            Ok(KeyPressEvent {
                key: raw_key_to_key(code, modifier),
            })
        } else {
            Err(())
        }
    }
}

/// Represents a mouse event.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MouseEvent {
    pub x: u16,
    pub y: u16,
    pub window_x: u16,
    pub window_y: u16,
    pub state: MouseState,
    pub button: Button,
}

impl MouseEvent {
    /// Creates a new MouseEvent with the provided properties.
    pub fn new(
        x: u16,
        y: u16,
        window_x: u16,
        window_y: u16,
        state: MouseState,
        button: Button,
    ) -> Self {
        MouseEvent {
            x,
            y,
            window_x,
            window_y,
            state,
            button,
        }
    }
}

impl Event for MouseEvent {}

impl TryFrom<RawEvent> for MouseEvent {
    type Error = ();

    /// Attempts to convert a RawEvent into a MouseEvent.
    fn try_from(value: RawEvent) -> Result<Self, Self::Error> {
        if let RawEvent::Mouse(x, y, kind) = value {
            match kind {
                event::MouseEventKind::Down(button) => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::Press,
                    raw_button_to_button(button),
                )),
                event::MouseEventKind::Up(button) => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::Release,
                    raw_button_to_button(button),
                )),
                event::MouseEventKind::Drag(button) => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::Hold,
                    raw_button_to_button(button),
                )),
                event::MouseEventKind::ScrollUp => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::ScrollUp,
                    Button::WheelUp,
                )),
                event::MouseEventKind::ScrollDown => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::ScrollDown,
                    Button::WheelDown,
                )),
                event::MouseEventKind::ScrollLeft => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::ScrollLeft,
                    Button::WheelLeft,
                )),
                event::MouseEventKind::ScrollRight => Ok(MouseEvent::new(
                    x,
                    y,
                    x,
                    y,
                    MouseState::ScrollRight,
                    Button::WheelRight,
                )),
                event::MouseEventKind::Moved => {
                    Ok(MouseEvent::new(x, y, x, y, MouseState::Move, Button::Left))
                }
            }
        } else {
            Err(())
        }
    }
}

/// Enum representing key inputs.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Key {
    Up,
    Down,
    Left,
    Right,
    Escape,
    Backspace,
    Space,
    Char(char),
    Ctrl(Box<Key>),
    Alt(Box<Key>),
    Shift(Box<Key>),
    Unknown,
}

/// Enum representing mouse buttons.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Button {
    Left,
    Right,
    Middle,
    WheelUp,
    WheelDown,
    WheelLeft,
    WheelRight,
}

/// Enum representing mouse states.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum MouseState {
    Press,
    Release,
    Hold,
    Scroll,
    ScrollUp,
    ScrollDown,
    ScrollLeft,
    ScrollRight,
    Move,
}

/// Converts a raw key code and modifier into an internal Key representation.
fn raw_key_to_key(key: event::KeyCode, modifier: event::KeyModifiers) -> Key {
    let modify = |key: Key| match modifier {
        event::KeyModifiers::CONTROL => Key::Ctrl(key.into()),
        event::KeyModifiers::ALT => Key::Alt(key.into()),
        event::KeyModifiers::SHIFT => Key::Shift(key.into()),
        event::KeyModifiers::NONE => key,
        _ => Key::Unknown,
    };
    match key {
        event::KeyCode::Up => modify(Key::Up),
        event::KeyCode::Down => modify(Key::Down),
        event::KeyCode::Left => modify(Key::Left),
        event::KeyCode::Right => modify(Key::Right),
        event::KeyCode::Esc => Key::Escape,
        event::KeyCode::Backspace => Key::Backspace,
        event::KeyCode::Enter => Key::Char('\n'),
        event::KeyCode::Char(c) => modify(Key::Char(c)),
        _ => Key::Unknown,
    }
}

/// Converts a raw mouse button into an internal Button representation.
fn raw_button_to_button(button: event::MouseButton) -> Button {
    match button {
        event::MouseButton::Left => Button::Left,
        event::MouseButton::Right => Button::Right,
        event::MouseButton::Middle => Button::Middle,
    }
}