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 fn run(&mut self) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
167}
168
169pub trait ToRenderRunner {
170 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 #[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 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 async fn render(&self, view: View) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
212
213 async fn render_partial(
217 &self,
218 partial: PartialView,
219 ) -> Result<(), Box<dyn std::error::Error + Send + 'static>>;
220
221 #[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 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 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}