twitter_tool/ui_framework/
mod.rs

1use crate::ui_framework::bounding_box::BoundingBox;
2use anyhow::Result;
3use crossterm::event::KeyEvent;
4use std::io::Stdout;
5
6pub mod bounding_box;
7pub mod scroll_buffer;
8
9pub trait Render {
10    // CR-soon: it's actually pretty tricky for implementers to get invalidation logic correct by
11    // hand.  Maybe think a bit harder about how to make this more foolproof.
12    fn should_render(&self) -> bool;
13
14    // CR: need to rethink architecture, maybe we want to do internal events after all and just
15    // think about what kind of latency guarantees are possible
16    fn invalidate(&mut self);
17
18    /// NB: [render] takes [&mut self] since there isn't a separate notification to component that
19    /// their bbox changed.
20    fn render(&mut self, stdout: &mut Stdout, bounding_box: BoundingBox) -> Result<()>;
21
22    fn get_cursor(&self) -> (u16, u16);
23}
24
25pub trait Input {
26    fn handle_focus(&mut self);
27
28    /// Returns true if the event was handled; that is, the caller won't consider the event and run
29    /// it's own handler.
30    fn handle_key_event(&mut self, event: &KeyEvent) -> bool;
31}
32
33pub struct Component<T: Render + Input> {
34    pub bounding_box: BoundingBox,
35    pub component: T,
36}
37
38impl<T: Render + Input> Component<T> {
39    pub fn new(component: T) -> Self {
40        Self {
41            bounding_box: BoundingBox::default(),
42            component,
43        }
44    }
45
46    pub fn render_if_necessary(&mut self, stdout: &mut Stdout) -> Result<()> {
47        if self.component.should_render() {
48            self.component.render(stdout, self.bounding_box)?;
49        }
50        Ok(())
51    }
52
53    pub fn get_cursor(&self) -> (u16, u16) {
54        let BoundingBox { left, top, .. } = self.bounding_box;
55        let relative = self.component.get_cursor();
56        (left + relative.0, top + relative.1)
57    }
58}