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    /// Current navigation entry
48    pub(crate) current_navigation: RwLock<N>,
49}
50
51impl<N: NavigationEntry<W, H, C>, W, H, C> DisplayManager<N, W, H, C>
52where
53    W: ArrayLength,
54    H: ArrayLength,
55    C: Send + Clone + Sync + 'static,
56{
57    /// Create a new display manager.
58    ///
59    /// This method creates a new display manager with the given
60    /// Stream Deck, render configuration, theme, and context.
61    pub async fn new(
62        deck: Arc<AsyncStreamDeck>,
63        config: RenderConfig,
64        theme: Theme,
65        context: C,
66    ) -> Result<(Self, mpsc::Receiver<N>), Box<dyn std::error::Error>> {
67        let (sender, receiver) = mpsc::channel(1);
68        let sender = Arc::new(sender);
69        Ok((
70            Self {
71                config,
72                theme,
73                deck,
74                view: RwLock::new(N::default().get_view(context.clone()).await?),
75                _navigation: PhantomData,
76                _width: PhantomData,
77                _height: PhantomData,
78                navigation_sender: sender.clone(),
79                context,
80                current_navigation: RwLock::new(N::default()),
81            },
82            receiver,
83        ))
84    }
85
86    /// Navigate to a new view.
87    ///
88    /// This method navigates to the view associated with the given
89    /// navigation entry.
90    pub async fn navigate_to(&self, navigation_entry: N) -> Result<(), Box<dyn std::error::Error>> {
91        let mut view = self.view.write().await;
92        let mut current_navigation = self.current_navigation.write().await;
93        *view = navigation_entry.get_view(self.context.clone()).await?;
94        *current_navigation = navigation_entry.clone();
95        Ok(())
96    }
97
98    /// Get current navigation entry.
99    ///
100    /// This method returns the current navigation entry.
101    pub async fn get_current_navigation(
102        &self,
103    ) -> Result<N, Box<dyn std::error::Error>> {
104        let current_navigation = self.current_navigation.read().await;
105        Ok(current_navigation.clone())
106    }
107
108    /// Render the current view.
109    ///
110    /// This method renders the current view to the Stream Deck.
111    pub async fn render(&self) -> Result<(), Box<dyn std::error::Error>> {
112        let view = self.view.read().await;
113        let button_matrix = view.render().await?;
114        self.render_matrix(&button_matrix).await?;
115        Ok(())
116    }
117
118    /// Fetch state for all buttons in the current view.
119    ///
120    /// This method fetches the state for all buttons in the current view.
121    pub async fn fetch_all(&self) -> Result<(), Box<dyn std::error::Error>> {
122        let view = self.view.read().await;
123        let result = view.fetch_all(&self.context).await;
124        if let Err(e) = result {
125            eprintln!("Error fetching view state: {}", e);
126        }
127        Ok(())
128    }
129
130    /// Render a button matrix to the Stream Deck.
131    ///
132    /// This method renders the given button matrix to the Stream Deck.
133    async fn render_matrix(
134        &self,
135        button_matrix: &ButtonMatrix<W, H>,
136    ) -> Result<(), Box<dyn std::error::Error>> {
137        for x in 0..W::to_usize() {
138            for y in 0..H::to_usize() {
139                let button = &button_matrix.buttons[y][x];
140                let button_index = (y * W::to_usize() + x) as u8;
141                let theme = button.theme.as_ref().unwrap_or(&self.theme);
142                let background_color = match button.state {
143                    ButtonState::Default => theme.background,
144                    ButtonState::Active => theme.active_background,
145                    ButtonState::Inactive => theme.inactive_background,
146                    ButtonState::Error => theme.error_background,
147                    ButtonState::Pressed => theme.pressed_background,
148                };
149                let foreground_color = match button.state {
150                    ButtonState::Default => theme.foreground_color,
151                    ButtonState::Active => theme.active_foreground_color,
152                    ButtonState::Inactive => theme.foreground_color,
153                    ButtonState::Error => theme.foreground_color,
154                    ButtonState::Pressed => theme.active_foreground_color,
155                };
156                let raw_button = match button.icon {
157                    Some(icon) => crate::button::Button::IconWithText {
158                        svg_data: icon,
159                        text: button.text.to_string(),
160                        background: background_color,
161                        foreground: foreground_color,
162                    },
163                    None => crate::button::Button::Text {
164                        text: button.text.to_string(),
165                        background: background_color,
166                        foreground: foreground_color,
167                    },
168                };
169                let image = render_button(&raw_button, &self.config)?;
170                self.deck.set_button_image(button_index, image).await?;
171            }
172            self.deck.flush().await?;
173        }
174        Ok(())
175    }
176
177    /// Handle a button press.
178    ///
179    /// This method is called when a button is pressed. It updates
180    /// the button state to pressed.
181    pub async fn on_press(&self, button: u8) -> Result<(), Box<dyn std::error::Error>> {
182        let view = self.view.read().await;
183        let mut button_matrix = view.render().await?;
184        let button_index = button as usize;
185        let button = button_matrix
186            .get_button_by_index(button_index)
187            .ok_or("Button not found")?;
188        let new_button = button.updated_state(ButtonState::Pressed);
189        button_matrix.set_button_by_index(button_index, new_button)?;
190        self.render_matrix(&button_matrix).await?;
191        Ok(())
192    }
193
194    /// Handle a button release.
195    ///
196    /// This method is called when a button is released. It calls
197    /// the on_click method of the current view.
198    pub async fn on_release(&self, button: u8) -> Result<(), Box<dyn std::error::Error>> {
199        let view = self.view.read().await;
200        let result = view
201            .on_click(&self.context, button, self.navigation_sender.clone())
202            .await;
203        if let Err(e) = result {
204            eprintln!("Error handling button click: {}", e);
205        }
206        self.render().await?;
207        Ok(())
208    }
209}