Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Implement group widget in imUI
Erik Kundt committed 1 year ago
commit ccc3358de062e1ae360e8d36520838d2af0123fa
parent 0ef39b098fa381cf9e92975114374965b6340ba3
1 file changed +155 -8
modified src/im.rs
@@ -210,6 +210,20 @@ impl From<ratatui::layout::Layout> for Layout {
}

impl Layout {
+
    pub fn len(&self) -> usize {
+
        match self {
+
            Layout::None => 0,
+
            Layout::Wrapped { internal } => internal.split(Rect::default()).len(),
+
            Layout::Expandable3 { left_only } => {
+
                if *left_only {
+
                    1
+
                } else {
+
                    3
+
                }
+
            }
+
        }
+
    }
+

    pub fn split(&self, area: Rect) -> Rc<[Rect]> {
        match self {
            Layout::None => Rc::new([]),
@@ -248,7 +262,8 @@ pub struct Ui {
    pub(crate) theme: Theme,
    pub(crate) area: Rect,
    pub(crate) layout: Layout,
-
    next_area: usize,
+
    focus: usize,
+
    count: usize,
    ctx: Context,
}

@@ -290,10 +305,19 @@ impl Ui {
    }

    pub fn next_area(&mut self) -> Option<Rect> {
-
        let rect = self.layout.split(self.area).get(self.next_area).cloned();
-
        self.next_area = self.next_area + 1;
+
        let rect = self.layout.split(self.area).get(self.count).cloned();
+
        self.count = self.count + 1;
        rect
    }
+

+
    pub fn count(&self) -> usize {
+
        self.count
+
    }
+

+
    pub fn has_focus(&self) -> bool {
+
        log::trace!("[Ui] Querying focus: {}, {}", self.count, self.focus);
+
        self.count == self.focus
+
    }
}

impl Ui {
@@ -330,6 +354,22 @@ impl Ui {
}

impl Ui {
+
    pub fn group<R>(
+
        &mut self,
+
        layout: impl Into<Layout>,
+
        focus: &mut usize,
+
        add_contents: impl FnOnce(&mut Ui) -> R,
+
    ) -> InnerResponse<R> {
+
        let area = self.next_area().unwrap_or_default();
+

+
        let layout: Layout = layout.into();
+
        let len = layout.len();
+

+
        let mut child_ui = self.child_ui(area, layout);
+

+
        widget::Group::new(len, focus).show(&mut child_ui, add_contents)
+
    }
+

    pub fn table<'a, R, const W: usize>(
        &mut self,
        frame: &mut Frame,
@@ -448,6 +488,82 @@ pub mod widget {
    }

    #[derive(Clone, Debug)]
+
    pub struct GroupState {
+
        len: usize,
+
        focus: usize,
+
    }
+

+
    impl GroupState {
+
        pub fn new(len: usize, focus: usize) -> Self {
+
            Self { len, focus }
+
        }
+

+
        pub fn focus(&self) -> usize {
+
            self.focus
+
        }
+

+
        pub fn len(&self) -> usize {
+
            self.len
+
        }
+
    }
+

+
    pub struct Group<'a> {
+
        focus: &'a mut usize,
+
        len: usize,
+
    }
+

+
    impl<'a> Group<'a> {
+
        pub fn new(len: usize, focus: &'a mut usize) -> Self {
+
            Self { len, focus }
+
        }
+

+
        pub fn show<R>(
+
            self,
+
            ui: &mut Ui,
+
            add_contents: impl FnOnce(&mut Ui) -> R,
+
        ) -> InnerResponse<R> {
+
            self.show_dyn(ui, Box::new(add_contents))
+
        }
+

+
        pub fn show_dyn<'c, R>(
+
            self,
+
            ui: &mut Ui,
+
            add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
+
        ) -> InnerResponse<R> {
+
            let mut response = Response::default();
+

+
            let mut state = GroupState {
+
                focus: *self.focus,
+
                len: self.len,
+
            };
+

+
            if let Some(key) = ui.input_with_key(|_| true) {
+
                match key {
+
                    Key::Char('\t') => {
+
                        state.focus =
+
                            cmp::min(state.focus.saturating_add(1), state.len.saturating_sub(1));
+
                    }
+
                    Key::BackTab => {
+
                        state.focus = state.focus.saturating_sub(1);
+
                    }
+
                    _ => {}
+
                }
+
                response.changed = true;
+
            }
+
            *self.focus = state.focus;
+

+
            let mut ui = Ui {
+
                focus: state.focus,
+
                ..ui.clone()
+
            };
+

+
            let inner = add_contents(&mut ui);
+

+
            InnerResponse::new(inner, response)
+
        }
+
    }
+

+
    #[derive(Clone, Debug)]
    pub struct TableState<R> {
        items: Vec<R>,
        internal: ratatui::widgets::TableState,
@@ -573,11 +689,12 @@ pub mod widget {
    {
        fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
            let mut response = Response::default();
+

+
            let has_focus = ui.has_focus();
            let area = ui.next_area().unwrap_or_default();

            let show_scrollbar = self.show_scrollbar && self.items.len() >= area.height.into();
            let has_items = !self.items.is_empty();
-
            let has_focus = true;

            let mut state = TableState {
                items: self.items.clone(),
@@ -588,7 +705,13 @@ pub mod widget {
                },
            };

-
            let area = render_block(frame, area, self.borders, ui.theme.border_style);
+
            let border_style = if has_focus {
+
                ui.theme.focus_border_style
+
            } else {
+
                ui.theme.border_style
+
            };
+

+
            let area = render_block(frame, area, self.borders, border_style);

            if let Some(key) = ui.input_with_key(|_| true) {
                let len = self.items.len();
@@ -728,8 +851,16 @@ pub mod widget {

    impl<'a> Widget for Columns<'a> {
        fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
            let has_focus = ui.has_focus();
            let area = ui.next_area().unwrap_or_default();
-
            let area = render_block(frame, area, self.borders, ui.theme.border_style);
+

+
            let border_style = if has_focus {
+
                ui.theme.focus_border_style
+
            } else {
+
                ui.theme.border_style
+
            };
+

+
            let area = render_block(frame, area, self.borders, border_style);
            let area = Rect {
                width: area.width - 1,
                ..area
@@ -775,8 +906,16 @@ pub mod widget {

    impl Widget for TextView {
        fn ui(self, ui: &mut Ui, frame: &mut Frame) -> Response {
+
            let has_focus = ui.has_focus();
            let area = ui.next_area().unwrap_or_default();
-
            let area = render_block(frame, area, self.borders, ui.theme.border_style);
+

+
            let border_style = if has_focus {
+
                ui.theme.focus_border_style
+
            } else {
+
                ui.theme.border_style
+
            };
+

+
            let area = render_block(frame, area, self.borders, border_style);

            frame.render_widget(Paragraph::new(self.text), area);

@@ -903,8 +1042,16 @@ pub mod widget {
        pub fn show(self, ui: &mut Ui, frame: &mut Frame) -> TextEditOutput {
            let mut response = Response::default();

+
            let has_focus = ui.has_focus();
            let area = ui.next_area().unwrap_or_default();
-
            let area = render_block(frame, area, self.borders, ui.theme.border_style);
+

+
            let border_style = if has_focus {
+
                ui.theme.focus_border_style
+
            } else {
+
                ui.theme.border_style
+
            };
+

+
            let area = render_block(frame, area, self.borders, border_style);

            let layout = Layout::vertical(Constraint::from_lengths([1, 1])).split(area);