tui_popup/
popup_state.rs

1#[cfg(feature = "crossterm")]
2use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
3use derive_getters::Getters;
4use ratatui_core::layout::Rect;
5
6#[derive(Clone, Debug, Default, Getters)]
7pub struct PopupState {
8    /// The last rendered area of the popup
9    pub(crate) area: Option<Rect>,
10    /// A state indicating whether the popup is being dragged or not
11    pub(crate) drag_state: DragState,
12}
13
14#[derive(Clone, Debug, Default)]
15pub enum DragState {
16    #[default]
17    NotDragging,
18    Dragging {
19        col_offset: u16,
20        row_offset: u16,
21    },
22}
23
24impl PopupState {
25    pub fn move_up(&mut self, amount: u16) {
26        self.move_by(0, -i32::from(amount));
27    }
28
29    pub fn move_down(&mut self, amount: u16) {
30        self.move_by(0, i32::from(amount));
31    }
32
33    pub fn move_left(&mut self, amount: u16) {
34        self.move_by(-i32::from(amount), 0);
35    }
36
37    pub fn move_right(&mut self, amount: u16) {
38        self.move_by(i32::from(amount), 0);
39    }
40
41    /// Move the popup by the given amount.
42    pub fn move_by(&mut self, x: i32, y: i32) {
43        if let Some(area) = self.area {
44            self.area.replace(Rect {
45                x: i32::from(area.x)
46                    .saturating_add(x)
47                    .try_into()
48                    .unwrap_or(area.x),
49                y: i32::from(area.y)
50                    .saturating_add(y)
51                    .try_into()
52                    .unwrap_or(area.y),
53                ..area
54            });
55        }
56    }
57
58    /// Move the popup to the given position.
59    pub const fn move_to(&mut self, x: u16, y: u16) {
60        if let Some(area) = self.area {
61            self.area.replace(Rect { x, y, ..area });
62        }
63    }
64
65    /// Set the state to dragging if the mouse click is in the popup title
66    pub fn mouse_down(&mut self, col: u16, row: u16) {
67        if let Some(area) = self.area {
68            if area.contains((col, row).into()) {
69                self.drag_state = DragState::Dragging {
70                    col_offset: col.saturating_sub(area.x),
71                    row_offset: row.saturating_sub(area.y),
72                };
73            }
74        }
75    }
76
77    /// Set the state to not dragging
78    pub const fn mouse_up(&mut self, _col: u16, _row: u16) {
79        self.drag_state = DragState::NotDragging;
80    }
81
82    /// Move the popup if the state is dragging
83    pub const fn mouse_drag(&mut self, col: u16, row: u16) {
84        if let DragState::Dragging {
85            col_offset,
86            row_offset,
87        } = self.drag_state
88        {
89            if let Some(area) = self.area {
90                let x = col.saturating_sub(col_offset);
91                let y = row.saturating_sub(row_offset);
92                self.area.replace(Rect { x, y, ..area });
93            }
94        }
95    }
96
97    #[cfg(feature = "crossterm")]
98    pub fn handle_mouse_event(&mut self, event: MouseEvent) {
99        match event.kind {
100            MouseEventKind::Down(MouseButton::Left) => self.mouse_down(event.column, event.row),
101            MouseEventKind::Up(MouseButton::Left) => self.mouse_up(event.column, event.row),
102            MouseEventKind::Drag(MouseButton::Left) => self.mouse_drag(event.column, event.row),
103            _ => {}
104        }
105    }
106}