1use std::{
7 future::Future,
8 marker::PhantomData,
9 pin::Pin,
10 sync::{
11 atomic::{AtomicBool, Ordering},
12 Arc,
13 },
14};
15
16use generic_array::{sequence::GenericSequence, GenericArray, ArrayLength};
17use tokio::sync::mpsc;
18
19use crate::navigation::NavigationEntry;
20
21use super::{button::Button, button::ButtonState, matrix::ButtonMatrix, View};
22
23type Matrix<W, H, C, N> = GenericArray<GenericArray<Option<CustomizableViewButton<W, H, C, N>>, W>, H>;
24
25pub struct CustomizableView<W, H, C, N>
30where
31 W: ArrayLength,
32 H: ArrayLength,
33 C: Send + Clone + Sync + 'static,
34 N: NavigationEntry<W, H, C>,
35{
36 pub(crate) matrix: Matrix<W, H, C, N>,
38 pub(crate) _marker: PhantomData<N>,
40}
41
42pub enum CustomizableViewButton<W, H, C, N>
47where
48 W: ArrayLength,
49 H: ArrayLength,
50 C: Send + Clone + Sync + 'static,
51 N: NavigationEntry<W, H, C>,
52{
53 Navigation {
57 navigation: N,
59 button: Button,
61 _marker: PhantomData<fn() -> (W, H)>
63 },
64 Button(Box<dyn CustomButton<C>>),
68}
69
70#[async_trait::async_trait]
76pub trait CustomButton<C>: Send + Sync + 'static
77where
78 C: Send + Clone + Sync + 'static,
79{
80 fn get_state(&self) -> Button;
84
85 async fn fetch(&self, context: &C) -> Result<(), Box<dyn std::error::Error>>;
90
91 async fn click(&self, context: &C) -> Result<(), Box<dyn std::error::Error>>;
96}
97
98pub type FetchFuture =
100 Pin<Box<dyn Future<Output = Result<bool, Box<dyn std::error::Error>>> + Send + Sync>>;
101
102pub type FetchFunction<C> = Arc<Box<dyn Fn(&C) -> FetchFuture + Send + Sync>>;
104
105pub type ClickFuture =
107 Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error>>> + Send + Sync>>;
108
109pub type ClickAction<C> = Arc<Box<dyn Fn(&C) -> ClickFuture + Send + Sync>>;
111
112pub type PushFuture =
114 Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error>>> + Send + Sync>>;
115
116pub type PushFunction<C> = Arc<Box<dyn Fn(&C, bool) -> PushFuture + Send + Sync>>;
118
119pub struct ToggleButton<C>
124where
125 C: Send + Clone + Sync + 'static,
126{
127 pub(crate) fetch_active: FetchFunction<C>,
129 pub(crate) push_active: PushFunction<C>,
131 pub(crate) button: Button,
133 pub(crate) active_button: Button,
135 pub(crate) active: AtomicBool,
137}
138
139pub struct ClickButton<C>
144where
145 C: Send + Clone + Sync + 'static,
146{
147 pub(crate) push_click: ClickAction<C>,
149 pub(crate) button: Button,
151}
152
153impl<C> ClickButton<C>
154where
155 C: Send + Clone + Sync + 'static,
156{
157 pub fn new<A, F>(text: &'static str, icon: Option<&'static str>, action: A) -> Self
162 where
163 F: Future<Output = Result<(), Box<dyn std::error::Error>>> + Send + Sync + 'static,
164 A: Fn(C) -> F + Send + Sync + Clone + 'static,
165 {
166 ClickButton {
167 push_click: Arc::new(Box::new(move |ctx| {
168 let action = action.clone();
169 let ctx = ctx.clone();
170 Box::pin(async move { action(ctx).await })
171 })),
172 button: Button {
173 text,
174 icon,
175 state: ButtonState::Default,
176 },
177 }
178 }
179}
180
181impl<C> ToggleButton<C>
182where
183 C: Send + Clone + Sync + 'static,
184{
185 pub fn new<FF, PF, F, P>(
190 text: &'static str,
191 icon: Option<&'static str>,
192 fetch_active: F,
193 push_active: P,
194 ) -> Self
195 where
196 FF: Future<Output = Result<bool, Box<dyn std::error::Error>>> + Send + Sync + 'static,
197 PF: Future<Output = Result<(), Box<dyn std::error::Error>>> + Send + Sync + 'static,
198 F: Fn(C) -> FF + Send + Sync + Clone + 'static,
199 P: Fn(C, bool) -> PF + Send + Sync + Clone + 'static,
200 {
201 ToggleButton {
202 fetch_active: Arc::new(Box::new(move |ctx| {
203 let fetch_active = fetch_active.clone();
204 let ctx = ctx.clone();
205 Box::pin(async move { fetch_active(ctx).await })
206 })),
207 push_active: Arc::new(Box::new(move |ctx, x| {
208 let push_active = push_active.clone();
209 let ctx = ctx.clone();
210 Box::pin(async move { push_active(ctx, x).await })
211 })),
212 button: Button {
213 text,
214 icon,
215 state: ButtonState::Default,
216 },
217 active_button: Button {
218 text,
219 icon,
220 state: ButtonState::Active,
221 },
222 active: AtomicBool::new(false),
223 }
224 }
225
226 pub fn when_active(self, text: &'static str, icon: Option<&'static str>) -> Self {
230 ToggleButton {
231 active_button: Button {
232 text,
233 icon,
234 state: ButtonState::Active,
235 },
236 ..self
237 }
238 }
239}
240
241#[async_trait::async_trait]
242impl<C> CustomButton<C> for ToggleButton<C>
243where
244 C: Send + Clone + Sync + 'static,
245{
246 fn get_state(&self) -> Button {
247 let current_state = self.active.load(Ordering::SeqCst);
248 match current_state {
249 true => self.active_button,
250 false => self.button,
251 }
252 }
253
254 async fn fetch(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
255 let new_state = (self.fetch_active)(context).await;
256 self.active.store(new_state?, Ordering::SeqCst);
257 Ok(())
258 }
259
260 async fn click(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
261 let current_state = self.active.load(Ordering::SeqCst);
262 (self.push_active)(context, !current_state).await?;
263 self.active.store(!current_state, Ordering::SeqCst);
264 Ok(())
265 }
266}
267
268#[async_trait::async_trait]
269impl<C> CustomButton<C> for ClickButton<C>
270where
271 C: Send + Clone + Sync + 'static,
272{
273 fn get_state(&self) -> Button {
274 self.button
275 }
276
277 async fn fetch(&self, _: &C) -> Result<(), Box<dyn std::error::Error>> {
278 Ok(())
279 }
280
281 async fn click(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
282 (self.push_click)(context).await?;
283 Ok(())
284 }
285}
286
287impl<W, H, C, N> Default for CustomizableView<W, H, C, N>
288where
289 W: ArrayLength,
290 H: ArrayLength,
291 C: Send + Clone + Sync + 'static,
292 N: NavigationEntry<W, H, C>,
293{
294 fn default() -> Self {
295 CustomizableView::new()
296 }
297}
298
299impl<W, H, C, N> CustomizableView<W, H, C, N>
300where
301 W: ArrayLength,
302 H: ArrayLength,
303 C: Send + Clone + Sync + 'static,
304 N: NavigationEntry<W, H, C>,
305{
306 pub fn new() -> Self {
308 CustomizableView {
309 matrix: GenericArray::generate(|_| GenericArray::generate(|_| None)),
310 _marker: PhantomData,
311 }
312 }
313
314 pub fn set_button(
318 &mut self,
319 x: usize,
320 y: usize,
321 button: impl CustomButton<C>,
322 ) -> Result<(), Box<dyn std::error::Error>> {
323 if x < W::to_usize() && y < H::to_usize() {
324 self.matrix[y][x] = Some(CustomizableViewButton::Button(Box::new(button)));
325 Ok(())
326 } else {
327 Err(Box::new(std::io::Error::new(
328 std::io::ErrorKind::InvalidInput,
329 "Row or column out of bounds",
330 )))
331 }
332 }
333
334 pub fn set_navigation(
338 &mut self,
339 x: usize,
340 y: usize,
341 navigation: N,
342 text: &'static str,
343 icon: Option<&'static str>,
344 ) -> Result<(), Box<dyn std::error::Error>> {
345 if x < W::to_usize() && y < H::to_usize() {
346 self.matrix[y][x] = Some(CustomizableViewButton::Navigation {
347 navigation,
348 button: Button {
349 text,
350 icon,
351 state: ButtonState::Default,
352 },
353 _marker: PhantomData,
354 });
355 Ok(())
356 } else {
357 Err(Box::new(std::io::Error::new(
358 std::io::ErrorKind::InvalidInput,
359 "Row or column out of bounds",
360 )))
361 }
362 }
363
364 pub fn remove_button(&mut self, x: usize, y: usize) -> Result<(), Box<dyn std::error::Error>> {
368 if x < W::to_usize() && y < H::to_usize() {
369 self.matrix[y][x] = None;
370 Ok(())
371 } else {
372 Err(Box::new(std::io::Error::new(
373 std::io::ErrorKind::InvalidInput,
374 "Row or column out of bounds",
375 )))
376 }
377 }
378}
379
380#[async_trait::async_trait]
381impl<W, H, C, N> View<W, H, C, N> for CustomizableView<W, H, C, N>
382where
383 W: ArrayLength,
384 H: ArrayLength,
385 C: Send + Clone + Sync + 'static,
386 N: NavigationEntry<W, H, C>,
387{
388 async fn render(&self) -> Result<ButtonMatrix<W, H>, Box<dyn std::error::Error>> {
389 let mut button_matrix = ButtonMatrix::new();
390 for x in 0..W::to_usize() {
391 for y in 0..H::to_usize() {
392 if let Some(button) = &self.matrix[y][x] {
393 let state = match button {
394 CustomizableViewButton::Navigation { button, .. } => button,
395 CustomizableViewButton::Button(button) => &button.get_state(),
396 };
397 button_matrix.set_button(x, y, *state)?;
398 }
399 }
400 }
401 Ok(button_matrix)
402 }
403
404 async fn on_click(
405 &self,
406 context: &C,
407 index: u8,
408 navigation: Arc<mpsc::Sender<N>>,
409 ) -> Result<(), Box<dyn std::error::Error>> {
410 if (index as usize) < W::to_usize() * H::to_usize() {
411 let x = index % W::to_u8();
412 let y = index / W::to_u8();
413 if let Some(button) = &self.matrix[y as usize][x as usize] {
414 match button {
415 CustomizableViewButton::Navigation { navigation: nav, .. } => {
416 navigation.send(nav.clone()).await?;
417 }
418 CustomizableViewButton::Button(button) => {
419 button.click(context).await?;
420 }
421 }
422 }
423 Ok(())
424 } else {
425 return Err(Box::new(std::io::Error::new(
426 std::io::ErrorKind::InvalidInput,
427 "Button index out of bounds",
428 )));
429 }
430 }
431
432 async fn fetch_all(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
433 for x in 0..W::to_usize() {
434 for y in 0..H::to_usize() {
435 if let Some(button) = &self.matrix[y][x] {
436 match button {
437 CustomizableViewButton::Navigation { .. } => {}
438 CustomizableViewButton::Button(button) => {
439 button.fetch(context).await?;
440 }
441 }
442 }
443 }
444 }
445 Ok(())
446 }
447}