1use ratatui::style::{Color, Style};
2use ratatui::widgets::Block;
3
4use crate::action::{Action, ActionExt};
5use crate::config::OverlayLayoutSettings;
6use crate::ui::{Frame, Rect};
7
8use crate::{config::OverlayConfig};
9
10#[derive(Debug, Default)]
11pub enum OverlayEffect {
12 #[default]
13 None,
14 Disable,
15}
16
17pub trait Overlay {
18 type A: ActionExt;
19 fn on_enable(&mut self, area: &Rect) {
20 let _ = area;
21 }
22 fn on_disable(&mut self) {}
23 fn handle_input(&mut self, c: char) -> OverlayEffect;
24 fn handle_action(&mut self, action: &Action<Self::A>) -> OverlayEffect {
25 let _ = action;
26 OverlayEffect::None
27 }
28
29 fn draw(
42 &mut self,
43 frame: &mut Frame,
44 area: Rect
45 );
46
47 fn area(&mut self, ui_area: &Rect) -> Result<Rect, [u16; 2]> {
53 let _ = ui_area;
54 Err([0, 0])
55 }
56}
57
58pub struct OverlayUI<A: ActionExt> {
61 overlays: Box<[Box<dyn Overlay<A = A>>]>,
62 index: Option<usize>,
63 config: OverlayConfig,
64 cached_area: Rect,
65}
66
67impl<A: ActionExt> OverlayUI<A> {
68 pub fn new(overlays: Box<[Box<dyn Overlay<A = A>>]>, config: OverlayConfig) -> Self {
69 Self {
70 overlays,
71 index: None,
72 config,
73 cached_area: Default::default(),
74 }
75 }
76
77 pub fn index(&self) -> Option<usize> {
78 self.index
79 }
80
81 pub fn enable(&mut self, index: usize, ui_area: &Rect) {
82 assert!(index < self.overlays.len());
83 self.index = Some(index);
84 self.current_mut().unwrap().on_enable(ui_area);
85 self.update_dimensions(ui_area);
86 }
87
88 pub fn disable(&mut self) {
89 if let Some(x) = self.current_mut() {
90 x.on_disable()
91 }
92 self.index = None
93 }
94
95 pub fn current(&self) -> Option<&dyn Overlay<A = A>> {
96 self.index
97 .and_then(|i| self.overlays.get(i))
98 .map(|b| b.as_ref())
99 }
100
101 fn current_mut(&mut self) -> Option<&mut Box<dyn Overlay<A = A> + 'static>> {
102 if let Some(i) = self.index {
103 self.overlays.get_mut(i)
104 } else {
105 None
106 }
107 }
108
109 pub fn update_dimensions(&mut self, ui_area: &Rect) {
111 if let Some(x) = self.current_mut() {
112 self.cached_area = match x.area(ui_area) {
113 Ok(x) => x,
114 Err(pref) => default_area(pref, &self.config.layout, ui_area)
116 };
117 log::debug!("Overlay area: {}", self.cached_area);
118 }
119 }
120
121
122 pub fn draw(&mut self, frame: &mut Frame) {
125 let area = self.cached_area;
127 let outer_dim = self.config.outer_dim;
128
129 if let Some(x) = self.current_mut() {
130 if outer_dim {
131 Self::dim_surroundings(frame, area)
132 };
133 x.draw(frame, area);
134 }
135 }
136
137 fn dim_surroundings(frame: &mut Frame, inner: Rect) {
139 let full_area = frame.area();
140 let dim_style = Style::default().bg(Color::Black).fg(Color::DarkGray);
141
142 if inner.y > 0 {
144 let top = Rect {
145 x: 0,
146 y: 0,
147 width: full_area.width,
148 height: inner.y,
149 };
150 frame.render_widget(Block::default().style(dim_style), top);
151 }
152
153 if inner.y + inner.height < full_area.height {
155 let bottom = Rect {
156 x: 0,
157 y: inner.y + inner.height,
158 width: full_area.width,
159 height: full_area.height - (inner.y + inner.height),
160 };
161 frame.render_widget(Block::default().style(dim_style), bottom);
162 }
163
164 if inner.x > 0 {
166 let left = Rect {
167 x: 0,
168 y: inner.y,
169 width: inner.x,
170 height: inner.height,
171 };
172 frame.render_widget(Block::default().style(dim_style), left);
173 }
174
175 if inner.x + inner.width < full_area.width {
177 let right = Rect {
178 x: inner.x + inner.width,
179 y: inner.y,
180 width: full_area.width - (inner.x + inner.width),
181 height: inner.height,
182 };
183 frame.render_widget(Block::default().style(dim_style), right);
184 }
185 }
186
187 pub fn handle_input(&mut self, action: char) -> bool {
189 if let Some(x) = self.current_mut() {
190 match x.handle_input(action) {
191 OverlayEffect::None => {}
192 OverlayEffect::Disable => self.disable(),
193 }
194 true
195 } else {
196 false
197 }
198 }
199
200 pub fn handle_action(&mut self, action: &Action<A>) -> bool {
201 if let Some(inner) = self.current_mut() {
202 match inner.handle_action(action) {
203 OverlayEffect::None => {}
204 OverlayEffect::Disable => self.disable(),
205 }
206 true
207 } else {
208 false
209 }
210 }
211}
212
213pub fn default_area([mut w, mut h]: [u16; 2], layout: &OverlayLayoutSettings, ui_area: &Rect) -> Rect {
214 if w == 0 {
216 w = layout.percentage[0].compute_with_clamp(ui_area.width, 0);
217 w = w.clamp(layout.min[0], layout.max[0]);
218 }
219 if h == 0 {
220 h = layout.percentage[1].compute_with_clamp(ui_area.height, 0);
221 h = h.clamp(layout.min[1], layout.max[1]);
222 }
223
224 let x = ui_area.x + (ui_area.width.saturating_sub(w)) / 2;
226 let y = ui_area.y + (ui_area.height.saturating_sub(h + 8)) / 2; Rect {
229 x,
230 y,
231 width: w,
232 height: h,
233 }
234}
235
236