rat_widget/
shadow.rs

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