streamdeck_oxide/view/
manager.rs

1//! Display manager for the view system.
2//!
3//! This module provides a display manager for the view system.
4
5use std::{marker::PhantomData, sync::Arc};
6
7use elgato_streamdeck::AsyncStreamDeck;
8use generic_array::ArrayLength;
9use tokio::sync::{mpsc, RwLock};
10
11use crate::{
12    button::{render_button, RenderConfig},
13    navigation::NavigationEntry,
14    theme::Theme,
15};
16
17use super::{button::ButtonState, matrix::ButtonMatrix, View};
18
19/// A display manager for the view system.
20///
21/// This struct manages the display of views on the Stream Deck.
22/// It handles rendering, navigation, and event processing.
23pub struct DisplayManager<N: NavigationEntry<W, H, C>, W, H, C>
24where
25    W: ArrayLength,
26    H: ArrayLength,
27    C: Send + Clone + Sync + 'static,
28{
29    /// The render configuration.
30    pub(crate) config: RenderConfig,
31    /// The theme.
32    pub(crate) theme: Theme,
33    /// The Stream Deck.
34    pub(crate) deck: Arc<AsyncStreamDeck>,
35    /// The current view.
36    pub(crate) view: RwLock<Box<dyn View<W, H, C, N>>>,
37    /// Phantom data for the navigation type.
38    pub(crate) _navigation: PhantomData<N>,
39    /// Phantom data for the width.
40    pub(crate) _width: PhantomData<W>,
41    /// Phantom data for the height.
42    pub(crate) _height: PhantomData<H>,
43    /// The sender for navigation events.
44    pub(crate) navigation_sender: Arc<mpsc::Sender<N>>,
45    /// The application context.
46    pub(crate) context: C,
47}
48
49impl<N: NavigationEntry<W, H, C>, W, H, C> DisplayManager<N, W, H, C>
50where
51    W: ArrayLength,
52    H: ArrayLength,
53    C: Send + Clone + Sync + 'static,
54{
55    /// Create a new display manager.
56    ///
57    /// This method creates a new display manager with the given
58    /// Stream Deck, render configuration, theme, and context.
59    pub fn new(
60        deck: Arc<AsyncStreamDeck>,
61        config: RenderConfig,
62        theme: Theme,
63        context: C,
64    ) -> Result<(Self, mpsc::Receiver<N>), Box<dyn std::error::Error>> {
65        let (sender, receiver) = mpsc::channel(1);
66        let sender = Arc::new(sender);
67        Ok((
68            Self {
69                config,
70                theme,
71                deck,
72                view: RwLock::new(N::default().get_view()?),
73                _navigation: PhantomData,
74                _width: PhantomData,
75                _height: PhantomData,
76                navigation_sender: sender.clone(),
77                context,
78            },
79            receiver,
80        ))
81    }
82
83    /// Navigate to a new view.
84    ///
85    /// This method navigates to the view associated with the given
86    /// navigation entry.
87    pub async fn navigate_to(
88        &self,
89        navigation_entry: N,
90    ) -> Result<(), Box<dyn std::error::Error>> {
91        let mut view = self.view.write().await;
92        *view = navigation_entry.get_view()?;
93        Ok(())
94    }
95
96    /// Render the current view.
97    ///
98    /// This method renders the current view to the Stream Deck.
99    pub async fn render(&self) -> Result<(), Box<dyn std::error::Error>> {
100        let view = self.view.read().await;
101        let button_matrix = view.render().await?;
102        self.render_matrix(&button_matrix).await?;
103        Ok(())
104    }
105
106    /// Fetch state for all buttons in the current view.
107    ///
108    /// This method fetches the state for all buttons in the current view.
109    pub async fn fetch_all(&self) -> Result<(), Box<dyn std::error::Error>> {
110        let view = self.view.read().await;
111        let result = view.fetch_all(&self.context).await;
112        if let Err(e) = result {
113            eprintln!("Error fetching view state: {}", e);
114        }
115        Ok(())
116    }
117
118    /// Render a button matrix to the Stream Deck.
119    ///
120    /// This method renders the given button matrix to the Stream Deck.
121    async fn render_matrix(
122        &self,
123        button_matrix: &ButtonMatrix<W, H>,
124    ) -> Result<(), Box<dyn std::error::Error>> {
125        for x in 0..W::to_usize() {
126            for y in 0..H::to_usize() {
127                let button = &button_matrix.buttons[y][x];
128                let button_index = (y * W::to_usize() + x) as u8;
129                let background_color = match button.state {
130                    ButtonState::Default => self.theme.background,
131                    ButtonState::Active => self.theme.active_background,
132                    ButtonState::Inactive => self.theme.inactive_background,
133                    ButtonState::Error => self.theme.error_background,
134                    ButtonState::Pressed => self.theme.pressed_background,
135                };
136                let foreground_color = match button.state {
137                    ButtonState::Default => self.theme.foreground_color,
138                    ButtonState::Active => self.theme.active_foreground_color,
139                    ButtonState::Inactive => self.theme.foreground_color,
140                    ButtonState::Error => self.theme.foreground_color,
141                    ButtonState::Pressed => self.theme.active_foreground_color,
142                };
143                let raw_button = match button.icon {
144                    Some(icon) => crate::button::Button::IconWithText {
145                        svg_data: icon,
146                        text: button.text.to_string(),
147                        background: background_color,
148                        foreground: foreground_color,
149                    },
150                    None => crate::button::Button::Text {
151                        text: button.text.to_string(),
152                        background: background_color,
153                        foreground: foreground_color,
154                    },
155                };
156                let image = render_button(&raw_button, &self.config)?;
157                self.deck.set_button_image(button_index, image).await?;
158            }
159            self.deck.flush().await?;
160        }
161        Ok(())
162    }
163
164    /// Handle a button press.
165    ///
166    /// This method is called when a button is pressed. It updates
167    /// the button state to pressed.
168    pub async fn on_press(&self, button: u8) -> Result<(), Box<dyn std::error::Error>> {
169        let view = self.view.read().await;
170        let mut button_matrix = view.render().await?;
171        let button_index = button as usize;
172        let button = button_matrix
173            .get_button_by_index(button_index)
174            .ok_or("Button not found")?;
175        let new_button = button.updated_state(ButtonState::Pressed);
176        button_matrix.set_button_by_index(button_index, new_button)?;
177        self.render_matrix(&button_matrix).await?;
178        Ok(())
179    }
180
181    /// Handle a button release.
182    ///
183    /// This method is called when a button is released. It calls
184    /// the on_click method of the current view.
185    pub async fn on_release(&self, button: u8) -> Result<(), Box<dyn std::error::Error>> {
186        let view = self.view.read().await;
187        let result = view.on_click(&self.context, button, self.navigation_sender.clone())
188            .await;
189        if let Err(e) = result {
190            eprintln!("Error handling button click: {}", e);
191        }
192        self.render().await?;
193        Ok(())
194    }
195}