yew_canvas/
lib.rs

1use gloo::{events::EventListener, utils::window};
2use std::ops::Deref;
3use wasm_bindgen::JsCast;
4use web_sys::HtmlCanvasElement;
5use yew::{html::ChildrenRenderer, prelude::*};
6
7/// A Canvas component is encapsulated.
8///
9/// # Parameters and types
10/// ```ignore
11/// <Canvas<...1, ...2>
12///    style="
13///        ...3
14///    "
15///    rander={Box::new(...4)}
16/// />
17/// ```
18/// **...1:** The canvas context u need.
19///
20/// **...2:** struct you impl`yew_canvas::WithRander`.
21///
22/// **...3:** Just use style, canvas can suit automaticly.
23///
24/// **...4:** U have to wrap ur `yew_canvas::WithRander` struct in `Box<>`.
25///
26/// # Example
27///
28/// ```ignore
29/// #[function_component(App)]
30/// pub fn app() -> Html {
31///     html!(
32///         <Canvas<CanvasRenderingContext2d, Rander>
33///             //Just use style, canvas can suit automaticly.
34///             style="
35///                 width: 100%;
36///                 height: 100%;
37///             "
38///             rander={Box::new(Rander())}
39///         />
40///             {"The browser is not supported."}
41///         </Canvas<CanvasRenderingContext2d, Rander>>
42///     )
43/// }
44/// ```
45#[function_component(Canvas)]
46pub fn canvas<CanvasContext, T>(props: &Props<T>) -> Html
47where
48    T: PartialEq + WithRander + Clone + 'static,
49    CanvasContext: JsCast,
50{
51    let node_ref = NodeRef::default();
52    let is_first_rander = use_state(|| true);
53    let style = props.style.clone().unwrap_or(String::new());
54    let display_size = use_state(|| (300, 150));
55
56    let size_listen_enent_state = use_state(|| EventListener::new(&window(), "resize", |_| ()));
57
58    {
59        let node_ref = node_ref.clone();
60        let display_size = display_size.clone();
61        let rander = props.rander.clone();
62
63        use_effect(move || {
64            if let Some(canvas) = node_ref.cast::<HtmlCanvasElement>() {
65                if *is_first_rander {
66                    is_first_rander.set(false);
67                    let canvas = canvas.clone();
68
69                    display_size.set((canvas.client_width(), canvas.client_height()));
70
71                    size_listen_enent_state.set(EventListener::new(
72                        &window(),
73                        "resize",
74                        move |_| {
75                            display_size.set((canvas.client_width(), canvas.client_height()));
76                        },
77                    ));
78                }
79
80                rander.rand(&canvas);
81            }
82
83            || ()
84        });
85    }
86
87    let children = props
88        .children
89        .clone()
90        .unwrap_or(ChildrenRenderer::default());
91
92    html! {
93    <canvas
94        style={style}
95        width={display_size.clone().deref().0.to_string()}
96        height={display_size.deref().1.to_string()}
97        ref={node_ref}
98    >
99    { for children.iter() }
100    </ canvas>
101    }
102}
103
104/// Implement this trait for rendering.
105///
106/// use `&self` to pass data.
107///
108/// # example
109/// ```ignore
110/// #[derive(Clone, PartialEq)]
111///struct Rander();
112///
113///impl WithRander for Rander {
114///    fn rand(self, canvas: &HtmlCanvasElement) {
115///    // CanvasRenderingContext2d can be
116///    // any kind of canvas context.
117///    // Make sure that, it's the same
118///    // context as Canvas component.
119///        let interface: CanvasRenderingContext2d = canvas
120///            .get_context("2d")
121///            .unwrap()
122///            .unwrap()
123///            .dyn_into()
124///            .unwrap();
125///    ...
126/// ```
127pub trait WithRander: Clone + PartialEq {
128    fn rand(self, canvas: &HtmlCanvasElement);
129}
130
131#[derive(Properties, Clone, PartialEq)]
132pub struct Props<T: PartialEq> {
133    pub rander: Box<T>,
134    pub children: Option<Children>,
135    pub style: Option<String>,
136}