@@ -0,0 +1,285 @@
1+mod colors;
2+
3+use cfg_if::cfg_if;
4+use dodrio::bumpalo::{self, Bump};
5+use dodrio::{Node, Render, Vdom};
6+use std::cell::RefCell;
7+use std::rc::Rc;
8+use std::str;
9+use wasm_bindgen::prelude::*;
10+use wasm_bindgen::JsCast;
11+
12+/// This is the main function that is automatically invoked when the wasm module
13+/// is loaded.
14+#[wasm_bindgen(start)]
15+pub fn run() {
16+ // Set up the panic hook for debugging when things go wrong.
17+ init_logging();
18+
19+ // Grab the document's `<body>`.
20+ let window = web_sys::window().unwrap_throw();
21+ let document = window.document().unwrap_throw();
22+ let scene = document.get_element_by_id("scene").unwrap_throw();
23+
24+ // Create a new `Moire` render component.
25+ let component = Moire::new();
26+
27+ // Create a virtual DOM and mount it and the `Hello` render component to the
28+ // scene.
29+ let vdom = Vdom::new(&scene, component);
30+
31+ // Kick off a loop that keeps re-rendering on every animation frame.
32+ let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
33+ let rc2 = rc.clone();
34+ let window2 = window.clone();
35+ let weak = vdom.weak();
36+ let f = Closure::wrap(Box::new(move || {
37+ weak.schedule_render();
38+ window
39+ .request_animation_frame(
40+ rc.borrow()
41+ .as_ref()
42+ .unwrap_throw()
43+ .as_ref()
44+ .unchecked_ref::<js_sys::Function>(),
45+ )
46+ .unwrap_throw();
47+ }) as Box<FnMut()>);
48+ window2
49+ .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
50+ .unwrap_throw();
51+ *rc2.borrow_mut() = Some(f);
52+
53+ // Run the virtual DOM forever and don't unmount it.
54+ vdom.forget();
55+}
56+
57+cfg_if! {
58+ if #[cfg(feature = "logging")] {
59+ fn init_logging() {
60+ console_error_panic_hook::set_once();
61+ console_log::init_with_level(log::Level::Trace)
62+ .expect_throw("should initialize logging OK");
63+ }
64+ } else {
65+ fn init_logging() {
66+ // Do nothing.
67+ }
68+ }
69+}
70+
71+/// A rendering component that renders two overlapping sets of concentric
72+/// circles that move around and form Moiré patterns.
73+///
74+/// https://en.wikipedia.org/wiki/Moir%C3%A9_pattern
75+pub struct Moire {
76+ // How many circles to render in each of our sets. This is controlled by the
77+ // `<input>` slider.
78+ count: u32,
79+}
80+
81+impl Moire {
82+ /// Construct a new `Moire` rendering component.
83+ ///
84+ /// Defaults to 200 circles.
85+ pub fn new() -> Moire {
86+ Moire { count: 200 }
87+ }
88+
89+ /// Callback for "change" events emitted on the `<input>` slider that
90+ /// controls how many circles we render on each frame.
91+ fn on_change(&mut self, event: web_sys::Event) {
92+ // Get the `<input>` element.
93+ let input = event
94+ .target()
95+ .unwrap_throw()
96+ .unchecked_into::<web_sys::HtmlInputElement>();
97+
98+ // Parse its new value.
99+ let value = input.value();
100+ let value: u32 = value
101+ .parse()
102+ .expect_throw("<input type=\"range\"> value should always be an integer");
103+
104+ // And update our circle count!
105+ self.count = value;
106+ }
107+}
108+
109+impl Render for Moire {
110+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
111+ where
112+ 'a: 'bump,
113+ {
114+ use dodrio::builder::*;
115+
116+ let elapsed = web_sys::window()
117+ .unwrap_throw()
118+ .performance()
119+ .unwrap_throw()
120+ .now();
121+ let elapsed = elapsed / 1600.0;
122+
123+ main(bump)
124+ .attr("style", self.moire_style(bump, elapsed))
125+ .children([
126+ // The `<input>` that lets users control how many circles to
127+ // render.
128+ input(bump)
129+ .attr("id", "circle-count")
130+ .attr("type", "range")
131+ .attr("min", "30")
132+ .attr("max", "500")
133+ .attr("value", {
134+ let count = bumpalo::format!(in bump, "{}", self.count);
135+ count.into_bump_str()
136+ })
137+ .on("change", |root, _vdom, event| {
138+ root.unwrap_mut::<Moire>().on_change(event);
139+
140+ // Note: there is no need to manually schedule a
141+ // re-render here, since we already started a
142+ // `requestAnimationFrame` loop that automatically
143+ // re-renders on every frame in the `run` function.
144+ })
145+ .finish(),
146+ // And the actual concentric circles that form the Moire patterns.
147+ self.orbiting_objects(bump, elapsed),
148+ self.lemniscate_objects(bump, elapsed),
149+ ])
150+ .finish()
151+ }
152+}
153+
154+/// Rendering helper methods.
155+impl Moire {
156+ /// Generate the main Moire element's inline CSS styles.
157+ fn moire_style<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> &'bump str
158+ where
159+ 'a: 'bump,
160+ {
161+ let elapsed = elapsed / 3.0;
162+ let color = colors::get_interpolated_color(|(_, bg)| bg, elapsed % 1.0);
163+ let style = bumpalo::format!(
164+ in bump,
165+ "background-color: rgb({}, {}, {})",
166+ color.r,
167+ color.g,
168+ color.b
169+ );
170+ style.into_bump_str()
171+ }
172+
173+ /// Generate the orbiting circle objects.
174+ fn orbiting_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
175+ where
176+ 'a: 'bump,
177+ {
178+ const D: f64 = 200.0;
179+ let x = elapsed.sin() * D;
180+ let y = elapsed.cos() * D;
181+ self.moving_object(bump, elapsed, x, y)
182+ }
183+
184+ /// Generate the lemniscate circle objects that interact with the orbiting
185+ /// circle objects to form Moire patterns.
186+ fn lemniscate_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
187+ where
188+ 'a: 'bump,
189+ {
190+ const A: f64 = 150.0;
191+ let x = elapsed.sin() * A;
192+ let y = elapsed.sin() * elapsed.cos() * A;
193+ self.moving_object(bump, elapsed, x, y)
194+ }
195+
196+ fn moving_object<'a, 'bump>(
197+ &'a self,
198+ bump: &'bump Bump,
199+ elapsed: f64,
200+ x: f64,
201+ y: f64,
202+ ) -> Node<'bump>
203+ where
204+ 'a: 'bump,
205+ {
206+ use dodrio::builder::*;
207+ div(bump)
208+ .attr("class", "object")
209+ .attr("style", self.moving_object_style(bump, x, y))
210+ .children([self.circle(bump, elapsed, self.count)])
211+ .finish()
212+ }
213+
214+ /// Generate inline CSS styles for a moving object.
215+ fn moving_object_style<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64) -> &'bump str
216+ where
217+ 'a: 'bump,
218+ {
219+ bumpalo::format!(in bump, "left: {}px; top: {}px;", x, y).into_bump_str()
220+ }
221+
222+ /// Recursively generate `self.count` concentric circles.
223+ fn circle<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64, n: u32) -> Node<'bump>
224+ where
225+ 'a: 'bump,
226+ {
227+ use dodrio::builder::*;
228+
229+ let r = n * 16;
230+
231+ let mut circle = div(bump)
232+ .attr("class", "circle")
233+ .attr("data-radius", {
234+ let r = bumpalo::format!(in bump, "{}", r);
235+ r.into_bump_str()
236+ })
237+ .attr("style", self.circle_style(bump, elapsed, n, r));
238+
239+ if n > 0 {
240+ circle = circle.child(self.circle(bump, elapsed, n - 1));
241+ }
242+
243+ circle.finish()
244+ }
245+
246+ /// Generate inline CSS styles for a circle.
247+ fn circle_style<'a, 'bump>(
248+ &'a self,
249+ bump: &'bump Bump,
250+ elapsed: f64,
251+ n: u32,
252+ r: u32,
253+ ) -> &'bump str
254+ where
255+ 'a: 'bump,
256+ {
257+ let (border, alpha) = self.circle_color(elapsed, n);
258+ let margin = r / 2 + 3;
259+ let style = bumpalo::format!(
260+ in bump,
261+ "border-color: rgba({}, {}, {}, {});\
262+ margin-left: -{}px;\
263+ margin-top: -{}px;\
264+ width: {}px;\
265+ height: {}px;",
266+ border.r,
267+ border.g,
268+ border.b,
269+ alpha,
270+ margin,
271+ margin,
272+ r,
273+ r
274+ );
275+ style.into_bump_str()
276+ }
277+
278+ /// Compute the color for a circle.
279+ fn circle_color(&self, elapsed: f64, n: u32) -> (colors::Rgb, f64) {
280+ let elapsed = elapsed / 3.0;
281+ let base = colors::get_interpolated_color(|(fg, _)| fg, elapsed % 1.0);
282+ let lightness = 1.0 - (n as f64) / (self.count as f64);
283+ (base, lightness)
284+ }
285+}