iced_pancurses/renderer/
mod.rs

1mod button;
2mod checkbox;
3mod column;
4//mod debugger;
5mod image;
6mod radio;
7mod scrollable;
8mod slider;
9//mod space;
10mod row;
11mod text;
12mod text_input;
13
14use crate::colors::{ColorRegistry, PancursesColor};
15use crate::primitive::Primitive;
16use iced_native::input::{
17    keyboard, keyboard::KeyCode, mouse::Button, mouse::Event as MouseEvent, ButtonState,
18};
19use iced_native::layout::Limits;
20use iced_native::{Event, Renderer};
21use pancurses::{initscr, Input, Window};
22
23/// Pancurses Renderer implementation for iced
24pub struct PancursesRenderer {
25    /// Pancurses window to use to print UI elements
26    window: Window,
27    /// The ColorRegistry is the place to store pancurses color pairs indices
28    color_registry: ColorRegistry,
29}
30
31impl Default for PancursesRenderer {
32    /// Default config for a Pancurses renderer
33    fn default() -> Self {
34        let window = initscr();
35        pancurses::noecho();
36        pancurses::curs_set(0);
37        pancurses::start_color();
38        pancurses::use_default_colors();
39        // Set keypad mode; necessary for correct input handling
40        window.keypad(true);
41
42        // Listen to all mouse events
43        pancurses::mousemask(pancurses::ALL_MOUSE_EVENTS, std::ptr::null_mut());
44        Self {
45            window,
46            color_registry: Default::default(),
47        }
48    }
49}
50
51impl Renderer for PancursesRenderer {
52    type Output = Primitive;
53
54    fn layout<'a, Message>(
55        &mut self,
56        element: &iced_native::Element<'a, Message, Self>,
57    ) -> iced_native::layout::Node {
58        let limits = Limits::NONE
59            .max_width(self.window.get_max_x() as u32)
60            .max_height(self.window.get_max_y() as u32);
61        element.layout(self, &limits)
62    }
63}
64
65impl PancursesRenderer {
66    /// Clears the output of the renderer
67    pub fn flush(&mut self) {
68        self.window.clear();
69        self.window.refresh();
70    }
71
72    /// Polls event from the pancurses window
73    pub fn handle(&self) -> Option<Vec<Event>> {
74        let input = self.window.getch();
75        match input {
76            Some(Input::Character(c)) => {
77                Some(vec![Event::Keyboard(keyboard::Event::CharacterReceived(c))])
78            }
79            Some(Input::KeyBackspace) => Some(vec![
80                Event::Keyboard(keyboard::Event::Input {
81                    state: ButtonState::Pressed,
82                    key_code: KeyCode::Backspace,
83                }),
84                Event::Keyboard(keyboard::Event::Input {
85                    state: ButtonState::Released,
86                    key_code: KeyCode::Backspace,
87                }),
88            ]),
89            Some(Input::KeyEnter) => Some(vec![
90                Event::Keyboard(keyboard::Event::Input {
91                    state: ButtonState::Pressed,
92                    key_code: KeyCode::Enter,
93                }),
94                Event::Keyboard(keyboard::Event::Input {
95                    state: ButtonState::Released,
96                    key_code: KeyCode::Enter,
97                }),
98            ]),
99            Some(Input::KeyMouse) => {
100                if let Ok(mouse_event) = pancurses::getmouse() {
101                    match mouse_event.bstate {
102                        pancurses::BUTTON1_PRESSED => Some(move_cursor_and(
103                            mouse_event.x,
104                            mouse_event.y,
105                            vec![Event::Mouse(MouseEvent::Input {
106                                state: ButtonState::Pressed,
107                                button: Button::Left,
108                            })],
109                        )),
110                        pancurses::BUTTON1_RELEASED => Some(move_cursor_and(
111                            mouse_event.x,
112                            mouse_event.y,
113                            vec![Event::Mouse(MouseEvent::Input {
114                                state: ButtonState::Released,
115                                button: Button::Left,
116                            })],
117                        )),
118                        pancurses::BUTTON1_CLICKED => Some(move_cursor_and(
119                            mouse_event.x,
120                            mouse_event.y,
121                            vec![
122                                Event::Mouse(MouseEvent::Input {
123                                    state: ButtonState::Pressed,
124                                    button: Button::Left,
125                                }),
126                                Event::Mouse(MouseEvent::Input {
127                                    state: ButtonState::Released,
128                                    button: Button::Left,
129                                }),
130                            ],
131                        )),
132                        _ => None,
133                    }
134                } else {
135                    None
136                }
137            }
138            _ => None,
139        }
140    }
141
142    /// Draws a given primitive onto the window
143    pub fn draw(&mut self, primitive: Primitive) {
144        match primitive {
145            Primitive::Group(prims) => prims.into_iter().for_each(|p| self.draw(p)),
146            Primitive::Text(texts, bounds, color) => {
147                let col = crate::colors::get_closest_color(color);
148                let col_idx = self.color_registry.get_idx(PancursesColor::new(col, -1));
149                self.window
150                    .attrset(pancurses::COLOR_PAIR((col_idx as u32).into()));
151                let mut y = 0;
152                texts.into_iter().for_each(|l| {
153                    self.window.mv(bounds.y as i32 + y as i32, bounds.x as i32);
154                    self.window.addstr(l);
155                    y += 1;
156                });
157            }
158            Primitive::BoxDisplay(bounds) => {
159                let col_idx = self
160                    .color_registry
161                    .get_idx(PancursesColor::new(pancurses::COLOR_WHITE, -1));
162                self.window
163                    .attrset(pancurses::COLOR_PAIR((col_idx as u32).into()));
164                let x = bounds.x as i32;
165                let y = bounds.y as i32;
166                let w = bounds.width as i32;
167                let h = bounds.height as i32;
168                if let Ok(sub_win) = self.window.subwin(h, w, y, x) {
169                    sub_win.border(0, 0, 0, 0, 0, 0, 0, 0);
170                    sub_win.delwin();
171                }
172            }
173            Primitive::Char(x, y, boxchar) => {
174                let col_idx = self
175                    .color_registry
176                    .get_idx(PancursesColor::new(pancurses::COLOR_WHITE, -1));
177                self.window
178                    .attrset(pancurses::COLOR_PAIR((col_idx as u32).into()));
179                self.window.mv(y, x);
180                self.window.addch(boxchar);
181            }
182            _ => (),
183        }
184    }
185
186    /// Gets the current size of the terminal root window
187    pub fn size(&self) -> (u16, u16) {
188        let yx = self.window.get_max_yx();
189        (yx.1 as u16, yx.0 as u16)
190    }
191}
192
193pub fn move_cursor_and(x: i32, y: i32, other: Vec<Event>) -> Vec<Event> {
194    vec![Event::Mouse(MouseEvent::CursorMoved {
195        x: x as f32,
196        y: y as f32,
197    })]
198    .into_iter()
199    .chain(other.into_iter())
200    .collect()
201}