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(&mut self, frame: &mut Frame, area: Rect);
42
43 fn area(&mut self, ui_area: &Rect) -> Result<Rect, [u16; 2]> {
49 let _ = ui_area;
50 Err([0, 0])
51 }
52}
53
54pub struct OverlayUI<A: ActionExt> {
57 overlays: Box<[Box<dyn Overlay<A = A>>]>,
58 index: Option<usize>,
59 config: OverlayConfig,
60 cached_area: Rect,
61}
62
63impl<A: ActionExt> OverlayUI<A> {
64 pub fn new(overlays: Box<[Box<dyn Overlay<A = A>>]>, config: OverlayConfig) -> Self {
65 Self {
66 overlays,
67 index: None,
68 config,
69 cached_area: Default::default(),
70 }
71 }
72
73 pub fn index(&self) -> Option<usize> {
74 self.index
75 }
76
77 pub fn enable(&mut self, index: usize, ui_area: &Rect) {
78 assert!(index < self.overlays.len());
79 self.index = Some(index);
80 self.current_mut().unwrap().on_enable(ui_area);
81 self.update_dimensions(ui_area);
82 }
83
84 pub fn disable(&mut self) {
85 if let Some(x) = self.current_mut() {
86 x.on_disable()
87 }
88 self.index = None
89 }
90
91 pub fn current(&self) -> Option<&dyn Overlay<A = A>> {
92 self.index
93 .and_then(|i| self.overlays.get(i))
94 .map(|b| b.as_ref())
95 }
96
97 fn current_mut(&mut self) -> Option<&mut Box<dyn Overlay<A = A> + 'static>> {
98 if let Some(i) = self.index {
99 self.overlays.get_mut(i)
100 } else {
101 None
102 }
103 }
104
105 pub fn update_dimensions(&mut self, ui_area: &Rect) {
107 if let Some(x) = self.current_mut() {
108 self.cached_area = match x.area(ui_area) {
109 Ok(x) => x,
110 Err(pref) => default_area(pref, &self.config.layout, ui_area),
112 };
113 log::debug!("Overlay area: {}", self.cached_area);
114 }
115 }
116
117 pub fn draw(&mut self, frame: &mut Frame) {
120 let area = self.cached_area;
122 let outer_dim = self.config.outer_dim;
123
124 if let Some(x) = self.current_mut() {
125 if outer_dim {
126 Self::dim_surroundings(frame, area)
127 };
128 x.draw(frame, area);
129 }
130 }
131
132 fn dim_surroundings(frame: &mut Frame, inner: Rect) {
134 let full_area = frame.area();
135 let dim_style = Style::default().bg(Color::Black).fg(Color::DarkGray);
136
137 if inner.y > 0 {
139 let top = Rect {
140 x: 0,
141 y: 0,
142 width: full_area.width,
143 height: inner.y,
144 };
145 frame.render_widget(Block::default().style(dim_style), top);
146 }
147
148 if inner.y + inner.height < full_area.height {
150 let bottom = Rect {
151 x: 0,
152 y: inner.y + inner.height,
153 width: full_area.width,
154 height: full_area.height - (inner.y + inner.height),
155 };
156 frame.render_widget(Block::default().style(dim_style), bottom);
157 }
158
159 if inner.x > 0 {
161 let left = Rect {
162 x: 0,
163 y: inner.y,
164 width: inner.x,
165 height: inner.height,
166 };
167 frame.render_widget(Block::default().style(dim_style), left);
168 }
169
170 if inner.x + inner.width < full_area.width {
172 let right = Rect {
173 x: inner.x + inner.width,
174 y: inner.y,
175 width: full_area.width - (inner.x + inner.width),
176 height: inner.height,
177 };
178 frame.render_widget(Block::default().style(dim_style), right);
179 }
180 }
181
182 pub fn handle_input(&mut self, action: char) -> bool {
184 if let Some(x) = self.current_mut() {
185 match x.handle_input(action) {
186 OverlayEffect::None => {}
187 OverlayEffect::Disable => self.disable(),
188 }
189 true
190 } else {
191 false
192 }
193 }
194
195 pub fn handle_action(&mut self, action: &Action<A>) -> bool {
196 if let Some(inner) = self.current_mut() {
197 match inner.handle_action(action) {
198 OverlayEffect::None => {}
199 OverlayEffect::Disable => self.disable(),
200 }
201 true
202 } else {
203 false
204 }
205 }
206}
207
208pub fn default_area(
209 [mut w, mut h]: [u16; 2],
210 layout: &OverlayLayoutSettings,
211 ui_area: &Rect,
212) -> Rect {
213 if w == 0 {
215 w = layout.percentage[0].compute_clamped(ui_area.width, layout.min[0], layout.max[0]);
216 }
217 if h == 0 {
218 h = layout.percentage[1].compute_clamped(ui_area.height, layout.min[1], layout.max[1]);
219 h = h.clamp(layout.min[1], layout.max[1]);
220 }
221
222 let x = ui_area.x + (ui_area.width.saturating_sub(w)) / 2;
224 let y = ui_area.y + (ui_area.height.saturating_sub(h + 8)) / 2; Rect {
227 x,
228 y,
229 width: w,
230 height: h,
231 }
232}
233
234