Skip to main content

termint/widgets/
bg_grad.rs

1use std::marker::PhantomData;
2
3use crate::{
4    buffer::Buffer,
5    enums::{Color, RGB},
6    geometry::{Constraint, Direction, Padding, Rect, Vec2},
7    style::Style,
8    widgets::cache::Cache,
9};
10
11use super::{widget::Widget, Element, Layout, Spacer};
12
13/// A container widget that renders a gradient background behind its child
14/// widget.
15///
16/// The [`BgGrad`] widget supports horizontal and vertical gradients. You can
17/// set the gradient direction by providing [`Direction`] directly using
18/// [`BgGrad::new`] method, or you can use methods like [`BgGrad::horizontal`]
19/// and [`BgGrad::vertical`].
20///
21/// By default BgGrad is empty, it doesn't have a child. To set the child
22/// widget, you can use [`BgGrad::child`] method.
23///
24/// # Examples
25///
26/// ```rust
27/// # use termint::{term::Term, widgets::BgGrad};
28/// # fn example() -> Result<(), termint::Error> {
29/// let grad = BgGrad::horizontal((0, 150, 255), (150, 255, 0));
30///
31/// let mut term = Term::default();
32/// term.render(grad)?;
33/// # Ok(())
34/// # }
35/// ```
36#[derive(Debug)]
37pub struct BgGrad<W = Element> {
38    bg_start: RGB,
39    bg_end: RGB,
40    direction: Direction,
41    padding: Padding,
42    child: Element,
43    child_type: PhantomData<W>,
44}
45
46impl BgGrad<Spacer> {
47    /// Creates a new empty [`BgGrad`] with the given gradient colors and
48    /// direction.
49    ///
50    /// For `start` and `end` you can provide any type that can be converted
51    /// into RGB, such as `u32`, `(u8 ,u8, u8)`.
52    ///
53    /// You can add child to be rendered on top of the gradient using
54    /// [`BgGrad::child`] method.
55    ///
56    /// # Example
57    /// ```rust
58    /// # use termint::{widgets::BgGrad, geometry::Direction};
59    /// let widget = BgGrad::new(
60    ///     Direction::Vertical,
61    ///     (0, 150, 255),
62    ///     (150, 255, 0)
63    /// );
64    /// ```
65    #[must_use]
66    pub fn new<T1, T2>(dir: Direction, start: T1, end: T2) -> Self
67    where
68        T1: Into<RGB>,
69        T2: Into<RGB>,
70    {
71        Self::construct(start.into(), end.into(), dir, Spacer::new())
72    }
73
74    /// Creates a new empty vertical [`BgGrad`] with the given gradient colors.
75    ///
76    /// For `start` and `end` you can provide any type that can be converted
77    /// into RGB, such as `u32`, `(u8 ,u8, u8)`.
78    ///
79    /// You can add child to be rendered on top of the gradient using
80    /// [`BgGrad::child`] method.
81    ///
82    /// # Example
83    /// ```rust
84    /// # use termint::widgets::BgGrad;
85    /// let widget = BgGrad::vertical((0, 150, 255), (150, 255, 0));
86    /// ```
87    #[must_use]
88    pub fn vertical<T1, T2>(start: T1, end: T2) -> Self
89    where
90        T1: Into<RGB>,
91        T2: Into<RGB>,
92    {
93        Self::construct(
94            start.into(),
95            end.into(),
96            Direction::Vertical,
97            Spacer::new(),
98        )
99    }
100
101    /// Creates a new empty horizontal [`BgGrad`] with the given gradient
102    /// colors.
103    ///
104    /// For `start` and `end` you can provide any type that can be converted
105    /// into RGB, such as `u32`, `(u8 ,u8, u8)`.
106    ///
107    /// You can add child to be rendered on top of the gradient using
108    /// [`BgGrad::child`] method.
109    ///
110    /// # Example
111    /// ```rust
112    /// # use termint::widgets::BgGrad;
113    /// let widget = BgGrad::horizontal((0, 150, 255), (150, 255, 0));
114    /// ```
115    #[must_use]
116    pub fn horizontal<T1, T2>(start: T1, end: T2) -> Self
117    where
118        T1: Into<RGB>,
119        T2: Into<RGB>,
120    {
121        Self::construct(
122            start.into(),
123            end.into(),
124            Direction::Horizontal,
125            Spacer::new(),
126        )
127    }
128}
129
130impl<W> BgGrad<W> {
131    /// Sets the child widget to be displayed in front of the gradient.
132    ///
133    /// # Example
134    /// ```rust
135    /// # use termint::widgets::{BgGrad, Spacer};
136    /// # type SomeWidget = Spacer;
137    /// let widget = BgGrad::vertical((0, 150, 255), (150, 255, 0))
138    ///     .child(SomeWidget::new());
139    /// ```
140    #[must_use]
141    pub fn child<CW>(self, child: CW) -> BgGrad<CW>
142    where
143        CW: Into<Element>,
144    {
145        BgGrad {
146            bg_start: self.bg_start,
147            bg_end: self.bg_end,
148            direction: self.direction,
149            padding: self.padding,
150            child: child.into(),
151            child_type: PhantomData,
152        }
153    }
154}
155
156impl<W> BgGrad<W>
157where
158    W: Widget,
159{
160    /// Sets the gradient direction of the [`BgGrad`] background.
161    ///
162    /// The direction determines in which direction is the gradient drawn.
163    #[must_use]
164    pub fn bg_dir(mut self, direction: Direction) -> Self {
165        self.direction = direction;
166        self
167    }
168
169    /// Sets padding around the child widget of the [`BgGrad`].
170    ///
171    /// You can provide any type that can be converted into [`Padding`], such
172    /// as `usize`, `(usize, usize)`, or `(usize, usize, usize, usize)`.
173    #[must_use]
174    pub fn padding<T>(mut self, padding: T) -> Self
175    where
176        T: Into<Padding>,
177    {
178        self.padding = padding.into();
179        self
180    }
181
182    fn construct(start: RGB, end: RGB, dir: Direction, child: W) -> Self {
183        Self {
184            bg_start: start,
185            bg_end: end,
186            direction: dir,
187            padding: Default::default(),
188            child: Element::new(child),
189            child_type: PhantomData,
190        }
191    }
192}
193
194impl BgGrad<Layout> {
195    /// Sets flexing [`Direction`] of the [`Layout`].
196    #[must_use]
197    pub fn direction(mut self, direction: Direction) -> Self {
198        self.child = self.child.map::<Layout, _>(|l| l.direction(direction));
199        self
200    }
201
202    /// Sets the base style of the [`Layout`].
203    #[must_use]
204    pub fn style<T>(mut self, style: T) -> Self
205    where
206        T: Into<Style>,
207    {
208        self.child = self.child.map::<Layout, _>(|l| l.style(style));
209        self
210    }
211
212    /// Sets base background color of the [`Layout`].
213    #[must_use]
214    pub fn bg<T>(mut self, bg: T) -> Self
215    where
216        T: Into<Option<Color>>,
217    {
218        self.child = self.child.map::<Layout, _>(|l| l.bg(bg));
219        self
220    }
221
222    /// Sets base foreground color of the [`Layout`].
223    #[must_use]
224    pub fn fg<T>(mut self, fg: T) -> Self
225    where
226        T: Into<Option<Color>>,
227    {
228        self.child = self.child.map::<Layout, _>(|l| l.fg(fg));
229        self
230    }
231
232    /// Makes [`Layout`] center its content in the direction it flexes.
233    ///
234    /// If the layout is flexing its children horizontally, the content will
235    /// be centered horizontally. Otherwise it will be centered vertically.
236    #[must_use]
237    pub fn center(mut self) -> Self {
238        self.child = self.child.map::<Layout, _>(|l| l.center());
239        self
240    }
241
242    /// Adds child with its [`Constraint`] to [`Layout`]
243    #[deprecated(
244        since = "0.6.0",
245        note = "Kept for compatibility purposes; use `push` function instead"
246    )]
247    pub fn add_child<T, C>(&mut self, child: T, constraint: C)
248    where
249        T: Into<Element>,
250        C: Into<Constraint>,
251    {
252        if let Some(layout) = self.child.downcast_mut::<Layout>() {
253            layout.push(child, constraint);
254        }
255    }
256
257    /// Adds a child widget with its contraint
258    ///
259    /// # Parameters
260    /// - `child`: The widget to add (any type convertible to [`Element`])
261    /// - `contraint`: Widget's contraint (any type convertible to
262    ///   [`Constraint`])
263    pub fn push<T, C>(&mut self, child: T, constraint: C)
264    where
265        T: Into<Element>,
266        C: Into<Constraint>,
267    {
268        if let Some(layout) = self.child.downcast_mut::<Layout>() {
269            layout.push(child, constraint);
270        }
271    }
272}
273
274impl<W> Widget for BgGrad<W>
275where
276    W: Widget,
277{
278    fn render(&self, buffer: &mut Buffer, rect: Rect, cache: &mut Cache) {
279        if rect.is_empty() {
280            return;
281        }
282
283        match self.direction {
284            Direction::Vertical => self.ver_render(buffer, &rect),
285            Direction::Horizontal => self.hor_render(buffer, &rect),
286        };
287        self.child.render(
288            buffer,
289            rect.inner(self.padding),
290            &mut cache.children[0],
291        );
292    }
293
294    fn height(&self, size: &Vec2) -> usize {
295        let size = Vec2::new(
296            size.x.saturating_sub(self.padding.get_horizontal()),
297            size.y.saturating_sub(self.padding.get_vertical()),
298        );
299        self.child.height(&size) + self.padding.get_vertical()
300    }
301
302    fn width(&self, size: &Vec2) -> usize {
303        let size = Vec2::new(
304            size.x.saturating_sub(self.padding.get_horizontal()),
305            size.y.saturating_sub(self.padding.get_vertical()),
306        );
307        self.child.width(&size) + self.padding.get_horizontal()
308    }
309
310    fn children(&self) -> Vec<&Element> {
311        vec![&self.child]
312    }
313}
314
315impl<W> BgGrad<W>
316where
317    W: Widget,
318{
319    /// Renders horizontal background gradient
320    fn hor_render(&self, buffer: &mut Buffer, rect: &Rect) {
321        let step = self.get_step(rect.width() as i16 - 1);
322        let (mut r, mut g, mut b) =
323            (self.bg_start.r, self.bg_start.g, self.bg_start.b);
324
325        for x in rect.x()..rect.width() + rect.x() {
326            let bg = Color::Rgb(r, g, b);
327            (r, g, b) = self.add_step((r, g, b), step);
328
329            for y in rect.y()..rect.height() + rect.y() {
330                buffer.set_bg(bg, &Vec2::new(x, y));
331            }
332        }
333    }
334
335    /// Renders vertical background gradient
336    fn ver_render(&self, buffer: &mut Buffer, rect: &Rect) {
337        let step = self.get_step(rect.height() as i16 - 1);
338        let (mut r, mut g, mut b) =
339            (self.bg_start.r, self.bg_start.g, self.bg_start.b);
340
341        for y in rect.y()..rect.height() + rect.y() {
342            let bg = Color::Rgb(r, g, b);
343            (r, g, b) = self.add_step((r, g, b), step);
344
345            for x in rect.x()..rect.width() + rect.x() {
346                buffer.set_bg(bg, &Vec2::new(x, y));
347            }
348        }
349    }
350
351    /// Gets step per character based on start and eng background color
352    fn get_step(&self, len: i16) -> (i16, i16, i16) {
353        if len <= 0 {
354            return (0, 0, 0);
355        }
356        (
357            (self.bg_end.r as i16 - self.bg_start.r as i16) / len,
358            (self.bg_end.g as i16 - self.bg_start.g as i16) / len,
359            (self.bg_end.b as i16 - self.bg_start.b as i16) / len,
360        )
361    }
362
363    /// Adds given step to RGB value in tuple
364    fn add_step(
365        &self,
366        rgb: (u8, u8, u8),
367        step: (i16, i16, i16),
368    ) -> (u8, u8, u8) {
369        (
370            (rgb.0 as i16 + step.0) as u8,
371            (rgb.1 as i16 + step.1) as u8,
372            (rgb.2 as i16 + step.2) as u8,
373        )
374    }
375}
376
377// From implementations
378impl<W> From<BgGrad<W>> for Box<dyn Widget>
379where
380    W: Widget + 'static,
381{
382    fn from(value: BgGrad<W>) -> Self {
383        Box::new(value)
384    }
385}
386
387impl<W> From<BgGrad<W>> for Element
388where
389    W: Widget + 'static,
390{
391    fn from(value: BgGrad<W>) -> Self {
392        Element::new(value)
393    }
394}