| 1 | //! Codewhale's terminal component library for ratatui. |
| 2 | //! |
| 3 | //! Reusable components from Codewhale's current terminal UI. Native palettes |
| 4 | //! and layout are available through [`Theme::tui`], [`NativeComposer`], |
| 5 | //! [`Workbar`] and [`TerminalShell`]. Desktop token themes and additional |
| 6 | //! compositions remain available. |
| 7 | //! |
| 8 | //! ```no_run |
| 9 | //! use codewhale_ratatui::{Paint, Theme, KeyHint, KeyHints}; |
| 10 | //! # fn draw(frame: &mut ratatui::Frame) { |
| 11 | //! let theme = Theme::detect().tui(); |
| 12 | //! let hints = KeyHints::new(vec![ |
| 13 | //! KeyHint::new("↑↓", "move"), |
| 14 | //! KeyHint::new("Enter", "select"), |
| 15 | //! KeyHint::new("Esc", "cancel"), |
| 16 | //! ]); |
| 17 | //! frame.render_widget(hints.themed(&theme), frame.area()); |
| 18 | //! # } |
| 19 | //! ``` |
| 20 | |
| 21 | pub mod color; |
| 22 | pub mod detect; |
| 23 | pub mod glyphs; |
| 24 | pub mod keys; |
| 25 | pub mod ocean; |
| 26 | pub mod tui_palettes; |
| 27 | pub use tui_palettes::{TuiGround, TuiInk, TuiPalette}; |
| 28 | pub mod ombre; |
| 29 | pub mod osc11; |
| 30 | #[rustfmt::skip] |
| 31 | mod roles; |
| 32 | pub mod text; |
| 33 | pub mod theme; |
| 34 | /// The vendored Codewhale design tokens (`vendor/codewhale-design/tokens.rs`): |
| 35 | /// spacing, type and motion constants alongside the colors. |
| 36 | #[path = "../vendor/codewhale-design/tokens.rs"] |
| 37 | pub mod tokens; |
| 38 | |
| 39 | mod components; |
| 40 | pub mod gallery; |
| 41 | pub mod testing; |
| 42 | pub mod whale; |
| 43 | pub mod whale_motion; |
| 44 | |
| 45 | // Every component module is re-exported whole (see `components/mod.rs`), so |
| 46 | // a package that makes an item `pub` in its own file exports it from here |
| 47 | // without touching this file. |
| 48 | pub use components::*; |
| 49 | pub use ocean::{OceanColumn, OceanPhase, OceanRamp}; |
| 50 | pub use ombre::{Ombre, OmbreDirection, WaterPalette}; |
| 51 | pub use theme::{Caps, Ground, Role, Theme}; |
| 52 | pub use whale::{Whale, WhaleState}; |
| 53 | |
| 54 | use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; |
| 55 | |
| 56 | /// A component that paints with a [`Theme`]. |
| 57 | /// |
| 58 | /// Components hold only what they show. The theme arrives when they paint, |
| 59 | /// so nothing caches a color and a theme change reaches every component on |
| 60 | /// the next frame. |
| 61 | pub trait Paint { |
| 62 | fn paint(&self, area: Rect, buf: &mut Buffer, theme: &Theme); |
| 63 | |
| 64 | /// Rows this component wants at `width`. |
| 65 | fn height(&self, _width: u16, _theme: &Theme) -> u16 { |
| 66 | 1 |
| 67 | } |
| 68 | |
| 69 | /// Wrap as a ratatui [`Widget`] for `frame.render_widget`. |
| 70 | /// |
| 71 | /// Render the returned wrapper by reference to reuse it across frames. |
| 72 | /// [`Themed::new`] also accepts a `dyn Paint` for heterogeneous collections. |
| 73 | fn themed<'a>(&'a self, theme: &'a Theme) -> Themed<'a, Self> |
| 74 | where |
| 75 | Self: Sized, |
| 76 | { |
| 77 | Themed::new(self, theme) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /// A component paired with the theme it paints with. |
| 82 | /// |
| 83 | /// The wrapper borrows both values. Rendering it by reference leaves the |
| 84 | /// component and wrapper available for another frame, without cloning either. |
| 85 | /// |
| 86 | /// ```no_run |
| 87 | /// use codewhale_ratatui::{NativeComposer, Paint, Theme}; |
| 88 | /// # fn draw(frame: &mut ratatui::Frame<'_>) { |
| 89 | /// let theme = Theme::detect().tui(); |
| 90 | /// let composer = NativeComposer::new("Review the changes"); |
| 91 | /// let widget = composer.themed(&theme); |
| 92 | /// frame.render_widget(&widget, frame.area()); |
| 93 | /// # } |
| 94 | /// ``` |
| 95 | /// |
| 96 | /// Use [`Themed::new`] when the component's concrete type is erased: |
| 97 | /// |
| 98 | /// ```no_run |
| 99 | /// use codewhale_ratatui::{KeyHint, KeyHints, NativeComposer, Paint, Themed, Theme}; |
| 100 | /// # fn draw(frame: &mut ratatui::Frame<'_>) { |
| 101 | /// let theme = Theme::detect().tui(); |
| 102 | /// let composer = NativeComposer::new("Review the changes"); |
| 103 | /// let hints = KeyHints::new(vec![KeyHint::new("Enter", "send")]); |
| 104 | /// let parts: [&dyn Paint; 2] = [&composer, &hints]; |
| 105 | /// # let areas = [frame.area(), frame.area()]; |
| 106 | /// for (part, area) in parts.into_iter().zip(areas) { |
| 107 | /// frame.render_widget(Themed::new(part, &theme), area); |
| 108 | /// } |
| 109 | /// # } |
| 110 | /// ``` |
| 111 | #[must_use] |
| 112 | pub struct Themed<'a, P: Paint + ?Sized> { |
| 113 | component: &'a P, |
| 114 | theme: &'a Theme, |
| 115 | } |
| 116 | |
| 117 | impl<'a, P: Paint + ?Sized> Themed<'a, P> { |
| 118 | /// Borrow a component and its theme, including a `dyn Paint` component. |
| 119 | pub const fn new(component: &'a P, theme: &'a Theme) -> Self { |
| 120 | Self { component, theme } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | impl<P: Paint + ?Sized> Widget for Themed<'_, P> { |
| 125 | fn render(self, area: Rect, buf: &mut Buffer) { |
| 126 | (&self).render(area, buf); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | impl<P: Paint + ?Sized> Widget for &Themed<'_, P> { |
| 131 | fn render(self, area: Rect, buf: &mut Buffer) { |
| 132 | self.component.paint(area, buf, self.theme); |
| 133 | } |
| 134 | } |
| 135 |