tui_popup/
popup_state.rs

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