hyperchad_renderer/
lib.rs

1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3#![allow(clippy::multiple_crate_versions)]
4
5#[cfg(feature = "assets")]
6pub mod assets;
7#[cfg(feature = "canvas")]
8pub mod canvas;
9#[cfg(feature = "viewport")]
10pub mod viewport;
11
12use std::{future::Future, pin::Pin};
13
14use async_trait::async_trait;
15pub use hyperchad_color::Color;
16use hyperchad_transformer::{Container, ResponsiveTrigger, html::ParseError};
17pub use tokio::runtime::Handle;
18
19pub use hyperchad_transformer as transformer;
20
21#[derive(Debug)]
22pub enum RendererEvent {
23    View(View),
24    Partial(PartialView),
25    #[cfg(feature = "canvas")]
26    CanvasUpdate(canvas::CanvasUpdate),
27    Event {
28        name: String,
29        value: Option<String>,
30    },
31}
32
33pub enum Content {
34    View(View),
35    PartialView(PartialView),
36    #[cfg(feature = "json")]
37    Json(serde_json::Value),
38}
39
40#[derive(Default, Debug, Clone)]
41pub struct PartialView {
42    pub target: String,
43    pub container: Container,
44}
45
46#[derive(Default)]
47pub struct View {
48    pub future: Option<Pin<Box<dyn Future<Output = Container> + Send>>>,
49    pub immediate: Container,
50}
51
52impl std::fmt::Debug for View {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.debug_struct("View")
55            .field("future", &self.future.is_some())
56            .field("immediate", &self.immediate)
57            .finish()
58    }
59}
60
61#[cfg(feature = "json")]
62impl TryFrom<serde_json::Value> for Content {
63    type Error = serde_json::Error;
64
65    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
66        Ok(Self::Json(value))
67    }
68}
69
70#[cfg(feature = "maud")]
71impl TryFrom<maud::Markup> for Content {
72    type Error = ParseError;
73
74    fn try_from(value: maud::Markup) -> Result<Self, Self::Error> {
75        Ok(Self::View(value.into_string().try_into()?))
76    }
77}
78
79impl<'a> TryFrom<&'a str> for Content {
80    type Error = ParseError;
81
82    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
83        Ok(Self::View(View {
84            future: None,
85            immediate: value.try_into()?,
86        }))
87    }
88}
89
90impl TryFrom<String> for Content {
91    type Error = ParseError;
92
93    fn try_from(value: String) -> Result<Self, Self::Error> {
94        Ok(Self::View(View {
95            future: None,
96            immediate: value.try_into()?,
97        }))
98    }
99}
100
101impl From<Container> for Content {
102    fn from(value: Container) -> Self {
103        Self::View(View {
104            future: None,
105            immediate: value,
106        })
107    }
108}
109
110impl From<View> for Content {
111    fn from(value: View) -> Self {
112        Self::View(value)
113    }
114}
115
116impl From<PartialView> for Content {
117    fn from(value: PartialView) -> Self {
118        Self::PartialView(value)
119    }
120}
121
122#[cfg(feature = "maud")]
123impl TryFrom<maud::Markup> for View {
124    type Error = ParseError;
125
126    fn try_from(value: maud::Markup) -> Result<Self, Self::Error> {
127        value.into_string().try_into()
128    }
129}
130
131impl<'a> TryFrom<&'a str> for View {
132    type Error = ParseError;
133
134    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
135        Ok(Self {
136            future: None,
137            immediate: value.try_into()?,
138        })
139    }
140}
141
142impl TryFrom<String> for View {
143    type Error = ParseError;
144
145    fn try_from(value: String) -> Result<Self, Self::Error> {
146        Ok(Self {
147            future: None,
148            immediate: value.try_into()?,
149        })
150    }
151}
152
153impl From<Container> for View {
154    fn from(value: Container) -> Self {
155        Self {
156            future: None,
157            immediate: value,
158        }
159    }
160}
161
162pub trait RenderRunner: Send + Sync {
163    /// # Errors
164    ///
165    /// Will error if fails to run
166    fn run(&mut self) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
167}
168
169pub trait ToRenderRunner {
170    /// # Errors
171    ///
172    /// * If failed to convert the value to a `RenderRunner`
173    fn to_runner(
174        self,
175        handle: Handle,
176    ) -> Result<Box<dyn RenderRunner>, Box<dyn std::error::Error + Send>>;
177}
178
179#[async_trait]
180pub trait Renderer: ToRenderRunner + Send + Sync {
181    /// # Errors
182    ///
183    /// Will error if `Renderer` implementation app fails to start
184    #[allow(clippy::too_many_arguments)]
185    async fn init(
186        &mut self,
187        width: f32,
188        height: f32,
189        x: Option<i32>,
190        y: Option<i32>,
191        background: Option<Color>,
192        title: Option<&str>,
193        description: Option<&str>,
194        viewport: Option<&str>,
195    ) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
196
197    fn add_responsive_trigger(&mut self, name: String, trigger: ResponsiveTrigger);
198
199    /// # Errors
200    ///
201    /// Will error if `Renderer` implementation fails to emit the event.
202    async fn emit_event(
203        &self,
204        event_name: String,
205        event_value: Option<String>,
206    ) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
207
208    /// # Errors
209    ///
210    /// Will error if `Renderer` implementation fails to render the view.
211    async fn render(&self, view: View) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
212
213    /// # Errors
214    ///
215    /// Will error if `Renderer` implementation fails to render the partial elements.
216    async fn render_partial(
217        &self,
218        partial: PartialView,
219    ) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
220
221    /// # Errors
222    ///
223    /// Will error if `Renderer` implementation fails to render the canvas update.
224    #[cfg(feature = "canvas")]
225    async fn render_canvas(
226        &self,
227        update: canvas::CanvasUpdate,
228    ) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
229}
230
231#[cfg(feature = "html")]
232pub trait HtmlTagRenderer {
233    fn add_responsive_trigger(&mut self, name: String, trigger: ResponsiveTrigger);
234
235    /// # Errors
236    ///
237    /// * If the `HtmlTagRenderer` fails to write the element attributes
238    fn element_attrs_to_html(
239        &self,
240        f: &mut dyn std::io::Write,
241        container: &Container,
242        is_flex_child: bool,
243    ) -> Result<(), std::io::Error>;
244
245    /// # Errors
246    ///
247    /// * If the `HtmlTagRenderer` fails to write the css media-queries
248    fn reactive_conditions_to_css(
249        &self,
250        _f: &mut dyn std::io::Write,
251        _container: &Container,
252    ) -> Result<(), std::io::Error> {
253        Ok(())
254    }
255
256    fn partial_html(
257        &self,
258        headers: &std::collections::HashMap<String, String>,
259        container: &Container,
260        content: String,
261        viewport: Option<&str>,
262        background: Option<Color>,
263    ) -> String;
264
265    #[allow(clippy::too_many_arguments)]
266    fn root_html(
267        &self,
268        headers: &std::collections::HashMap<String, String>,
269        container: &Container,
270        content: String,
271        viewport: Option<&str>,
272        background: Option<Color>,
273        title: Option<&str>,
274        description: Option<&str>,
275    ) -> String;
276}