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}