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