Codex Handbook
tui/src/render/renderable.rs 503 lines
use std::sync::Arc;use crossterm::cursor::SetCursorStyle;use ratatui::buffer::Buffer;use ratatui::layout::Rect;use ratatui::text::Line;use ratatui::text::Span;use ratatui::widgets::Paragraph;use ratatui::widgets::WidgetRef;use crate::render::Insets;use crate::render::RectExt as _;pub trait Renderable {    fn render(&self, area: Rect, buf: &mut Buffer);    fn desired_height(&self, width: u16) -> u16;    fn cursor_pos(&self, _area: Rect) -> Option<(u16, u16)> {        None    }    fn cursor_style(&self, _area: Rect) -> SetCursorStyle {        SetCursorStyle::DefaultUserShape    }}pub enum RenderableItem<'a> {    Owned(Box<dyn Renderable + 'a>),    Borrowed(&'a dyn Renderable),}impl<'a> Renderable for RenderableItem<'a> {    fn render(&self, area: Rect, buf: &mut Buffer) {        match self {            RenderableItem::Owned(child) => child.render(area, buf),            RenderableItem::Borrowed(child) => child.render(area, buf),        }    }    fn desired_height(&self, width: u16) -> u16 {        match self {            RenderableItem::Owned(child) => child.desired_height(width),            RenderableItem::Borrowed(child) => child.desired_height(width),        }    }    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        match self {            RenderableItem::Owned(child) => child.cursor_pos(area),            RenderableItem::Borrowed(child) => child.cursor_pos(area),        }    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        match self {            RenderableItem::Owned(child) => child.cursor_style(area),            RenderableItem::Borrowed(child) => child.cursor_style(area),        }    }}impl<'a> From<Box<dyn Renderable + 'a>> for RenderableItem<'a> {    fn from(value: Box<dyn Renderable + 'a>) -> Self {        RenderableItem::Owned(value)    }}impl<'a, R> From<R> for Box<dyn Renderable + 'a>where    R: Renderable + 'a,{    fn from(value: R) -> Self {        Box::new(value)    }}impl Renderable for () {    fn render(&self, _area: Rect, _buf: &mut Buffer) {}    fn desired_height(&self, _width: u16) -> u16 {        0    }}impl Renderable for &str {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.render_ref(area, buf);    }    fn desired_height(&self, _width: u16) -> u16 {        1    }}impl Renderable for String {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.render_ref(area, buf);    }    fn desired_height(&self, _width: u16) -> u16 {        1    }}impl<'a> Renderable for Span<'a> {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.render_ref(area, buf);    }    fn desired_height(&self, _width: u16) -> u16 {        1    }}impl<'a> Renderable for Line<'a> {    fn render(&self, area: Rect, buf: &mut Buffer) {        WidgetRef::render_ref(self, area, buf);    }    fn desired_height(&self, _width: u16) -> u16 {        1    }}impl<'a> Renderable for Paragraph<'a> {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.render_ref(area, buf);    }    fn desired_height(&self, width: u16) -> u16 {        self.line_count(width) as u16    }}impl<R: Renderable> Renderable for Option<R> {    fn render(&self, area: Rect, buf: &mut Buffer) {        if let Some(renderable) = self {            renderable.render(area, buf);        }    }    fn desired_height(&self, width: u16) -> u16 {        if let Some(renderable) = self {            renderable.desired_height(width)        } else {            0        }    }    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        self.as_ref()            .and_then(|renderable| renderable.cursor_pos(area))    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        self.as_ref()            .map_or(SetCursorStyle::DefaultUserShape, |renderable| {                renderable.cursor_style(area)            })    }}impl<R: Renderable> Renderable for Arc<R> {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.as_ref().render(area, buf);    }    fn desired_height(&self, width: u16) -> u16 {        self.as_ref().desired_height(width)    }    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        self.as_ref().cursor_pos(area)    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        self.as_ref().cursor_style(area)    }}pub struct ColumnRenderable<'a> {    children: Vec<RenderableItem<'a>>,}impl Renderable for ColumnRenderable<'_> {    fn render(&self, area: Rect, buf: &mut Buffer) {        let mut y = area.y;        for child in &self.children {            let child_area = Rect::new(area.x, y, area.width, child.desired_height(area.width))                .intersection(area);            if !child_area.is_empty() {                child.render(child_area, buf);            }            y += child_area.height;        }    }    fn desired_height(&self, width: u16) -> u16 {        self.children            .iter()            .map(|child| child.desired_height(width))            .sum()    }    /// Returns the cursor position of the first child that has a cursor position, offset by the    /// child's position in the column.    ///    /// It is generally assumed that either zero or one child will have a cursor position.    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        let mut y = area.y;        for child in &self.children {            let child_area = Rect::new(area.x, y, area.width, child.desired_height(area.width))                .intersection(area);            if !child_area.is_empty()                && let Some((px, py)) = child.cursor_pos(child_area)            {                return Some((px, py));            }            y += child_area.height;        }        None    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        let mut y = area.y;        for child in &self.children {            let child_area = Rect::new(area.x, y, area.width, child.desired_height(area.width))                .intersection(area);            if !child_area.is_empty() && child.cursor_pos(child_area).is_some() {                return child.cursor_style(child_area);            }            y += child_area.height;        }        SetCursorStyle::DefaultUserShape    }}impl<'a> ColumnRenderable<'a> {    pub fn new() -> Self {        Self { children: vec![] }    }    pub fn with<I, T>(children: I) -> Self    where        I: IntoIterator<Item = T>,        T: Into<RenderableItem<'a>>,    {        Self {            children: children.into_iter().map(Into::into).collect(),        }    }    pub fn push(&mut self, child: impl Into<Box<dyn Renderable + 'a>>) {        self.children.push(RenderableItem::Owned(child.into()));    }}pub struct FlexChild<'a> {    flex: i32,    child: RenderableItem<'a>,}pub struct FlexRenderable<'a> {    children: Vec<FlexChild<'a>>,}/// Lays out children in a column, with the ability to specify a flex factor for each child.////// Children with flex factor > 0 will be allocated the remaining space after the non-flex children,/// proportional to the flex factor.impl<'a> FlexRenderable<'a> {    pub fn new() -> Self {        Self { children: vec![] }    }    pub fn push(&mut self, flex: i32, child: impl Into<RenderableItem<'a>>) {        self.children.push(FlexChild {            flex,            child: child.into(),        });    }    /// Loosely inspired by Flutter's Flex widget.    ///    /// Ref https://github.com/flutter/flutter/blob/3fd81edbf1e015221e143c92b2664f4371bdc04a/packages/flutter/lib/src/rendering/flex.dart#L1205-L1209    fn allocate(&self, area: Rect) -> Vec<Rect> {        let mut allocated_rects = Vec::with_capacity(self.children.len());        let mut child_sizes = vec![0; self.children.len()];        let mut allocated_size = 0;        let mut flex_children = Vec::new();        // 1. Allocate space to non-flex children.        let max_size = area.height;        for (i, FlexChild { flex, child }) in self.children.iter().enumerate() {            if *flex > 0 {                flex_children.push((i, *flex as u16, child.desired_height(area.width)));            } else {                child_sizes[i] = child                    .desired_height(area.width)                    .min(max_size.saturating_sub(allocated_size));                allocated_size += child_sizes[i];            }        }        let free_space = max_size.saturating_sub(allocated_size);        // 2. Satisfy flex children that need less than their proportional share so their unused        // space can be redistributed instead of leaving blank rows.        let mut remaining_space = free_space;        while !flex_children.is_empty() {            let total_flex = flex_children.iter().map(|(_, flex, _)| *flex).sum::<u16>();            let mut satisfied_any = false;            flex_children.retain(|(i, flex, desired_height)| {                let proportional_share =                    (u32::from(remaining_space) * u32::from(*flex) / u32::from(total_flex)) as u16;                if *desired_height <= proportional_share {                    child_sizes[*i] = *desired_height;                    remaining_space = remaining_space.saturating_sub(*desired_height);                    satisfied_any = true;                    false                } else {                    true                }            });            if !satisfied_any {                break;            }        }        // 3. Divide the remaining space proportionally. The final child absorbs rounding slack.        let total_flex = flex_children.iter().map(|(_, flex, _)| *flex).sum::<u16>();        let mut allocated_flex_space = 0;        let last_flex_child_idx = flex_children.last().map(|(i, _, _)| *i);        for (i, flex, desired_height) in flex_children {            let max_child_extent = if Some(i) == last_flex_child_idx {                remaining_space.saturating_sub(allocated_flex_space)            } else {                (u32::from(remaining_space) * u32::from(flex) / u32::from(total_flex)) as u16            };            let child_size = desired_height.min(max_child_extent);            child_sizes[i] = child_size;            allocated_flex_space += child_size;        }        let mut y = area.y;        for size in child_sizes {            let child_area = Rect::new(area.x, y, area.width, size);            allocated_rects.push(child_area);            y += child_area.height;        }        allocated_rects    }}impl<'a> Renderable for FlexRenderable<'a> {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.allocate(area)            .into_iter()            .zip(self.children.iter())            .for_each(|(rect, child)| {                child.child.render(rect, buf);            });    }    fn desired_height(&self, width: u16) -> u16 {        self.allocate(Rect::new(0, 0, width, u16::MAX))            .last()            .map(|rect| rect.bottom())            .unwrap_or(0)    }    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        self.allocate(area)            .into_iter()            .zip(self.children.iter())            .find_map(|(rect, child)| child.child.cursor_pos(rect))    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        self.allocate(area)            .into_iter()            .zip(self.children.iter())            .find_map(|(rect, child)| {                child                    .child                    .cursor_pos(rect)                    .map(|_| child.child.cursor_style(rect))            })            .unwrap_or(SetCursorStyle::DefaultUserShape)    }}pub struct RowRenderable<'a> {    children: Vec<(u16, RenderableItem<'a>)>,}impl Renderable for RowRenderable<'_> {    fn render(&self, area: Rect, buf: &mut Buffer) {        let mut x = area.x;        for (width, child) in &self.children {            let available_width = area.width.saturating_sub(x - area.x);            let child_area = Rect::new(x, area.y, (*width).min(available_width), area.height);            if child_area.is_empty() {                break;            }            child.render(child_area, buf);            x = x.saturating_add(*width);        }    }    fn desired_height(&self, width: u16) -> u16 {        let mut max_height = 0;        let mut width_remaining = width;        for (child_width, child) in &self.children {            let w = (*child_width).min(width_remaining);            if w == 0 {                break;            }            let height = child.desired_height(w);            if height > max_height {                max_height = height;            }            width_remaining = width_remaining.saturating_sub(w);        }        max_height    }    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        let mut x = area.x;        for (width, child) in &self.children {            let available_width = area.width.saturating_sub(x - area.x);            let child_area = Rect::new(x, area.y, (*width).min(available_width), area.height);            if !child_area.is_empty()                && let Some(pos) = child.cursor_pos(child_area)            {                return Some(pos);            }            x = x.saturating_add(*width);        }        None    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        let mut x = area.x;        for (width, child) in &self.children {            let available_width = area.width.saturating_sub(x - area.x);            let child_area = Rect::new(x, area.y, (*width).min(available_width), area.height);            if !child_area.is_empty() && child.cursor_pos(child_area).is_some() {                return child.cursor_style(child_area);            }            x = x.saturating_add(*width);        }        SetCursorStyle::DefaultUserShape    }}impl<'a> RowRenderable<'a> {    pub fn new() -> Self {        Self { children: vec![] }    }    pub fn push(&mut self, width: u16, child: impl Into<Box<dyn Renderable>>) {        self.children            .push((width, RenderableItem::Owned(child.into())));    }}pub struct InsetRenderable<'a> {    child: RenderableItem<'a>,    insets: Insets,}impl<'a> Renderable for InsetRenderable<'a> {    fn render(&self, area: Rect, buf: &mut Buffer) {        self.child.render(area.inset(self.insets), buf);    }    fn desired_height(&self, width: u16) -> u16 {        self.child            .desired_height(width - self.insets.left - self.insets.right)            + self.insets.top            + self.insets.bottom    }    fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {        self.child.cursor_pos(area.inset(self.insets))    }    fn cursor_style(&self, area: Rect) -> SetCursorStyle {        self.child.cursor_style(area.inset(self.insets))    }}impl<'a> InsetRenderable<'a> {    pub fn new(child: impl Into<RenderableItem<'a>>, insets: Insets) -> Self {        Self {            child: child.into(),            insets,        }    }}pub trait RenderableExt<'a> {    fn inset(self, insets: Insets) -> RenderableItem<'a>;}impl<'a, R> RenderableExt<'a> for Rwhere    R: Renderable + 'a,{    fn inset(self, insets: Insets) -> RenderableItem<'a> {        let child: RenderableItem<'a> =            RenderableItem::Owned(Box::new(self) as Box<dyn Renderable + 'a>);        RenderableItem::Owned(Box::new(InsetRenderable { child, insets }))    }}#[cfg(test)]#[path = "renderable_tests.rs"]mod tests;