rat_widget/
shadow.rs

1//!
2//! Draw a shadow around a widget.
3//!
4use crate::_private::NonExhaustive;
5use ratatui::buffer::Buffer;
6use ratatui::layout::Rect;
7use ratatui::style::Style;
8use ratatui::widgets::StatefulWidget;
9#[cfg(feature = "unstable-widget-ref")]
10use ratatui::widgets::StatefulWidgetRef;
11
12/// Direction of the shadow.
13#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
14pub enum ShadowDirection {
15    #[default]
16    BottomRight,
17    BottomLeft,
18    TopRight,
19    TopLeft,
20}
21
22/// Draw a shadow around a widget.
23///
24/// render is called with the area of the original widget,
25/// and this renders just outside of it.
26/// It sets the style of the cells to the given style
27/// but leaves the text-content untouched.
28///
29#[derive(Debug, Default, Clone)]
30pub struct Shadow {
31    style: Style,
32    dir: ShadowDirection,
33}
34
35impl Shadow {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    pub fn styles(mut self, styles: ShadowStyle) -> Self {
41        self.style = styles.style;
42        self.dir = styles.dir;
43        self
44    }
45
46    pub fn style(mut self, style: Style) -> Self {
47        self.style = style;
48        self
49    }
50
51    pub fn direction(mut self, direction: ShadowDirection) -> Self {
52        self.dir = direction;
53        self
54    }
55}
56
57#[derive(Debug)]
58pub struct ShadowStyle {
59    pub style: Style,
60    pub dir: ShadowDirection,
61    pub non_exhaustive: NonExhaustive,
62}
63
64impl Default for ShadowStyle {
65    fn default() -> Self {
66        Self {
67            style: Default::default(),
68            dir: Default::default(),
69            non_exhaustive: NonExhaustive,
70        }
71    }
72}
73
74#[cfg(feature = "unstable-widget-ref")]
75impl StatefulWidgetRef for Shadow {
76    type State = ();
77
78    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
79        render_ref(&self, area, buf, state);
80    }
81}
82
83impl StatefulWidget for Shadow {
84    type State = ();
85
86    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
87        render_ref(&self, area, buf, state);
88    }
89}
90
91fn render_ref(widget: &Shadow, area: Rect, buf: &mut Buffer, _state: &mut ()) {
92    match widget.dir {
93        ShadowDirection::BottomRight => {
94            for y in area.top() + 1..area.bottom() + 1 {
95                if let Some(cell) = buf.cell_mut((area.right(), y)) {
96                    cell.set_style(widget.style);
97                }
98            }
99            for x in area.left() + 1..area.right() {
100                if let Some(cell) = buf.cell_mut((x, area.bottom())) {
101                    cell.set_style(widget.style);
102                }
103            }
104        }
105        ShadowDirection::BottomLeft => {
106            if area.left() > 0 {
107                for y in area.top() + 1..area.bottom() + 1 {
108                    if let Some(cell) = buf.cell_mut((area.left() - 1, y)) {
109                        cell.set_style(widget.style);
110                    }
111                }
112            }
113            for x in area.left()..area.right().saturating_sub(1) {
114                if let Some(cell) = buf.cell_mut((x, area.bottom())) {
115                    cell.set_style(widget.style);
116                }
117            }
118        }
119        ShadowDirection::TopRight => {
120            for y in area.top().saturating_sub(1)..area.bottom().saturating_sub(1) {
121                if let Some(cell) = buf.cell_mut((area.right(), y)) {
122                    cell.set_style(widget.style);
123                }
124            }
125            if area.top() > 0 {
126                for x in area.left() + 1..area.right() {
127                    if let Some(cell) = buf.cell_mut((x, area.top() - 1)) {
128                        cell.set_style(widget.style);
129                    }
130                }
131            }
132        }
133        ShadowDirection::TopLeft => {
134            if area.left() > 0 {
135                for y in area.top().saturating_sub(1)..area.bottom().saturating_sub(1) {
136                    if let Some(cell) = buf.cell_mut((area.left() - 1, y)) {
137                        cell.set_style(widget.style);
138                    }
139                }
140            }
141            if area.top() > 0 {
142                for x in area.left()..area.right().saturating_sub(1) {
143                    if let Some(cell) = buf.cell_mut((x, area.top() - 1)) {
144                        cell.set_style(widget.style);
145                    }
146                }
147            }
148        }
149    }
150}