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, S>(text: S, 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 S: Into<String>
166 {
167 ClickButton {
168 push_click: Arc::new(Box::new(move |ctx| {
169 let action = action.clone();
170 let ctx = ctx.clone();
171 Box::pin(async move { action(ctx).await })
172 })),
173 button: Button {
174 text: text.into(),
175 icon,
176 state: ButtonState::Default,
177 },
178 }
179 }
180}
181
182impl<C> ToggleButton<C>
183where
184 C: Send + Clone + Sync + 'static,
185{
186 pub fn new<FF, PF, F, P, S>(
191 text: S,
192 icon: Option<&'static str>,
193 fetch_active: F,
194 push_active: P,
195 ) -> Self
196 where
197 FF: Future<Output = Result<bool, Box<dyn std::error::Error>>> + Send + Sync + 'static,
198 PF: Future<Output = Result<(), Box<dyn std::error::Error>>> + Send + Sync + 'static,
199 F: Fn(C) -> FF + Send + Sync + Clone + 'static,
200 P: Fn(C, bool) -> PF + Send + Sync + Clone + 'static,
201 S: Into<String>
202 {
203 let text = text.into();
204 ToggleButton {
205 fetch_active: Arc::new(Box::new(move |ctx| {
206 let fetch_active = fetch_active.clone();
207 let ctx = ctx.clone();
208 Box::pin(async move { fetch_active(ctx).await })
209 })),
210 push_active: Arc::new(Box::new(move |ctx, x| {
211 let push_active = push_active.clone();
212 let ctx = ctx.clone();
213 Box::pin(async move { push_active(ctx, x).await })
214 })),
215 button: Button {
216 text: text.clone(),
217 icon,
218 state: ButtonState::Default,
219 },
220 active_button: Button {
221 text,
222 icon,
223 state: ButtonState::Active,
224 },
225 active: AtomicBool::new(false),
226 }
227 }
228
229 pub fn when_active<S: Into<String>>(self, text: S, icon: Option<&'static str>) -> Self {
233 ToggleButton {
234 active_button: Button {
235 text: text.into(),
236 icon,
237 state: ButtonState::Active,
238 },
239 ..self
240 }
241 }
242}
243
244#[async_trait::async_trait]
245impl<C> CustomButton<C> for ToggleButton<C>
246where
247 C: Send + Clone + Sync + 'static,
248{
249 fn get_state(&self) -> Button {
250 let current_state = self.active.load(Ordering::SeqCst);
251 match current_state {
252 true => self.active_button.clone(),
253 false => self.button.clone(),
254 }
255 }
256
257 async fn fetch(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
258 let new_state = (self.fetch_active)(context).await;
259 self.active.store(new_state?, Ordering::SeqCst);
260 Ok(())
261 }
262
263 async fn click(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
264 let current_state = self.active.load(Ordering::SeqCst);
265 (self.push_active)(context, !current_state).await?;
266 self.active.store(!current_state, Ordering::SeqCst);
267 Ok(())
268 }
269}
270
271#[async_trait::async_trait]
272impl<C> CustomButton<C> for ClickButton<C>
273where
274 C: Send + Clone + Sync + 'static,
275{
276 fn get_state(&self) -> Button {
277 self.button.clone()
278 }
279
280 async fn fetch(&self, _: &C) -> Result<(), Box<dyn std::error::Error>> {
281 Ok(())
282 }
283
284 async fn click(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
285 (self.push_click)(context).await?;
286 Ok(())
287 }
288}
289
290impl<W, H, C, N> Default for CustomizableView<W, H, C, N>
291where
292 W: ArrayLength,
293 H: ArrayLength,
294 C: Send + Clone + Sync + 'static,
295 N: NavigationEntry<W, H, C>,
296{
297 fn default() -> Self {
298 CustomizableView::new()
299 }
300}
301
302impl<W, H, C, N> CustomizableView<W, H, C, N>
303where
304 W: ArrayLength,
305 H: ArrayLength,
306 C: Send + Clone + Sync + 'static,
307 N: NavigationEntry<W, H, C>,
308{
309 pub fn new() -> Self {
311 CustomizableView {
312 matrix: GenericArray::generate(|_| GenericArray::generate(|_| None)),
313 _marker: PhantomData,
314 }
315 }
316
317 pub fn set_button(
321 &mut self,
322 x: usize,
323 y: usize,
324 button: impl CustomButton<C>,
325 ) -> Result<(), Box<dyn std::error::Error>> {
326 if x < W::to_usize() && y < H::to_usize() {
327 self.matrix[y][x] = Some(CustomizableViewButton::Button(Box::new(button)));
328 Ok(())
329 } else {
330 Err(Box::new(std::io::Error::new(
331 std::io::ErrorKind::InvalidInput,
332 "Row or column out of bounds",
333 )))
334 }
335 }
336
337 pub fn set_navigation<S: Into<String>>(
341 &mut self,
342 x: usize,
343 y: usize,
344 navigation: N,
345 text: S,
346 icon: Option<&'static str>,
347 ) -> Result<(), Box<dyn std::error::Error>> {
348 if x < W::to_usize() && y < H::to_usize() {
349 self.matrix[y][x] = Some(CustomizableViewButton::Navigation {
350 navigation,
351 button: Button {
352 text: text.into(),
353 icon,
354 state: ButtonState::Default,
355 },
356 _marker: PhantomData,
357 });
358 Ok(())
359 } else {
360 Err(Box::new(std::io::Error::new(
361 std::io::ErrorKind::InvalidInput,
362 "Row or column out of bounds",
363 )))
364 }
365 }
366
367 pub fn remove_button(&mut self, x: usize, y: usize) -> Result<(), Box<dyn std::error::Error>> {
371 if x < W::to_usize() && y < H::to_usize() {
372 self.matrix[y][x] = None;
373 Ok(())
374 } else {
375 Err(Box::new(std::io::Error::new(
376 std::io::ErrorKind::InvalidInput,
377 "Row or column out of bounds",
378 )))
379 }
380 }
381}
382
383#[async_trait::async_trait]
384impl<W, H, C, N> View<W, H, C, N> for CustomizableView<W, H, C, N>
385where
386 W: ArrayLength,
387 H: ArrayLength,
388 C: Send + Clone + Sync + 'static,
389 N: NavigationEntry<W, H, C>,
390{
391 async fn render(&self) -> Result<ButtonMatrix<W, H>, Box<dyn std::error::Error>> {
392 let mut button_matrix = ButtonMatrix::new();
393 for x in 0..W::to_usize() {
394 for y in 0..H::to_usize() {
395 if let Some(button) = &self.matrix[y][x] {
396 let state = match button {
397 CustomizableViewButton::Navigation { button, .. } => button,
398 CustomizableViewButton::Button(button) => &button.get_state(),
399 };
400 button_matrix.set_button(x, y, state.clone())?;
401 }
402 }
403 }
404 Ok(button_matrix)
405 }
406
407 async fn on_click(
408 &self,
409 context: &C,
410 index: u8,
411 navigation: Arc<mpsc::Sender<N>>,
412 ) -> Result<(), Box<dyn std::error::Error>> {
413 if (index as usize) < W::to_usize() * H::to_usize() {
414 let x = index % W::to_u8();
415 let y = index / W::to_u8();
416 if let Some(button) = &self.matrix[y as usize][x as usize] {
417 match button {
418 CustomizableViewButton::Navigation { navigation: nav, .. } => {
419 navigation.send(nav.clone()).await?;
420 }
421 CustomizableViewButton::Button(button) => {
422 button.click(context).await?;
423 }
424 }
425 }
426 Ok(())
427 } else {
428 return Err(Box::new(std::io::Error::new(
429 std::io::ErrorKind::InvalidInput,
430 "Button index out of bounds",
431 )));
432 }
433 }
434
435 async fn fetch_all(&self, context: &C) -> Result<(), Box<dyn std::error::Error>> {
436 for x in 0..W::to_usize() {
437 for y in 0..H::to_usize() {
438 if let Some(button) = &self.matrix[y][x] {
439 match button {
440 CustomizableViewButton::Navigation { .. } => {}
441 CustomizableViewButton::Button(button) => {
442 button.fetch(context).await?;
443 }
444 }
445 }
446 }
447 }
448 Ok(())
449 }
450}