mirror of
https://github.com/enso-org/enso.git
synced 2024-12-01 01:53:17 +03:00
HTMLRenderer Benchmarks (https://github.com/enso-org/ide/pull/58)
* Custom benchmark solution
* Optimized HTMLRenderer
Original commit: 8a147bf9d4
This commit is contained in:
parent
b80e3f29e4
commit
d6df7dd156
@ -57,3 +57,4 @@ features = [
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.3"
|
||||
web-test = { version = "0.1.0" , path = "../web-test" }
|
||||
|
21
gui/lib/core/js/html_renderer.js
Normal file
21
gui/lib/core/js/html_renderer.js
Normal file
@ -0,0 +1,21 @@
|
||||
function arr_to_css_matrix3d(a) {
|
||||
return "matrix3d(" + a.join(',') + ")"
|
||||
}
|
||||
|
||||
export function set_object_transform(dom, matrix_array) {
|
||||
let css = arr_to_css_matrix3d(matrix_array);
|
||||
dom.style.transform = "translate(-50%, -50%)" + css;
|
||||
}
|
||||
|
||||
export function setup_perspective(dom, znear) {
|
||||
dom.style.perspective = znear + "px";
|
||||
}
|
||||
|
||||
export function setup_camera_transform
|
||||
(dom, znear, half_width, half_height, matrix_array) {
|
||||
let translateZ = "translateZ(" + znear + "px)";
|
||||
let matrix3d = arr_to_css_matrix3d(matrix_array);
|
||||
let translate2d = "translate(" + half_width + "px, " + half_height + "px)";
|
||||
let transform = translateZ + matrix3d + translate2d;
|
||||
dom.style.transform = transform;
|
||||
}
|
@ -115,6 +115,14 @@ impl<'a, T> IntoIterator for &'a OptVec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a mut OptVec<T> {
|
||||
type Item = &'a mut T;
|
||||
type IntoIter = IterMut<'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -169,4 +177,27 @@ mod tests {
|
||||
assert_eq!(i + 1, *value);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter_mut() {
|
||||
let mut v = OptVec::new();
|
||||
|
||||
let ix1 = v.insert(0);
|
||||
let _ix2 = v.insert(1);
|
||||
let _ix3 = v.insert(2);
|
||||
|
||||
assert_eq!(v.len(), 3, "OptVec should have 3 items");
|
||||
|
||||
v.remove(ix1);
|
||||
|
||||
assert_eq!(v.len(), 2, "OptVec should have 2 items");
|
||||
|
||||
for value in &mut v {
|
||||
*value *= 2;
|
||||
}
|
||||
|
||||
for (i, value) in v.into_iter().enumerate() {
|
||||
assert_eq!((i + 1) * 2, *value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub mod mesh_registry;
|
||||
pub mod rendering;
|
||||
pub mod symbol;
|
||||
pub mod workspace;
|
||||
pub mod world;
|
||||
pub mod rendering;
|
@ -1,6 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Object;
|
||||
use crate::display::rendering::Object;
|
||||
|
||||
use nalgebra::base::Matrix4;
|
||||
use nalgebra::geometry::Perspective3;
|
||||
@ -22,13 +22,15 @@ pub struct Camera {
|
||||
|
||||
impl Camera {
|
||||
/// Creates a Camera with perspective projection.
|
||||
pub fn perspective(fov: f32, aspect: f32, z_near: f32, z_far: f32) -> Self {
|
||||
pub fn perspective(fov:f32, aspect:f32, z_near:f32, z_far:f32) -> Self {
|
||||
let fov = fov / 180.0 * PI;
|
||||
let projection = Perspective3::new(aspect, fov, z_near, z_far);
|
||||
let projection = *projection.as_matrix();
|
||||
let object = default();
|
||||
Self { object, projection }
|
||||
}
|
||||
|
||||
pub fn get_y_scale(&self) -> f32 { self.projection.m11 }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
99
gui/lib/core/src/display/rendering/dom_container.rs
Normal file
99
gui/lib/core/src/display/rendering/dom_container.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::system::web::get_element_by_id;
|
||||
use crate::system::web::dyn_into;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::StyleSetter;
|
||||
use crate::system::web::resize_observer::ResizeObserver;
|
||||
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use web_sys::HtmlElement;
|
||||
use nalgebra::Vector2;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
// ======================
|
||||
// === ResizeCallback ===
|
||||
// ======================
|
||||
|
||||
type ResizeCallback = Box<dyn Fn(&Vector2<f32>)>;
|
||||
pub trait ResizeCallbackFn = where Self: Fn(&Vector2<f32>) + 'static;
|
||||
|
||||
// ========================
|
||||
// === DOMContainerData ===
|
||||
// ========================
|
||||
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct DOMContainerData {
|
||||
dimensions : Vector2<f32>,
|
||||
#[derivative(Debug="ignore")]
|
||||
resize_callbacks : Vec<ResizeCallback>
|
||||
}
|
||||
|
||||
impl DOMContainerData {
|
||||
pub fn new(dimensions : Vector2<f32>) -> Self {
|
||||
let resize_callbacks = default();
|
||||
Self { dimensions, resize_callbacks }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ====================
|
||||
// === DOMContainer ===
|
||||
// ====================
|
||||
|
||||
/// A collection for holding 3D `Object`s.
|
||||
#[derive(Debug)]
|
||||
pub struct DOMContainer {
|
||||
pub dom : HtmlElement,
|
||||
resize_observer : Option<ResizeObserver>,
|
||||
data : Rc<RefCell<DOMContainerData>>,
|
||||
}
|
||||
|
||||
impl DOMContainer {
|
||||
pub fn new(dom_id:&str) -> Result<Self> {
|
||||
let dom : HtmlElement = dyn_into(get_element_by_id(dom_id)?)?;
|
||||
|
||||
let width = dom.client_width() as f32;
|
||||
let height = dom.client_height() as f32;
|
||||
let dimensions = Vector2::new(width, height);
|
||||
let data = Rc::new(RefCell::new(DOMContainerData::new(dimensions)));
|
||||
let resize_observer = None;
|
||||
let mut ret = Self { dom, resize_observer, data };
|
||||
|
||||
ret.init_listeners();
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn init_listeners(&mut self) {
|
||||
let data = self.data.clone();
|
||||
let resize_closure = Closure::new(move |width, height| {
|
||||
let mut data = data.borrow_mut();
|
||||
data.dimensions = Vector2::new(width as f32, height as f32);
|
||||
for callback in &data.resize_callbacks {
|
||||
callback(&data.dimensions);
|
||||
}
|
||||
});
|
||||
self.resize_observer = Some(ResizeObserver::new(&self.dom, resize_closure));
|
||||
}
|
||||
|
||||
/// Sets the Scene DOM's dimensions.
|
||||
pub fn set_dimensions(&mut self, dimensions:Vector2<f32>) {
|
||||
self.dom.set_property_or_panic("width" , format!("{}px", dimensions.x));
|
||||
self.dom.set_property_or_panic("height", format!("{}px", dimensions.y));
|
||||
self.data.borrow_mut().dimensions = dimensions;
|
||||
}
|
||||
|
||||
/// Gets the Scene DOM's dimensions.
|
||||
pub fn dimensions(&self) -> Vector2<f32> {
|
||||
self.data.borrow().dimensions
|
||||
}
|
||||
|
||||
/// Adds a ResizeCallback.
|
||||
pub fn add_resize_callback<T>(&mut self, callback:T)
|
||||
where T : ResizeCallbackFn {
|
||||
self.data.borrow_mut().resize_callbacks.push(Box::new(callback));
|
||||
}
|
||||
}
|
42
gui/lib/core/src/display/rendering/graphics_renderer.rs
Normal file
42
gui/lib/core/src/display/rendering/graphics_renderer.rs
Normal file
@ -0,0 +1,42 @@
|
||||
// use super::Camera;
|
||||
use crate::prelude::*;
|
||||
use super::DOMContainer;
|
||||
use super::ResizeCallbackFn;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::StyleSetter;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
|
||||
// ========================
|
||||
// === GraphicsRenderer ===
|
||||
// ========================
|
||||
|
||||
/// Base structure for our Renderers.
|
||||
#[derive(Debug)]
|
||||
pub struct GraphicsRenderer {
|
||||
pub container : DOMContainer
|
||||
}
|
||||
|
||||
impl GraphicsRenderer {
|
||||
pub fn new(dom_id: &str) -> Result<Self> {
|
||||
let container = DOMContainer::new(dom_id)?;
|
||||
container.dom.set_property_or_panic("overflow", "hidden");
|
||||
Ok(Self { container })
|
||||
}
|
||||
|
||||
/// Sets the Scene Renderer's dimensions.
|
||||
pub fn set_dimensions(&mut self, dimensions : Vector2<f32>) {
|
||||
self.container.set_dimensions(dimensions);
|
||||
}
|
||||
|
||||
/// Gets the Scene Renderer's dimensions.
|
||||
pub fn dimensions(&self) -> Vector2<f32> {
|
||||
self.container.dimensions()
|
||||
}
|
||||
|
||||
/// Adds a ResizeCallback.
|
||||
pub fn add_resize_callback<T>(&mut self, callback : T)
|
||||
where T : ResizeCallbackFn {
|
||||
self.container.add_resize_callback(callback);
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Object;
|
||||
|
||||
use crate::display::rendering::Object;
|
||||
use crate::system::web::create_element;
|
||||
use crate::system::web::dyn_into;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::Error;
|
||||
use crate::system::web::StyleSetter;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
@ -20,26 +20,26 @@ use web_sys::HtmlElement;
|
||||
pub struct HTMLObject {
|
||||
#[shrinkwrap(main_field)]
|
||||
pub object : Object,
|
||||
pub element : HtmlElement,
|
||||
pub dimensions : Vector2<f32>,
|
||||
pub dom : HtmlElement,
|
||||
dimensions : Vector2<f32>,
|
||||
}
|
||||
|
||||
impl HTMLObject {
|
||||
/// Creates a HTMLObject from element name.
|
||||
pub fn new(dom_name: &str) -> Result<Self> {
|
||||
let element = dyn_into(create_element(dom_name)?)?;
|
||||
Ok(Self::from_element(element))
|
||||
let dom = dyn_into(create_element(dom_name)?)?;
|
||||
Ok(Self::from_element(dom))
|
||||
}
|
||||
|
||||
/// Creates a HTMLObject from a web_sys::HtmlElement.
|
||||
pub fn from_element(element: HtmlElement) -> Self {
|
||||
element.set_property_or_panic("transform-style", "preserve-3d");
|
||||
element.set_property_or_panic("position" , "absolute");
|
||||
element.set_property_or_panic("width" , "0px");
|
||||
element.set_property_or_panic("height" , "0px");
|
||||
let object = default();
|
||||
element.set_property_or_panic("position", "absolute");
|
||||
element.set_property_or_panic("width" , "0px");
|
||||
element.set_property_or_panic("height" , "0px");
|
||||
let dom = element;
|
||||
let object = default();
|
||||
let dimensions = Vector2::new(0.0, 0.0);
|
||||
Self { object, element, dimensions }
|
||||
Self { object, dom, dimensions }
|
||||
}
|
||||
|
||||
/// Creates a HTMLObject from a HTML string.
|
||||
@ -56,12 +56,12 @@ impl HTMLObject {
|
||||
/// Sets the underlying HtmlElement dimension.
|
||||
pub fn set_dimensions(&mut self, width: f32, height: f32) {
|
||||
self.dimensions = Vector2::new(width, height);
|
||||
self.element.set_property_or_panic("width", format!("{}px", width));
|
||||
self.element.set_property_or_panic("height", format!("{}px", height));
|
||||
self.dom.set_property_or_panic("width", format!("{}px", width));
|
||||
self.dom.set_property_or_panic("height", format!("{}px", height));
|
||||
}
|
||||
|
||||
/// Gets the underlying HtmlElement dimension.
|
||||
pub fn get_dimensions(&self) -> &Vector2<f32> {
|
||||
pub fn dimensions(&self) -> &Vector2<f32> {
|
||||
&self.dimensions
|
||||
}
|
||||
}
|
184
gui/lib/core/src/display/rendering/html/html_renderer.rs
Normal file
184
gui/lib/core/src/display/rendering/html/html_renderer.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display::rendering::GraphicsRenderer;
|
||||
use crate::display::rendering::Scene;
|
||||
use crate::display::rendering::Camera;
|
||||
use crate::display::rendering::html::HTMLObject;
|
||||
use crate::math::utils::IntoFloat32ArrayView;
|
||||
use crate::math::utils::eps;
|
||||
use crate::math::utils::invert_y;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::create_element;
|
||||
use crate::system::web::dyn_into;
|
||||
use crate::system::web::NodeInserter;
|
||||
use crate::system::web::StyleSetter;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
use nalgebra::Matrix4;
|
||||
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
mod js {
|
||||
use super::*;
|
||||
#[wasm_bindgen(module = "/js/html_renderer.js")]
|
||||
extern "C" {
|
||||
pub fn set_object_transform(dom: &JsValue, matrix_array: &JsValue);
|
||||
pub fn setup_perspective(dom: &JsValue, znear: &JsValue);
|
||||
pub fn setup_camera_transform
|
||||
( dom : &JsValue
|
||||
, znear : &JsValue
|
||||
, half_width : &JsValue
|
||||
, half_height : &JsValue
|
||||
, matrix_array : &JsValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_object_transform(dom: &JsValue, matrix: &Matrix4<f32>) {
|
||||
// Views to WASM memory are only valid as long the backing buffer isn't
|
||||
// resized. Check documentation of IntoFloat32ArrayView trait for more
|
||||
// details.
|
||||
unsafe {
|
||||
let matrix_array = matrix.into_float32_array_view();
|
||||
js::set_object_transform(&dom, &matrix_array);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_camera_transform
|
||||
( dom : &JsValue
|
||||
, near : f32
|
||||
, half_width : f32
|
||||
, half_height : f32
|
||||
, matrix : &Matrix4<f32>
|
||||
) {
|
||||
|
||||
// Views to WASM memory are only valid as long the backing buffer isn't
|
||||
// resized. Check documentation of IntoFloat32ArrayView trait for more
|
||||
// details.
|
||||
unsafe {
|
||||
let matrix_array = matrix.into_float32_array_view();
|
||||
js::setup_camera_transform(
|
||||
&dom,
|
||||
&near.into(),
|
||||
&half_width.into(),
|
||||
&half_height.into(),
|
||||
&matrix_array
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ========================
|
||||
// === HTMLRendererData ===
|
||||
// ========================
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HTMLRendererData {
|
||||
pub div : HtmlElement,
|
||||
pub camera : HtmlElement
|
||||
}
|
||||
|
||||
impl HTMLRendererData {
|
||||
pub fn new(div : HtmlElement, camera : HtmlElement) -> Self {
|
||||
Self { div, camera }
|
||||
}
|
||||
|
||||
pub fn set_dimensions(&self, dimensions : Vector2<f32>) {
|
||||
let width = format!("{}px", dimensions.x);
|
||||
let height = format!("{}px", dimensions.y);
|
||||
self.div .set_property_or_panic("width" , &width);
|
||||
self.div .set_property_or_panic("height", &height);
|
||||
self.camera.set_property_or_panic("width" , &width);
|
||||
self.camera.set_property_or_panic("height", &height);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
// === HTMLRenderer ===
|
||||
// ====================
|
||||
|
||||
/// A renderer for `HTMLObject`s.
|
||||
#[derive(Shrinkwrap, Debug)]
|
||||
pub struct HTMLRenderer {
|
||||
#[shrinkwrap(main_field)]
|
||||
pub renderer : GraphicsRenderer,
|
||||
pub data : Rc<HTMLRendererData>
|
||||
}
|
||||
|
||||
impl HTMLRenderer {
|
||||
/// Creates a HTMLRenderer.
|
||||
pub fn new(dom_id: &str) -> Result<Self> {
|
||||
let renderer = GraphicsRenderer::new(dom_id)?;
|
||||
let div : HtmlElement = dyn_into(create_element("div")?)?;
|
||||
let camera : HtmlElement = dyn_into(create_element("div")?)?;
|
||||
|
||||
div .set_property_or_panic("width" , "100%");
|
||||
div .set_property_or_panic("height" , "100%");
|
||||
camera.set_property_or_panic("width" , "100%");
|
||||
camera.set_property_or_panic("height" , "100%");
|
||||
camera.set_property_or_panic("transform-style", "preserve-3d");
|
||||
|
||||
renderer.container.dom.append_or_panic(&div);
|
||||
div .append_or_panic(&camera);
|
||||
|
||||
let data = Rc::new(HTMLRendererData::new(div, camera));
|
||||
let mut htmlrenderer = Self { renderer, data };
|
||||
|
||||
htmlrenderer.init_listeners();
|
||||
Ok(htmlrenderer)
|
||||
}
|
||||
|
||||
fn init_listeners(&mut self) {
|
||||
let dimensions = self.renderer.dimensions();
|
||||
let data = self.data.clone();
|
||||
self.renderer.add_resize_callback(move |dimensions:&Vector2<f32>| {
|
||||
data.set_dimensions(*dimensions);
|
||||
});
|
||||
self.set_dimensions(dimensions);
|
||||
}
|
||||
|
||||
/// Renders the `Scene` from `Camera`'s point of view.
|
||||
pub fn render(&self, camera: &mut Camera, scene: &Scene<HTMLObject>) {
|
||||
let trans_cam = camera.transform.to_homogeneous().try_inverse();
|
||||
let trans_cam = trans_cam.expect("Camera's matrix is not invertible.");
|
||||
let trans_cam = trans_cam.map(eps);
|
||||
let trans_cam = invert_y(trans_cam);
|
||||
|
||||
// Note [znear from projection matrix]
|
||||
let half_dim = self.renderer.container.dimensions() / 2.0;
|
||||
let y_scale = camera.get_y_scale();
|
||||
let near = y_scale * half_dim.y;
|
||||
|
||||
js::setup_perspective(&self.data.div, &near.into());
|
||||
setup_camera_transform(
|
||||
&self.data.camera,
|
||||
near,
|
||||
half_dim.x,
|
||||
half_dim.y,
|
||||
&trans_cam
|
||||
);
|
||||
|
||||
let scene : &Scene<HTMLObject> = &scene;
|
||||
for object in &mut scene.into_iter() {
|
||||
let mut transform = object.transform.to_homogeneous();
|
||||
transform.iter_mut().for_each(|a| *a = eps(*a));
|
||||
|
||||
let parent_node = object.dom.parent_node();
|
||||
if !self.data.camera.is_same_node(parent_node.as_ref()) {
|
||||
self.data.camera.append_or_panic(&object.dom);
|
||||
}
|
||||
|
||||
set_object_transform(&object.dom, &transform);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dimensions(&mut self, dimensions : Vector2<f32>) {
|
||||
self.renderer.set_dimensions(dimensions);
|
||||
self.data.set_dimensions(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
// Note [znear from projection matrix]
|
||||
// ===================================
|
||||
// https://github.com/mrdoob/three.js/blob/22ed6755399fa180ede84bf18ff6cea0ad66f6c0/examples/js/renderers/CSS3DRenderer.js#L275
|
5
gui/lib/core/src/display/rendering/html/mod.rs
Normal file
5
gui/lib/core/src/display/rendering/html/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod html_object;
|
||||
mod html_renderer;
|
||||
|
||||
pub use html_object::*;
|
||||
pub use html_renderer::*;
|
@ -1,53 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Camera;
|
||||
use super::HTMLScene;
|
||||
use crate::math::utils::IntoCSSMatrix;
|
||||
use crate::math::utils::eps;
|
||||
use crate::math::utils::invert_y;
|
||||
|
||||
use crate::system::web::StyleSetter;
|
||||
|
||||
// ====================
|
||||
// === HTMLRenderer ===
|
||||
// ====================
|
||||
|
||||
/// A renderer for `HTMLObject`s.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct HTMLRenderer {}
|
||||
|
||||
impl HTMLRenderer {
|
||||
/// Creates a HTMLRenderer.
|
||||
pub fn new() -> Self { default() }
|
||||
|
||||
/// Renders the `Scene` from `Camera`'s point of view.
|
||||
pub fn render(&self, camera: &mut Camera, scene: &HTMLScene) {
|
||||
// Note [znear from projection matrix]
|
||||
let half_dim = scene.get_dimensions() / 2.0;
|
||||
let expr = camera.projection[(1, 1)];
|
||||
let near = format!("{}px", expr * half_dim.y);
|
||||
let trans_cam = camera.transform.to_homogeneous().try_inverse();
|
||||
let trans_cam = trans_cam.expect("Camera's matrix is not invertible.");
|
||||
let trans_cam = trans_cam.map(eps);
|
||||
let trans_cam = invert_y(trans_cam);
|
||||
let trans_z = format!("translateZ({})", near);
|
||||
let matrix3d = trans_cam.into_css_matrix();
|
||||
let trans = format!("translate({}px,{}px)", half_dim.x, half_dim.y);
|
||||
let css = format!("{} {} {}", trans_z, matrix3d, trans);
|
||||
|
||||
scene.div .element.set_property_or_panic("perspective", near);
|
||||
scene.camera.element.set_property_or_panic("transform" , css);
|
||||
|
||||
for object in &scene.objects {
|
||||
let mut transform = object.transform.to_homogeneous();
|
||||
transform.iter_mut().for_each(|a| *a = eps(*a));
|
||||
let matrix3d = transform.into_css_matrix();
|
||||
let css = format!("translate(-50%, -50%) {}", matrix3d);
|
||||
object.element.set_property_or_panic("transform", css);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note [znear from projection matrix]
|
||||
// =================================
|
||||
// https://github.com/mrdoob/three.js/blob/22ed6755399fa180ede84bf18ff6cea0ad66f6c0/examples/js/renderers/CSS3DRenderer.js#L275
|
@ -1,76 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::HTMLObject;
|
||||
use super::Scene;
|
||||
use crate::data::opt_vec::OptVec;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::StyleSetter;
|
||||
use crate::system::web::NodeAppender;
|
||||
use crate::system::web::NodeRemover;
|
||||
|
||||
|
||||
// =================
|
||||
// === HTMLScene ===
|
||||
// =================
|
||||
|
||||
/// A collection for holding 3D `HTMLObject`s.
|
||||
#[derive(Shrinkwrap, Debug)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct HTMLScene {
|
||||
#[shrinkwrap(main_field)]
|
||||
pub scene : Scene,
|
||||
pub div : HTMLObject,
|
||||
pub camera : HTMLObject,
|
||||
pub objects : OptVec<HTMLObject>,
|
||||
}
|
||||
|
||||
pub type Index = usize;
|
||||
|
||||
impl HTMLScene {
|
||||
/// Searches for a HtmlElement identified by id and appends to it.
|
||||
pub fn new(dom_id: &str) -> Result<Self> {
|
||||
let scene = Scene::new(dom_id)?;
|
||||
let view_dim = scene.get_dimensions();
|
||||
let width = format!("{}px", view_dim.x);
|
||||
let height = format!("{}px", view_dim.y);
|
||||
let div = HTMLObject::new("div")?;
|
||||
let camera = HTMLObject::new("div")?;
|
||||
let objects = default();
|
||||
|
||||
scene.container.set_property_or_panic("overflow", "hidden");
|
||||
scene.container.append_child_or_panic(&div.element);
|
||||
div.element.append_child_or_panic(&camera.element);
|
||||
div .element.set_property_or_panic("width" , &width);
|
||||
div .element.set_property_or_panic("height" , &height);
|
||||
camera.element.set_property_or_panic("width" , &width);
|
||||
camera.element.set_property_or_panic("height" , &height);
|
||||
|
||||
Ok(Self { scene, div, camera, objects })
|
||||
}
|
||||
|
||||
/// Moves a HTMLObject to the Scene and returns an index to it.
|
||||
pub fn add(&mut self, object: HTMLObject) -> Index {
|
||||
self.camera.element.append_child_or_panic(&object.element);
|
||||
self.objects.insert(object)
|
||||
}
|
||||
|
||||
/// Removes and retrieves a HTMLObject based on the index provided by
|
||||
pub fn remove(&mut self, index: usize) -> Option<HTMLObject> {
|
||||
let result = self.objects.remove(index);
|
||||
result.iter().for_each(|object| {
|
||||
self.camera.element.remove_child_or_panic(&object.element);
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns the number of `Object`s in the Scene,
|
||||
/// also referred to as its 'length'.
|
||||
pub fn len(&self) -> usize {
|
||||
self.objects.len()
|
||||
}
|
||||
|
||||
/// Returns true if the Scene contains no `Object`s.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.objects.is_empty()
|
||||
}
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
mod camera;
|
||||
mod object;
|
||||
mod renderer;
|
||||
mod scene;
|
||||
|
||||
pub use object::*;
|
||||
pub use scene::*;
|
||||
|
||||
mod camera;
|
||||
mod transform;
|
||||
mod graphics_renderer;
|
||||
mod dom_container;
|
||||
|
||||
mod htmlobject;
|
||||
mod htmlrenderer;
|
||||
mod htmlscene;
|
||||
pub use camera::*;
|
||||
pub use transform::*;
|
||||
pub use graphics_renderer::*;
|
||||
pub use dom_container::*;
|
||||
|
||||
pub use camera::Camera;
|
||||
pub use object::Object;
|
||||
pub use renderer::Renderer;
|
||||
pub use scene::Scene;
|
||||
pub use transform::Transform;
|
||||
|
||||
pub use htmlobject::HTMLObject;
|
||||
pub use htmlrenderer::HTMLRenderer;
|
||||
pub use htmlscene::HTMLScene;
|
||||
pub mod html;
|
||||
|
@ -1,15 +1,18 @@
|
||||
use super::Transform;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display::rendering::Transform;
|
||||
|
||||
use nalgebra::UnitQuaternion;
|
||||
use nalgebra::Vector3;
|
||||
|
||||
// ==============
|
||||
// === Object ===
|
||||
// ==============
|
||||
|
||||
// FIXME: You should derive Debug on every structure whenever its possible.
|
||||
/// Base structure for representing a 3D object in a `Scene`.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Object {
|
||||
pub transform : Transform,
|
||||
pub transform : Transform
|
||||
}
|
||||
|
||||
impl Object {
|
||||
@ -17,19 +20,34 @@ impl Object {
|
||||
pub fn new() -> Object { default() }
|
||||
|
||||
/// Sets the object's position.
|
||||
pub fn set_position(&mut self, x: f32, y: f32, z: f32) {
|
||||
pub fn set_position(&mut self, x:f32, y:f32, z:f32) {
|
||||
self.transform.set_translation(x, y, z)
|
||||
}
|
||||
|
||||
/// Gets the object's position.
|
||||
pub fn position(&self) -> &Vector3<f32> {
|
||||
self.transform.translation()
|
||||
}
|
||||
|
||||
/// Sets the object's rotation in YXZ (yaw -> roll -> pitch) order.
|
||||
pub fn set_rotation(&mut self, roll: f32, pitch: f32, yaw: f32) {
|
||||
pub fn set_rotation(&mut self, roll:f32, pitch:f32, yaw:f32) {
|
||||
self.transform.set_rotation(roll, pitch, yaw)
|
||||
}
|
||||
|
||||
/// Gets the object's rotation UnitQuaternion.
|
||||
pub fn rotation(&self) -> &UnitQuaternion<f32> {
|
||||
self.transform.rotation()
|
||||
}
|
||||
|
||||
/// Sets the object's scale.
|
||||
pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
|
||||
self.transform.set_scale(x, y, z);
|
||||
}
|
||||
|
||||
/// Gets the object's scale.
|
||||
pub fn scale(&self) -> &Vector3<f32> {
|
||||
self.transform.scale()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -46,14 +64,14 @@ mod test {
|
||||
object.set_scale(3.0, 2.0, 1.0);
|
||||
object.set_rotation(PI * 2.0, PI, PI / 2.0);
|
||||
|
||||
assert_eq!(object.transform.translation, Vector3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(object.transform.scale, Vector3::new(3.0, 2.0, 1.0));
|
||||
assert_eq!(*object.position(), Vector3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(*object.scale(), Vector3::new(3.0, 2.0, 1.0));
|
||||
|
||||
let expected = Quaternion::new
|
||||
( 0.00000009272586
|
||||
, -0.7071068
|
||||
, -0.7071068
|
||||
, -0.000000030908623 );
|
||||
assert_eq!(*object.transform.rotation.quaternion(), expected);
|
||||
assert_eq!(*object.rotation().quaternion(), expected);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
// use super::Camera;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Base structure for our Renderers.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Renderer {}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new() -> Self { default() }
|
||||
}
|
@ -1,24 +1,57 @@
|
||||
use crate::system::web::{get_element_by_id, dyn_into, Result};
|
||||
use web_sys::HtmlElement;
|
||||
use nalgebra::Vector2;
|
||||
use crate::prelude::*;
|
||||
use crate::data::opt_vec::*;
|
||||
|
||||
type Index = usize;
|
||||
|
||||
// =============
|
||||
// === Scene ===
|
||||
// =============
|
||||
|
||||
/// A collection for holding 3D `Object`s.
|
||||
#[derive(Debug)]
|
||||
pub struct Scene {
|
||||
pub container : HtmlElement,
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Default(bound = ""))]
|
||||
pub struct Scene<T> {
|
||||
objects : OptVec<T>
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
impl<T> Scene<T> {
|
||||
/// Searches for a HtmlElement identified by id and appends to it.
|
||||
pub fn new(dom_id: &str) -> Result<Self> {
|
||||
let container = dyn_into(get_element_by_id(dom_id)?)?;
|
||||
Ok(Self { container })
|
||||
pub fn new() -> Self { default() }
|
||||
|
||||
/// Moves a HTMLObject to the Scene and returns an index to it.
|
||||
pub fn add(&mut self, object: T) -> Index {
|
||||
self.objects.insert(object)
|
||||
}
|
||||
|
||||
/// Gets the HtmlElement container's dimensions.
|
||||
pub fn get_dimensions(&self) -> Vector2<f32> {
|
||||
let width = self.container.client_width() as f32;
|
||||
let height = self.container.client_height() as f32;
|
||||
Vector2::new(width, height)
|
||||
/// Removes and retrieves a HTMLObject based on the index provided by
|
||||
pub fn remove(&mut self, index: usize) -> Option<T> {
|
||||
self.objects.remove(index)
|
||||
}
|
||||
|
||||
/// Returns the number of `Object`s in the Scene,
|
||||
/// also referred to as its 'length'.
|
||||
pub fn len(&self) -> usize {
|
||||
self.objects.len()
|
||||
}
|
||||
|
||||
/// Returns true if the Scene contains no `Object`s.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.objects.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a Scene<T> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = Iter<'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.objects.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a mut Scene<T> {
|
||||
type Item = &'a mut T;
|
||||
type IntoIter = IterMut<'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
(&mut self.objects).into_iter()
|
||||
}
|
||||
}
|
||||
|
@ -48,20 +48,35 @@ impl Transform {
|
||||
pub fn identity() -> Self { default() }
|
||||
|
||||
/// Sets Transform's translation.
|
||||
pub fn set_translation(&mut self, x: f32, y: f32, z: f32) {
|
||||
pub fn set_translation(&mut self, x:f32, y:f32, z:f32) {
|
||||
self.translation = Vector3::new(x, y, z);
|
||||
}
|
||||
|
||||
/// Set Transform's scale.
|
||||
pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
|
||||
/// Gets Transform's translation.
|
||||
pub fn translation(&self) -> &Vector3<f32> {
|
||||
&self.translation
|
||||
}
|
||||
|
||||
/// Sets Transform's scale.
|
||||
pub fn set_scale(&mut self, x:f32, y:f32, z:f32) {
|
||||
self.scale = Vector3::new(x, y, z);
|
||||
}
|
||||
|
||||
/// Set Transform's rotation from Euler angles in radians.
|
||||
pub fn set_rotation(&mut self, roll: f32, pitch: f32, yaw: f32) {
|
||||
/// Gets Transform's scale.
|
||||
pub fn scale(&self) -> &Vector3<f32> {
|
||||
&self.scale
|
||||
}
|
||||
|
||||
/// Sets Transform's rotation from Euler angles in radians.
|
||||
pub fn set_rotation(&mut self, roll:f32, pitch:f32, yaw:f32) {
|
||||
self.rotation = from_euler_angles_pry(roll, pitch, yaw);
|
||||
}
|
||||
|
||||
/// Gets Transform's rotation UnitQuaternion
|
||||
pub fn rotation(&self) -> &UnitQuaternion<f32> {
|
||||
&self.rotation
|
||||
}
|
||||
|
||||
/// Gets a homogeneous transform Matrix4. The rotation order is YXZ (pitch,
|
||||
/// roll, yaw). Based on:
|
||||
// https://github.com/mrdoob/three.js/blob/master/src/math/Matrix4.js#L732
|
||||
@ -119,9 +134,9 @@ mod test {
|
||||
use nalgebra::UnitQuaternion;
|
||||
|
||||
let transform = Transform::identity();
|
||||
assert_eq!(transform.translation, Vector3::new(0.0, 0.0, 0.0));
|
||||
assert_eq!(transform.scale , Vector3::new(1.0, 1.0, 1.0));
|
||||
assert_eq!(transform.rotation , UnitQuaternion::identity());
|
||||
assert_eq!(*transform.translation(), Vector3::new(0.0, 0.0, 0.0));
|
||||
assert_eq!(*transform.scale(), Vector3::new(1.0, 1.0, 1.0));
|
||||
assert_eq!(*transform.rotation(), UnitQuaternion::identity());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -136,14 +151,14 @@ mod test {
|
||||
transform.set_scale(3.0, 2.0, 1.0);
|
||||
transform.set_rotation(PI * 2.0, PI, PI / 2.0);
|
||||
|
||||
assert_eq!(transform.translation, Vector3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(transform.scale, Vector3::new(3.0, 2.0, 1.0));
|
||||
assert_eq!(*transform.translation(), Vector3::new(1.0, 2.0, 3.0));
|
||||
assert_eq!(*transform.scale(), Vector3::new(3.0, 2.0, 1.0));
|
||||
|
||||
let expected = Quaternion::new
|
||||
( 0.00000009272586
|
||||
, -0.7071068
|
||||
, -0.7071068
|
||||
, -0.000000030908623 );
|
||||
assert_eq!(*transform.rotation.quaternion(), expected);
|
||||
assert_eq!(*transform.rotation().quaternion(), expected);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use nalgebra::Matrix4;
|
||||
use nalgebra::RealField;
|
||||
use js_sys::Float32Array;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// ======================
|
||||
// === Matrix Printer ===
|
||||
// ======================
|
||||
|
||||
// =====================
|
||||
// === IntoCSSMatrix ===
|
||||
// =====================
|
||||
|
||||
pub trait IntoCSSMatrix {
|
||||
fn into_css_matrix(&self) -> String;
|
||||
@ -12,18 +17,51 @@ pub trait IntoCSSMatrix {
|
||||
impl<T : RealField> IntoCSSMatrix for Matrix4<T> {
|
||||
fn into_css_matrix(&self) -> String {
|
||||
let mut iter = self.iter();
|
||||
let item = iter.next().expect("Matrix4 should have the first item");
|
||||
let acc = format!("{}", item);
|
||||
let ret = iter.fold(acc, |acc, item| format!("{}, {}", acc, item));
|
||||
let item = iter.next().expect("Matrix4 should have the first item");
|
||||
let acc = format!("{}", item);
|
||||
let ret = iter.fold(acc, |acc, item| format!("{}, {}", acc, item));
|
||||
format!("matrix3d({})", ret)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================
|
||||
// === Float32ArrayView ===
|
||||
// ========================
|
||||
|
||||
/// A Float32Array view created from `IntoFloat32ArrayView`.
|
||||
#[derive(Shrinkwrap)]
|
||||
pub struct Float32ArrayView<'a> {
|
||||
#[shrinkwrap(main_field)]
|
||||
array : Float32Array,
|
||||
phantom : PhantomData<&'a Float32Array>
|
||||
}
|
||||
|
||||
pub trait IntoFloat32ArrayView {
|
||||
/// # Safety
|
||||
/// Views into WebAssembly memory are only valid so long as the backing buffer isn't resized in
|
||||
/// JS. Once this function is called any future calls to Box::new (or malloc of any form) may
|
||||
/// cause the returned value here to be invalidated. Use with caution!
|
||||
///
|
||||
/// Additionally the returned object can be safely mutated but the input slice isn't guaranteed
|
||||
/// to be mutable.
|
||||
unsafe fn into_float32_array_view(&self) -> Float32ArrayView<'_>;
|
||||
}
|
||||
|
||||
impl IntoFloat32ArrayView for Matrix4<f32> {
|
||||
unsafe fn into_float32_array_view(&self) -> Float32ArrayView<'_> {
|
||||
let matrix = matrix4_to_array(&self);
|
||||
let array = Float32Array::view(matrix);
|
||||
let phantom = PhantomData;
|
||||
Float32ArrayView { array, phantom }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn into_css_matrix() {
|
||||
use nalgebra::Matrix4;
|
||||
use super::Matrix4;
|
||||
use super::IntoCSSMatrix;
|
||||
|
||||
let matrix = Matrix4::new
|
||||
@ -38,11 +76,36 @@ mod tests {
|
||||
13, 14, 15, 16)";
|
||||
assert_eq!(column_major, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix4_memory_layout() {
|
||||
use super::Matrix4;
|
||||
use super::matrix4_to_array;
|
||||
|
||||
let matrix = Matrix4::<f32>::new
|
||||
( 1.0, 5.0, 9.0, 13.0
|
||||
, 2.0, 6.0, 10.0, 14.0
|
||||
, 3.0, 7.0, 11.0, 15.0
|
||||
, 4.0, 8.0, 12.0, 16.0 );
|
||||
|
||||
let layout = matrix4_to_array(&matrix);
|
||||
for (i, n) in layout.iter().enumerate() {
|
||||
assert_eq!((i + 1) as f32, *n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============
|
||||
// ============
|
||||
// === Misc ===
|
||||
// =============
|
||||
// ============
|
||||
|
||||
pub fn matrix4_to_array(matrix:&Matrix4<f32>) -> &[f32; 16] {
|
||||
// To interpret [[f32; 4]; 4] as [f32; 16].
|
||||
// The memory layout is the same, so this operation is safe.
|
||||
unsafe {
|
||||
&*(matrix.as_ref() as *const [[f32; 4]; 4] as *const [f32; 16])
|
||||
}
|
||||
}
|
||||
|
||||
// eps is used to round very small values to 0.0 for numerical stability
|
||||
pub fn eps(value: f32) -> f32 {
|
||||
@ -55,4 +118,4 @@ pub fn invert_y(mut m: Matrix4<f32>) -> Matrix4<f32> {
|
||||
// Negating the second column to invert Y.
|
||||
m.row_part_mut(1, 4).iter_mut().for_each(|a| *a = -*a);
|
||||
m
|
||||
}
|
||||
}
|
6
gui/lib/core/tests/bench_test.js
Normal file
6
gui/lib/core/tests/bench_test.js
Normal file
@ -0,0 +1,6 @@
|
||||
export function set_gradient_bg(dom, r, g, b) {
|
||||
let components = r + "," + g + "," + b;
|
||||
let css = "radial-gradient(rgb(" + components + ") 50%,"
|
||||
+ "rgba(" + components + ", 0.0))";
|
||||
dom.style.backgroundImage = css;
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
use basegl::display::rendering::HTMLObject;
|
||||
use basegl::system::web::document;
|
||||
use basegl::system::web::dyn_into;
|
||||
use basegl::system::web::create_element;
|
||||
use basegl::system::web::get_element_by_id;
|
||||
use basegl::system::web::AttributeSetter;
|
||||
use basegl::system::web::StyleSetter;
|
||||
use basegl::system::web::NodeAppender;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
// =================
|
||||
// === TestGroup ===
|
||||
// =================
|
||||
|
||||
pub struct TestGroup {
|
||||
pub div : HtmlElement,
|
||||
}
|
||||
|
||||
impl TestGroup {
|
||||
pub fn new() -> Self {
|
||||
let div : HtmlElement = match get_element_by_id("testgroup") {
|
||||
// If id="testgroup" exists, we use it.
|
||||
Ok(div) => dyn_into(div).expect("div should be a HtmlElement"),
|
||||
// If it doesn't exist, we create a new element.
|
||||
Err(_) => {
|
||||
let div = create_element("div")
|
||||
.expect("TestGroup failed to create div");
|
||||
|
||||
let div : HtmlElement = dyn_into(div).expect("HtmlElement");
|
||||
div.set_attribute_or_panic("id", "testgroup");
|
||||
div.set_property_or_panic("display", "flex");
|
||||
div.set_property_or_panic("flex-wrap", "wrap");
|
||||
document()
|
||||
.expect("Document is not present")
|
||||
.body()
|
||||
.expect("Body is not present")
|
||||
.append_child_or_panic(&div);
|
||||
div
|
||||
},
|
||||
};
|
||||
Self { div }
|
||||
}
|
||||
}
|
||||
|
||||
// =====================
|
||||
// === TestContainer ===
|
||||
// =====================
|
||||
|
||||
pub struct TestContainer {
|
||||
div: HTMLObject,
|
||||
}
|
||||
|
||||
impl TestContainer {
|
||||
pub fn new(name: &str, width: f32, height: f32) -> Self {
|
||||
let mut div = HTMLObject::new("div").expect("div");
|
||||
div.set_dimensions(width, height + 16.0);
|
||||
|
||||
div.element.set_property_or_panic("border", "1px solid black");
|
||||
div.element.set_property_or_panic("position", "relative");
|
||||
div.element.set_property_or_panic("margin", "10px");
|
||||
|
||||
let html_string = format!("<center>{}</center>", name);
|
||||
let mut header = HTMLObject::from_html_string(html_string)
|
||||
.expect("TestContainer should have a header");
|
||||
header.set_dimensions(width, 16.0);
|
||||
|
||||
let border_bottom = "1px solid black";
|
||||
header.element.set_property_or_panic("border-bottom", border_bottom);
|
||||
header.element.set_property_or_panic("position", "relative");
|
||||
|
||||
div.element.append_child_or_panic(&header.element);
|
||||
|
||||
let mut container = HTMLObject::new("div")
|
||||
.expect("TestContainer's div not created");
|
||||
|
||||
container.set_dimensions(width, height);
|
||||
container.element.set_attribute_or_panic("id", name);
|
||||
container.element.set_property_or_panic("position", "relative");
|
||||
|
||||
div.element.append_child_or_panic(&container.element);
|
||||
|
||||
TestGroup::new().div.append_child_or_panic(&div.element);
|
||||
Self { div }
|
||||
}
|
||||
|
||||
pub fn append_child(&mut self, element: &HtmlElement) {
|
||||
self.div.element.append_child_or_panic(&element);
|
||||
}
|
||||
}
|
251
gui/lib/core/tests/html_renderer.rs
Normal file
251
gui/lib/core/tests/html_renderer.rs
Normal file
@ -0,0 +1,251 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use web_test::web_configure;
|
||||
web_configure!(run_in_browser);
|
||||
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[wasm_bindgen(module = "/tests/bench_test.js")]
|
||||
extern "C" {
|
||||
fn set_gradient_bg(
|
||||
dom : &JsValue,
|
||||
red : &JsValue,
|
||||
green : &JsValue,
|
||||
blue : &JsValue);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use basegl::display::rendering::Scene;
|
||||
use basegl::display::rendering::Camera;
|
||||
use basegl::display::rendering::html::HTMLObject;
|
||||
use basegl::display::rendering::html::HTMLRenderer;
|
||||
use basegl::system::web::StyleSetter;
|
||||
use basegl::system::web::get_performance;
|
||||
use web_test::*;
|
||||
use web_sys::Performance;
|
||||
|
||||
#[web_test(no_container)]
|
||||
fn invalid_container() {
|
||||
let renderer = HTMLRenderer::new("nonexistent_id");
|
||||
assert!(renderer.is_err(), "nonexistent_id should not exist");
|
||||
}
|
||||
|
||||
#[web_test]
|
||||
fn object_behind_camera() {
|
||||
let mut scene : Scene<HTMLObject> = Scene::new();
|
||||
let renderer = HTMLRenderer::new("object_behind_camera")
|
||||
.expect("Renderer couldn't be created");
|
||||
assert_eq!(scene.len(), 0, "Scene should be empty");
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut object = HTMLObject::new("div").unwrap();
|
||||
object.set_position(0.0, 0.0, 0.0);
|
||||
object.dom.set_property_or_panic("background-color", "black");
|
||||
object.set_dimensions(100.0, 100.0);
|
||||
scene.add(object);
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
// We move the Camera behind the object so we don't see it.
|
||||
camera.set_position(0.0, 0.0, -100.0);
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
|
||||
fn create_scene(renderer : &HTMLRenderer) -> Scene<HTMLObject> {
|
||||
let mut scene : Scene<HTMLObject> = Scene::new();
|
||||
assert_eq!(scene.len(), 0);
|
||||
|
||||
renderer.container.dom.set_property_or_panic("background-color", "black");
|
||||
|
||||
// Iterate over 3 axes.
|
||||
for axis in vec![(1, 0, 0), (0, 1, 0), (0, 0, 1)] {
|
||||
// Creates 10 HTMLObjects per axis.
|
||||
for i in 0 .. 10 {
|
||||
let mut object = HTMLObject::new("div").unwrap();
|
||||
object.set_dimensions(1.0, 1.0);
|
||||
|
||||
// Using axis for masking.
|
||||
// For instance, the axis (0, 1, 0) creates:
|
||||
// (x, y, z) = (0, 0, 0) .. (0, 9, 0)
|
||||
let x = (i * axis.0) as f32;
|
||||
let y = (i * axis.1) as f32;
|
||||
let z = (i * axis.2) as f32;
|
||||
object.set_position(x, y, z);
|
||||
|
||||
// Creates a gradient color based on the axis.
|
||||
let r = (x * 25.5) as u8;
|
||||
let g = (y * 25.5) as u8;
|
||||
let b = (z * 25.5) as u8;
|
||||
let color = format!("rgba({}, {}, {}, {})", r, g, b, 1.0);
|
||||
|
||||
object.dom.set_property_or_panic("background-color", color);
|
||||
scene.add(object);
|
||||
}
|
||||
}
|
||||
assert_eq!(scene.len(), 30, "We should have 30 HTMLObjects");
|
||||
scene
|
||||
}
|
||||
|
||||
#[web_test]
|
||||
fn rhs_coordinates() {
|
||||
let renderer = HTMLRenderer::new("rhs_coordinates")
|
||||
.expect("Renderer couldn't be created");
|
||||
let scene = create_scene(&renderer);
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
|
||||
#[web_test]
|
||||
fn rhs_coordinates_from_back() {
|
||||
use std::f32::consts::PI;
|
||||
|
||||
let renderer = HTMLRenderer::new("rhs_coordinates_from_back")
|
||||
.expect("Renderer couldn't be created");
|
||||
let scene = create_scene(&renderer);
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
|
||||
// We move the Camera -29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, -29.0);
|
||||
// We rotate it 180 degrees so we can see the center of the scene
|
||||
// from behind.
|
||||
camera.set_rotation(0.0, PI, 0.0);
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn camera_movement(b: &mut Bencher) {
|
||||
let renderer = HTMLRenderer::new("camera_movement")
|
||||
.expect("Renderer couldn't be created");
|
||||
let scene = create_scene(&renderer);
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
let performance = get_performance()
|
||||
.expect("Couldn't get performance obj");
|
||||
|
||||
b.iter(move || {
|
||||
let t = (performance.now() / 1000.0) as f32;
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(t.sin() * 5.0, t.cos() * 5.0, 29.0);
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
})
|
||||
}
|
||||
|
||||
fn make_sphere(mut scene : &mut Scene<HTMLObject>, performance : &Performance) {
|
||||
use super::set_gradient_bg;
|
||||
|
||||
let t = (performance.now() / 1000.0) as f32;
|
||||
let length = scene.len() as f32;
|
||||
let mut scene : &mut Scene<HTMLObject> = &mut scene;
|
||||
for (i, object) in (&mut scene).into_iter().enumerate() {
|
||||
let i = i as f32;
|
||||
let d = (i / length - 0.5) * 2.0;
|
||||
|
||||
let mut y = d;
|
||||
let r = (1.0 - y * y).sqrt();
|
||||
let mut x = (y * 100.0 + t).cos() * r;
|
||||
let mut z = (y * 100.0 + t).sin() * r;
|
||||
|
||||
x += (y * 1.25 + t * 2.50).cos() * 0.5;
|
||||
y += (z * 1.25 + t * 2.00).cos() * 0.5;
|
||||
z += (x * 1.25 + t * 3.25).cos() * 0.5;
|
||||
object.set_position(x * 5.0, y * 5.0, z * 5.0);
|
||||
|
||||
let faster_t = t * 100.0;
|
||||
let r = (i + 0.0 + faster_t) as u8 % 255;
|
||||
let g = (i + 85.0 + faster_t) as u8 % 255;
|
||||
let b = (i + 170.0 + faster_t) as u8 % 255;
|
||||
set_gradient_bg(&object.dom, &r.into(), &g.into(), &b.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn object_x1000(b: &mut Bencher) {
|
||||
let mut scene : Scene<HTMLObject> = Scene::new();
|
||||
let renderer = HTMLRenderer::new("object_x1000")
|
||||
.expect("Renderer couldn't be created");
|
||||
renderer.container.dom.set_property_or_panic("background-color", "black");
|
||||
|
||||
for _ in 0..1000 {
|
||||
let mut object = HTMLObject::new("div")
|
||||
.expect("Failed to create object");
|
||||
object.set_dimensions(1.0, 1.0);
|
||||
object.set_scale(0.5, 0.5, 0.5);
|
||||
scene.add(object);
|
||||
}
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
let performance = get_performance()
|
||||
.expect("Couldn't get performance obj");
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
|
||||
make_sphere(&mut scene, &performance);
|
||||
|
||||
b.iter(move || {
|
||||
renderer.render(&mut camera, &scene);
|
||||
})
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn object_x400_update(b: &mut Bencher) {
|
||||
let renderer = HTMLRenderer::new("object_x400_update")
|
||||
.expect("Renderer couldn't be created");
|
||||
let mut scene : Scene<HTMLObject> = Scene::new();
|
||||
renderer.container.dom.set_property_or_panic("background-color", "black");
|
||||
|
||||
for _ in 0..400 {
|
||||
let mut object = HTMLObject::new("div")
|
||||
.expect("Failed to create object");
|
||||
object.set_dimensions(1.0, 1.0);
|
||||
object.set_scale(0.5, 0.5, 0.5);
|
||||
scene.add(object);
|
||||
}
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
let performance = get_performance()
|
||||
.expect("Couldn't get performance obj");
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
|
||||
b.iter(move || {
|
||||
make_sphere(&mut scene, &performance);
|
||||
renderer.render(&mut camera, &scene);
|
||||
})
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
pub mod common;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::TestContainer;
|
||||
use basegl::display::rendering::*;
|
||||
use basegl::system::web::StyleSetter;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn invalid_container() {
|
||||
let scene = HTMLScene::new("nonexistent_id");
|
||||
assert!(scene.is_err(), "nonexistent_id should not exist");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn object_behind_camera() {
|
||||
TestContainer::new("object_behind_camera", 320.0, 240.0);
|
||||
let mut scene = HTMLScene::new("object_behind_camera")
|
||||
.expect("Failed to create HTMLScene");
|
||||
assert_eq!(scene.len(), 0, "Scene should be empty");
|
||||
|
||||
let view_dim = scene.get_dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut object = HTMLObject::new("div").unwrap();
|
||||
object.set_position(0.0, 0.0, 0.0);
|
||||
object.element.set_property_or_panic("background-color", "black");
|
||||
object.set_dimensions(100.0, 100.0);
|
||||
scene.add(object);
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
// We move the Camera behind the object so we don't see it.
|
||||
camera.set_position(0.0, 0.0, -100.0);
|
||||
|
||||
let renderer = HTMLRenderer::new();
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
|
||||
fn create_scene(dom_id : &str) -> HTMLScene {
|
||||
let mut scene = HTMLScene::new(dom_id)
|
||||
.expect("Failed to create HTMLScene");
|
||||
assert_eq!(scene.len(), 0);
|
||||
|
||||
scene.container.set_property_or_panic("background-color", "black");
|
||||
|
||||
// Iterate over 3 axes.
|
||||
for axis in vec![(1, 0, 0), (0, 1, 0), (0, 0, 1)] {
|
||||
// Creates 10 HTMLObjects per axis.
|
||||
for i in 0 .. 10 {
|
||||
let mut object = HTMLObject::new("div").unwrap();
|
||||
object.set_dimensions(1.0, 1.0);
|
||||
|
||||
// Using axis for masking.
|
||||
// For instance, the axis (0, 1, 0) creates:
|
||||
// (x, y, z) = (0, 0, 0) .. (0, 9, 0)
|
||||
let x = (i * axis.0) as f32;
|
||||
let y = (i * axis.1) as f32;
|
||||
let z = (i * axis.2) as f32;
|
||||
object.set_position(x, y, z);
|
||||
|
||||
// Creates a gradient color based on the axis.
|
||||
let r = (x * 25.5) as u8;
|
||||
let g = (y * 25.5) as u8;
|
||||
let b = (z * 25.5) as u8;
|
||||
let color = format!("rgba({}, {}, {}, {})", r, g, b, 1.0);
|
||||
|
||||
object.element.set_property_or_panic("background-color", color);
|
||||
scene.add(object);
|
||||
}
|
||||
}
|
||||
assert_eq!(scene.len(), 30, "We should have 30 HTMLObjects");
|
||||
scene
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn rhs_coordinates() {
|
||||
TestContainer::new("rhs_coordinates", 320.0, 240.0);
|
||||
let scene = create_scene("rhs_coordinates");
|
||||
|
||||
let view_dim = scene.get_dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, 29.0);
|
||||
|
||||
let renderer = HTMLRenderer::new();
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn rhs_coordinates_from_back() {
|
||||
use std::f32::consts::PI;
|
||||
|
||||
TestContainer::new("rhs_coordinates_from_back", 320.0, 240.0);
|
||||
let scene = create_scene("rhs_coordinates_from_back");
|
||||
|
||||
let view_dim = scene.get_dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let aspect_ratio = view_dim.x / view_dim.y;
|
||||
let mut camera = Camera::perspective(45.0, aspect_ratio, 1.0, 1000.0);
|
||||
|
||||
// We move the Camera -29 units away from the center.
|
||||
camera.set_position(0.0, 0.0, -29.0);
|
||||
// We rotate it 180 degrees so we can see the center of the scene
|
||||
// from behind.
|
||||
camera.set_rotation(0.0, PI, 0.0);
|
||||
|
||||
let renderer = HTMLRenderer::new();
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
}
|
@ -32,7 +32,8 @@ features = [
|
||||
'WebGlProgram',
|
||||
'WebGlShader',
|
||||
'Window',
|
||||
'console'
|
||||
'console',
|
||||
'Performance'
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
|
60
gui/lib/system/web/src/animationframeloop.rs
Normal file
60
gui/lib/system/web/src/animationframeloop.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use super::request_animation_frame;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
|
||||
|
||||
// ==========================
|
||||
// === AnimationFrameData ===
|
||||
// ==========================
|
||||
|
||||
struct AnimationFrameData {
|
||||
run : bool
|
||||
}
|
||||
|
||||
pub struct AnimationFrameLoop {
|
||||
forget : bool,
|
||||
data : Rc<RefCell<AnimationFrameData>>
|
||||
}
|
||||
|
||||
|
||||
// ==========================
|
||||
// === AnimationFrameLoop ===
|
||||
// ==========================
|
||||
|
||||
impl AnimationFrameLoop {
|
||||
pub fn new(mut func:Box<dyn FnMut()>) -> Self {
|
||||
let nop_func = Box::new(|| ()) as Box<dyn FnMut()>;
|
||||
let nop_closure = Closure::once(nop_func);
|
||||
let callback = Rc::new(RefCell::new(nop_closure));
|
||||
let run = true;
|
||||
let data = Rc::new(RefCell::new(AnimationFrameData { run }));
|
||||
let callback_clone = callback.clone();
|
||||
let data_clone = data.clone();
|
||||
|
||||
*callback.borrow_mut() = Closure::wrap(Box::new(move || {
|
||||
if data_clone.borrow().run {
|
||||
func();
|
||||
let clb = &callback_clone.borrow();
|
||||
request_animation_frame(&clb).expect("Request Animation Frame");
|
||||
}
|
||||
}) as Box<dyn FnMut()>);
|
||||
request_animation_frame(&callback.borrow()).unwrap();
|
||||
|
||||
let forget = false;
|
||||
AnimationFrameLoop{forget,data}
|
||||
}
|
||||
|
||||
pub fn forget(mut self) {
|
||||
self.forget = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnimationFrameLoop {
|
||||
fn drop(&mut self) {
|
||||
if !self.forget {
|
||||
self.data.borrow_mut().run = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
#![feature(trait_alias)]
|
||||
|
||||
pub mod resize_observer;
|
||||
mod animationframeloop;
|
||||
pub use animationframeloop::AnimationFrameLoop;
|
||||
|
||||
use basegl_prelude::*;
|
||||
|
||||
@ -9,11 +11,13 @@ use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
use web_sys::WebGlRenderingContext;
|
||||
use web_sys::Performance;
|
||||
use web_sys::Node;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use web_sys::console;
|
||||
|
||||
|
||||
// =============
|
||||
// === Error ===
|
||||
// =============
|
||||
@ -30,47 +34,51 @@ pub enum Error {
|
||||
NoWebGL { version: u32 },
|
||||
}
|
||||
impl Error {
|
||||
pub fn missing(name: &str) -> Error {
|
||||
pub fn missing(name:&str) -> Error {
|
||||
let name = name.to_string();
|
||||
Error::Missing { name }
|
||||
}
|
||||
|
||||
pub fn type_mismatch(expected: &str, got: &str) -> Error {
|
||||
pub fn type_mismatch(expected:&str, got:&str) -> Error {
|
||||
let expected = expected.to_string();
|
||||
let got = got.to_string();
|
||||
Error::TypeMismatch { expected, got }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===================
|
||||
// === JS Bindings ===
|
||||
// ===================
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => ($crate::console::log_1(&format_args!($($t)*).to_string().into()))
|
||||
($($t:tt)*) => ($crate::console::log_1(&format_args!($($t)*)
|
||||
.to_string().into()))
|
||||
}
|
||||
|
||||
|
||||
// ==============
|
||||
// === LogMsg ===
|
||||
// ==============
|
||||
|
||||
pub trait LogMsg {
|
||||
fn with_log_msg<F: FnOnce(&str) -> T, T>(&self, f: F) -> T;
|
||||
fn with_log_msg<F: FnOnce(&str) -> T, T>(&self, f:F) -> T;
|
||||
}
|
||||
|
||||
impl LogMsg for &str {
|
||||
fn with_log_msg<F: FnOnce(&str) -> T, T>(&self, f: F) -> T {
|
||||
fn with_log_msg<F: FnOnce(&str) -> T, T>(&self, f:F) -> T {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fn() -> S, S: AsRef<str>> LogMsg for F {
|
||||
fn with_log_msg<G: FnOnce(&str) -> T, T>(&self, f: G) -> T {
|
||||
fn with_log_msg<G: FnOnce(&str) -> T, T>(&self, f:G) -> T {
|
||||
f(self().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ==============
|
||||
// === Logger ===
|
||||
// ==============
|
||||
@ -80,7 +88,7 @@ pub struct Logger {
|
||||
pub path: String,
|
||||
}
|
||||
impl Logger {
|
||||
pub fn new<T: AsRef<str>>(path: T) -> Self {
|
||||
pub fn new<T: AsRef<str>>(path:T) -> Self {
|
||||
let path = path.as_ref().to_string();
|
||||
Self { path }
|
||||
}
|
||||
@ -90,29 +98,29 @@ impl Logger {
|
||||
Self::new("")
|
||||
}
|
||||
|
||||
pub fn sub<T: AsRef<str>>(&self, path: T) -> Self {
|
||||
pub fn sub<T: AsRef<str>>(&self, path:T) -> Self {
|
||||
Self::new(format!("{}.{}", self.path, path.as_ref()))
|
||||
}
|
||||
|
||||
pub fn trace<M: LogMsg>(&self, msg: M) {
|
||||
pub fn trace<M: LogMsg>(&self, msg:M) {
|
||||
console::debug_1(&self.format(msg));
|
||||
}
|
||||
|
||||
pub fn info<M: LogMsg>(&self, msg: M) {
|
||||
pub fn info<M: LogMsg>(&self, msg:M) {
|
||||
// console::info_1(&self.format(msg));
|
||||
console::group_1(&self.format(msg));
|
||||
console::group_end();
|
||||
}
|
||||
|
||||
pub fn warning<M: LogMsg>(&self, msg: M) {
|
||||
pub fn warning<M: LogMsg>(&self, msg:M) {
|
||||
console::warn_1(&self.format(msg));
|
||||
}
|
||||
|
||||
pub fn error<M: LogMsg>(&self, msg: M) {
|
||||
pub fn error<M: LogMsg>(&self, msg:M) {
|
||||
console::error_1(&self.format(msg));
|
||||
}
|
||||
|
||||
pub fn group_begin<M: LogMsg>(&self, msg: M) {
|
||||
pub fn group_begin<M: LogMsg>(&self, msg:M) {
|
||||
console::group_1(&self.format(msg));
|
||||
}
|
||||
|
||||
@ -120,14 +128,14 @@ impl Logger {
|
||||
console::group_end();
|
||||
}
|
||||
|
||||
pub fn group<M: LogMsg, T, F: FnOnce() -> T>(&self, msg: M, f: F) -> T {
|
||||
pub fn group<M:LogMsg, T, F:FnOnce() -> T>(&self, msg:M, f:F) -> T {
|
||||
self.group_begin(msg);
|
||||
let out = f();
|
||||
self.group_end();
|
||||
out
|
||||
}
|
||||
|
||||
fn format<M: LogMsg>(&self, msg: M) -> JsValue {
|
||||
fn format<M: LogMsg>(&self, msg:M) -> JsValue {
|
||||
msg.with_log_msg(|s| format!("[{}] {}", self.path, s)).into()
|
||||
}
|
||||
}
|
||||
@ -138,6 +146,7 @@ impl Default for Logger {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ====================
|
||||
// === Logger Utils ===
|
||||
// ====================
|
||||
@ -170,7 +179,7 @@ macro_rules! group {
|
||||
// === DOM Helpers ===
|
||||
// ===================
|
||||
|
||||
pub fn dyn_into<T, U>(obj : T) -> Result<U>
|
||||
pub fn dyn_into<T,U>(obj :T) -> Result<U>
|
||||
where T : wasm_bindgen::JsCast + Debug,
|
||||
U : wasm_bindgen::JsCast
|
||||
{
|
||||
@ -192,23 +201,23 @@ pub fn document() -> Result<web_sys::Document> {
|
||||
window()?.document().ok_or_else(|| Error::missing("document"))
|
||||
}
|
||||
|
||||
pub fn get_element_by_id(id: &str) -> Result<web_sys::Element> {
|
||||
pub fn get_element_by_id(id:&str) -> Result<web_sys::Element> {
|
||||
document()?.get_element_by_id(id).ok_or_else(|| Error::missing(id))
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use get_element_by_id with dyn_into instead")]
|
||||
pub fn get_element_by_id_as<T: wasm_bindgen::JsCast>(id: &str) -> Result<T> {
|
||||
pub fn get_element_by_id_as<T:wasm_bindgen::JsCast>(id:&str) -> Result<T> {
|
||||
let elem = get_element_by_id(id)?;
|
||||
dyn_into(elem)
|
||||
}
|
||||
pub fn create_element(id: &str) -> Result<web_sys::Element> {
|
||||
pub fn create_element(id:&str) -> Result<web_sys::Element> {
|
||||
match document()?.create_element(id) {
|
||||
Ok(element) => Ok(element),
|
||||
Err(_) => Err(Error::missing(id)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_canvas(id: &str) -> Result<web_sys::HtmlCanvasElement> {
|
||||
pub fn get_canvas(id:&str) -> Result<web_sys::HtmlCanvasElement> {
|
||||
dyn_into(get_element_by_id(id)?)
|
||||
}
|
||||
|
||||
@ -218,90 +227,110 @@ pub fn get_webgl_context(
|
||||
) -> Result<WebGlRenderingContext>
|
||||
{
|
||||
let no_webgl = || Error::NoWebGL { version };
|
||||
let name_sfx = if version == 1 { "".to_string() } else { version.to_string() };
|
||||
let name_sfx = if version == 1 {
|
||||
"".to_string()
|
||||
} else {
|
||||
version.to_string()
|
||||
};
|
||||
let name = &format!("webgl{}", &name_sfx);
|
||||
let context = canvas.get_context(name).map_err(|_| no_webgl())?.ok_or_else(no_webgl)?;
|
||||
let context = canvas.get_context(name)
|
||||
.map_err(|_| no_webgl())?.ok_or_else(no_webgl)?;
|
||||
context.dyn_into().map_err(|_| no_webgl())
|
||||
}
|
||||
|
||||
pub fn request_animation_frame(f: &Closure<dyn FnMut()>) -> Result<i32> {
|
||||
pub fn request_animation_frame(f:&Closure<dyn FnMut()>) -> Result<i32> {
|
||||
let req = window()?.request_animation_frame(f.as_ref().unchecked_ref());
|
||||
req.map_err(|_| Error::missing("requestAnimationFrame"))
|
||||
}
|
||||
|
||||
pub fn cancel_animation_frame(id: i32) -> Result<()> {
|
||||
pub fn cancel_animation_frame(id:i32) -> Result<()> {
|
||||
let req = window()?.cancel_animation_frame(id);
|
||||
req.map_err(|_| Error::missing("cancel_animation_frame"))
|
||||
}
|
||||
|
||||
pub fn get_performance() -> Result<Performance> {
|
||||
window()?.performance().ok_or_else(|| Error::missing("performance"))
|
||||
}
|
||||
|
||||
|
||||
// =====================
|
||||
// === Other Helpers ===
|
||||
// =====================
|
||||
|
||||
pub trait AttributeSetter {
|
||||
fn set_attribute_or_panic<T, U>(&self, name : T, value : U)
|
||||
fn set_attribute_or_panic<T, U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str>;
|
||||
}
|
||||
|
||||
impl AttributeSetter for web_sys::HtmlElement {
|
||||
fn set_attribute_or_panic<T, U>(&self, name : T, value : U)
|
||||
fn set_attribute_or_panic<T,U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str> {
|
||||
let name = name.as_ref();
|
||||
let value = value.as_ref();
|
||||
let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self);
|
||||
self.set_attribute(name, value)
|
||||
let name = name.as_ref();
|
||||
let value = value.as_ref();
|
||||
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
|
||||
self.set_attribute(name,value)
|
||||
.unwrap_or_else(|_| panic!("Failed to set attribute {}", values));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StyleSetter {
|
||||
fn set_property_or_panic<T, U>(&self, name : T, value : U)
|
||||
fn set_property_or_panic<T,U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str>;
|
||||
}
|
||||
|
||||
impl StyleSetter for web_sys::HtmlElement {
|
||||
fn set_property_or_panic<T, U>(&self, name : T, value : U)
|
||||
fn set_property_or_panic<T,U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str> {
|
||||
let name = name.as_ref();
|
||||
let value = value.as_ref();
|
||||
let values = format!("\"{}\" = \"{}\" on \"{:?}\"", name, value, self);
|
||||
self.style().set_property(name, value)
|
||||
.unwrap_or_else(|_| panic!("Failed to set style {}", values));
|
||||
let name = name.as_ref();
|
||||
let value = value.as_ref();
|
||||
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
|
||||
let panic_msg = |_| panic!("Failed to set style {}",values);
|
||||
self.style().set_property(name, value).unwrap_or_else(panic_msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeAppender {
|
||||
fn append_child_or_panic(&self, node : &Node);
|
||||
pub trait NodeInserter {
|
||||
fn append_or_panic (&self, node:&Node);
|
||||
fn prepend_or_panic(&self, node:&Node);
|
||||
fn insert_before_or_panic(&self,node:&Node,reference_node:&Node);
|
||||
}
|
||||
|
||||
impl NodeAppender for Node {
|
||||
fn append_child_or_panic(&self, node : &Node) {
|
||||
self.append_child(node)
|
||||
.unwrap_or_else(|_|
|
||||
panic!("Failed to append child \"{:?}\" to \"{:?}\"",
|
||||
node,
|
||||
self
|
||||
)
|
||||
);
|
||||
impl NodeInserter for Node {
|
||||
fn append_or_panic(&self, node:&Node) {
|
||||
let panic_msg = |_|
|
||||
panic!("Failed to append child {:?} to {:?}",node,self);
|
||||
self.append_child(node).unwrap_or_else(panic_msg);
|
||||
}
|
||||
|
||||
fn prepend_or_panic(&self, node : &Node) {
|
||||
let panic_msg = |_|
|
||||
panic!("Failed to prepend child \"{:?}\" to \"{:?}\"",node,self);
|
||||
|
||||
let first_c = self.first_child();
|
||||
self.insert_before(node, first_c.as_ref()).unwrap_or_else(panic_msg);
|
||||
}
|
||||
fn insert_before_or_panic(&self, node:&Node, ref_node:&Node) {
|
||||
let panic_msg = |_|
|
||||
panic!("Failed to insert {:?} before {:?} in {:?}",
|
||||
node,
|
||||
ref_node,
|
||||
self);
|
||||
self.insert_before(node, Some(ref_node)).unwrap_or_else(panic_msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeRemover {
|
||||
fn remove_child_or_panic(&self, node : &Node);
|
||||
fn remove_child_or_panic(&self, node:&Node);
|
||||
}
|
||||
|
||||
impl NodeRemover for Node {
|
||||
fn remove_child_or_panic(&self, node : &Node) {
|
||||
self.remove_child(node)
|
||||
.unwrap_or_else(|_|
|
||||
panic!("Failed to remove child \"{:?}\" from \"{:?}\"",
|
||||
node,
|
||||
self
|
||||
)
|
||||
);
|
||||
fn remove_child_or_panic(&self, node:&Node) {
|
||||
let panic_msg = |_|
|
||||
panic!("Failed to remove child {:?} from {:?}",node,self);
|
||||
self.remove_child(node).unwrap_or_else(panic_msg);
|
||||
}
|
||||
}
|
||||
|
14
gui/lib/web-test-proc-macro/Cargo.toml
Normal file
14
gui/lib/web-test-proc-macro/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "web-test-proc-macro"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@luna-lang.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.1"
|
||||
syn = { version = "1.0.2", features = [ "full" ] }
|
||||
quote = "1.0"
|
||||
wasm-bindgen-test = "0.3.3"
|
109
gui/lib/web-test-proc-macro/src/lib.rs
Normal file
109
gui/lib/web-test-proc-macro/src/lib.rs
Normal file
@ -0,0 +1,109 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::*;
|
||||
use quote::quote;
|
||||
|
||||
// FIXME: Parse proc_macro args to read the following info:
|
||||
// #[web_test(dimensions(320.0, 240.0)) and
|
||||
// #[web_test(no_container)].
|
||||
|
||||
// ===================
|
||||
// === #[web_test] ===
|
||||
// ===================
|
||||
|
||||
/// #[web_test] creates a [320.0, 240.0] div with id = fn_name and appends it
|
||||
/// into the document.
|
||||
/// # Example
|
||||
/// ```rust,compile_fail
|
||||
/// use web_test::web_test;
|
||||
/// use web_test::web_configure;
|
||||
/// use basegl::system::web::get_element_by_id;
|
||||
///
|
||||
/// web_configure!(run_in_browser);
|
||||
///
|
||||
/// #[web_test]
|
||||
/// fn get_identified_element() {
|
||||
/// // #[web_test] creates a <div id="get_identified_element"></div>
|
||||
/// assert!(get_element_by_id("get_identified_element").is_ok());
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn web_test(_args:TokenStream, input:TokenStream) -> TokenStream {
|
||||
if let Ok(mut parsed) = syn::parse::<ItemFn>(input.clone()) {
|
||||
let fn_string = format!("{}", parsed.sig.ident);
|
||||
let code = format!("Container::new(\"Tests\", \"{}\", 320.0, 240.0);",
|
||||
fn_string);
|
||||
|
||||
if let Ok(stmt) = parse_str::<Stmt>(&code) {
|
||||
// We insert Container::new("Tests", fn_name, 320.0, 240.0)
|
||||
// at the beginning of the function block.
|
||||
parsed.block.stmts.insert(0, stmt);
|
||||
|
||||
let output = quote! {
|
||||
#[wasm_bindgen_test]
|
||||
#parsed
|
||||
};
|
||||
output.into()
|
||||
} else {
|
||||
input
|
||||
}
|
||||
} else {
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ====================
|
||||
// === #[web_bench] ===
|
||||
// ====================
|
||||
|
||||
/// #[web_bench] creates a benchmark div with a toggle button.
|
||||
/// # Example
|
||||
/// ```rust,compile_fail
|
||||
/// use web_test::web_bench;
|
||||
/// use web_test::web_configure;
|
||||
/// use basegl::system::web::get_element_by_id;
|
||||
/// use basegl::system::web::dyn_into;
|
||||
/// use web_sys::HtmlElement;
|
||||
///
|
||||
/// web_configure!(run_in_browser);
|
||||
///
|
||||
/// #[web_bench]
|
||||
/// fn test_performance(b: &mut Bencher) {
|
||||
/// let element = get_element_by_id("test_performance").expect("div");
|
||||
/// let element : HtmlElement = dyn_into(element).expect("HtmlElement");
|
||||
///
|
||||
/// let numbers : Vec<_> = (1 ..= 1000).collect();
|
||||
/// b.iter(move || {
|
||||
/// let ans = numbers.iter().fold(0, |acc, x| acc + x);
|
||||
/// element.set_inner_html(&format!("Answer: {}", ans));
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn web_bench(_args:TokenStream, input:TokenStream) -> TokenStream {
|
||||
|
||||
if let Ok(parsed) = parse::<ItemFn>(input.clone()) {
|
||||
use proc_macro2::*;
|
||||
let input : TokenStream = input.into();
|
||||
|
||||
let fn_ident = parsed.sig.ident;
|
||||
let fn_string = format!("{}", fn_ident);
|
||||
let fn_benchmark_str = format!("{}_benchmark", fn_string);
|
||||
let fn_benchmark = Ident::new(&fn_benchmark_str, Span::call_site());
|
||||
|
||||
let output = quote! {
|
||||
#[wasm_bindgen_test]
|
||||
fn #fn_benchmark() {
|
||||
let container = BenchContainer::new(#fn_string, 320.0, 240.0);
|
||||
let mut b = Bencher::new(container);
|
||||
#fn_ident(&mut b);
|
||||
}
|
||||
#input
|
||||
};
|
||||
output.into()
|
||||
} else {
|
||||
input
|
||||
}
|
||||
}
|
19
gui/lib/web-test/Cargo.toml
Normal file
19
gui/lib/web-test/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "web-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Enso Team <contact@luna-lang.org>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen-test = "0.3.3"
|
||||
web-test-proc-macro = { version = "0.1.0" , path = "../web-test-proc-macro" }
|
||||
basegl-prelude = { version = "0.1.0" , path = "../prelude" }
|
||||
basegl-system-web = { version = "0.1.0" , path = "../system/web" }
|
||||
wasm-bindgen = { version = "^0.2" , features = ["nightly"] }
|
||||
js-sys = { version = "0.3.28" }
|
||||
shrinkwraprs = { version = "0.2.1" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.4"
|
62
gui/lib/web-test/src/bench_container.rs
Normal file
62
gui/lib/web-test/src/bench_container.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Container;
|
||||
use crate::system::web::create_element;
|
||||
use crate::system::web::dyn_into;
|
||||
use crate::system::web::NodeInserter;
|
||||
use crate::system::web::StyleSetter;
|
||||
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
// ======================
|
||||
// === BenchContainer ===
|
||||
// ======================
|
||||
|
||||
/// Html container displaying benchmark results.
|
||||
#[derive(Shrinkwrap)]
|
||||
pub struct BenchContainer {
|
||||
#[shrinkwrap(main_field)]
|
||||
container : Container,
|
||||
pub measurement : HtmlElement,
|
||||
pub time : HtmlElement,
|
||||
pub iter : HtmlElement,
|
||||
pub button : HtmlElement
|
||||
}
|
||||
|
||||
impl BenchContainer {
|
||||
/// Creates an identificable container with provided dimensions.
|
||||
pub fn new(name:&str, width:f32, height:f32) -> Self {
|
||||
let div = create_element("div").expect("div");
|
||||
let div : HtmlElement = dyn_into(div).expect("HtmlElement");
|
||||
|
||||
div.set_property_or_panic("margin" , "0px 2px");
|
||||
div.set_property_or_panic("height" , "24px");
|
||||
div.set_property_or_panic("bottom-border" , "1px solid black");
|
||||
div.set_property_or_panic("display" , "flex");
|
||||
div.set_property_or_panic("justify-content", "space-between");
|
||||
div.set_property_or_panic("align-items" , "center");
|
||||
|
||||
div.set_inner_html("<div>00.00ms</div>\
|
||||
<div>0 iterations</div>\
|
||||
<button>Toggle</button>");
|
||||
|
||||
let children = div.children();
|
||||
let time = children.item(0).expect("time div");
|
||||
let iter = children.item(1).expect("iter div");
|
||||
let button = children.item(2).expect("button div");
|
||||
let time : HtmlElement = dyn_into(time).expect("time HtmlElement");
|
||||
let iter : HtmlElement = dyn_into(iter).expect("iter HtmlElement");
|
||||
let button : HtmlElement = dyn_into(button).expect("buttn HtmlElement");
|
||||
|
||||
let container = Container::new("Benchmarks", name, width, height);
|
||||
let header_height = 17.0;
|
||||
let height = format!("{}px", height + header_height + 25.0);
|
||||
|
||||
container.div.set_property_or_panic("height", height);
|
||||
container.div.insert_before_or_panic(&div, &container.container);
|
||||
|
||||
let measurement = div;
|
||||
Self { container,measurement,time,iter,button }
|
||||
}
|
||||
}
|
133
gui/lib/web-test/src/bencher.rs
Normal file
133
gui/lib/web-test/src/bencher.rs
Normal file
@ -0,0 +1,133 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::BenchContainer;
|
||||
pub use crate::system::web::get_performance;
|
||||
pub use crate::system::web::AnimationFrameLoop;
|
||||
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
||||
// ===================
|
||||
// === BencherCell ===
|
||||
// ===================
|
||||
|
||||
/// Cell, used to hold Bencher's data
|
||||
pub struct BencherCell {
|
||||
func : Box<dyn FnMut()>,
|
||||
container : BenchContainer,
|
||||
iterations : usize,
|
||||
total_time : f64,
|
||||
anim_loop : Option<AnimationFrameLoop>
|
||||
}
|
||||
|
||||
impl BencherCell {
|
||||
pub fn new(f:Box<dyn FnMut()>, container:BenchContainer) -> Self {
|
||||
let func = f;
|
||||
let iterations = 0;
|
||||
let total_time = 0.0;
|
||||
let anim_loop = None;
|
||||
Self { func,container,iterations,total_time,anim_loop }
|
||||
}
|
||||
|
||||
/// Adds the duration of the next iteration and updates the UI.
|
||||
pub fn add_iteration_time(&mut self, time : f64) {
|
||||
self.iterations += 1;
|
||||
self.total_time += time;
|
||||
let iterations = format!("{} iterations", self.iterations);
|
||||
let average = self.total_time / self.iterations as f64;
|
||||
let display = format!("{:.2}ms", average);
|
||||
|
||||
self.container.iter.set_inner_html(&iterations);
|
||||
self.container.time.set_inner_html(&display);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===================
|
||||
// === BencherData ===
|
||||
// ===================
|
||||
|
||||
#[derive(Shrinkwrap)]
|
||||
pub struct BencherData {
|
||||
cell : RefCell<BencherCell>
|
||||
}
|
||||
|
||||
impl BencherData {
|
||||
pub fn new(f:Box<dyn FnMut()>, container:BenchContainer) -> Rc<Self> {
|
||||
let cell = RefCell::new(BencherCell::new(f, container));
|
||||
Rc::new(Self { cell })
|
||||
}
|
||||
|
||||
/// Starts the benchmarking loop.
|
||||
fn start(self:&Rc<Self>) {
|
||||
let data_clone = self.clone();
|
||||
let performance = get_performance().expect("Performance object");
|
||||
let mut t0 = performance.now();
|
||||
let anim_loop = AnimationFrameLoop::new(Box::new(move || {
|
||||
let mut data = data_clone.borrow_mut();
|
||||
|
||||
(&mut data.func)();
|
||||
|
||||
let t1 = performance.now();
|
||||
let dt = t1 - t0;
|
||||
t0 = t1;
|
||||
|
||||
data.add_iteration_time(dt);
|
||||
}));
|
||||
self.borrow_mut().anim_loop = Some(anim_loop);
|
||||
}
|
||||
|
||||
/// Stops the benchmarking loop.
|
||||
fn stop(self:&Rc<Self>) {
|
||||
self.borrow_mut().anim_loop = None;
|
||||
}
|
||||
|
||||
/// Check if the loop is running.
|
||||
fn is_running(self:&Rc<Self>) -> bool {
|
||||
self.borrow().anim_loop.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
// ===============
|
||||
// === Bencher ===
|
||||
// ===============
|
||||
|
||||
/// The Bencher struct with an API compatible to Rust's test Bencher.
|
||||
pub struct Bencher {
|
||||
data : Rc<BencherData>
|
||||
}
|
||||
|
||||
impl Bencher {
|
||||
/// Creates a Bencher with a html test container.
|
||||
pub fn new(container:BenchContainer) -> Self {
|
||||
let func = Box::new(|| ());
|
||||
let data = BencherData::new(func, container);
|
||||
|
||||
let data_clone = data.clone();
|
||||
let closure = Box::new(move || {
|
||||
if data_clone.is_running() {
|
||||
data_clone.stop();
|
||||
} else {
|
||||
data_clone.start();
|
||||
}
|
||||
}) as Box<dyn FnMut()>;
|
||||
|
||||
{
|
||||
let closure = Closure::wrap(closure);
|
||||
let cell = data.cell.borrow();
|
||||
let measurement = &cell.container.measurement;
|
||||
measurement.set_onclick(Some(closure.as_ref().unchecked_ref()));
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
Self { data }
|
||||
}
|
||||
|
||||
/// Callback for benchmark functions to run in their body.
|
||||
pub fn iter<T, F:FnMut() -> T + 'static>(&mut self, mut func:F) {
|
||||
self.data.borrow_mut().func = Box::new(move || { func(); });
|
||||
}
|
||||
}
|
56
gui/lib/web-test/src/container.rs
Normal file
56
gui/lib/web-test/src/container.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use super::Group;
|
||||
use crate::system::web::create_element;
|
||||
use crate::system::web::dyn_into;
|
||||
use crate::system::web::AttributeSetter;
|
||||
use crate::system::web::StyleSetter;
|
||||
use crate::system::web::NodeInserter;
|
||||
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
// =================
|
||||
// === Container ===
|
||||
// =================
|
||||
|
||||
/// A container to hold tests in `wasm-pack test`.
|
||||
pub struct Container {
|
||||
pub div : HtmlElement,
|
||||
pub header : HtmlElement,
|
||||
pub container : HtmlElement
|
||||
}
|
||||
|
||||
impl Container {
|
||||
/// Creates an identificable container with provided dimensions.
|
||||
pub fn new(group:&str, name:&str, width:f32, height:f32) -> Self {
|
||||
let div = create_element("div").expect("div");
|
||||
let div = dyn_into::<_, HtmlElement>(div).expect("HtmlElement");
|
||||
let width = format!("{}px", width);
|
||||
let header = create_element("center").expect("div");
|
||||
let header = dyn_into::<_, HtmlElement>(header).expect("HtmlElement");
|
||||
|
||||
div.set_property_or_panic("width" , &width);
|
||||
div.set_property_or_panic("height" , format!("{}px", height + 17.0));
|
||||
div.set_property_or_panic("border" , "1px solid black");
|
||||
div.set_property_or_panic("position", "relative");
|
||||
div.set_property_or_panic("margin" , "10px");
|
||||
header.set_inner_html(name);
|
||||
header.set_property_or_panic("width" , &width);
|
||||
header.set_property_or_panic("height", format!("{}px", 16.0));
|
||||
header.set_property_or_panic("border-bottom", "1px solid black");
|
||||
header.set_property_or_panic("position", "relative");
|
||||
div.append_or_panic(&header);
|
||||
|
||||
let container = create_element("div").expect("div");
|
||||
let container : HtmlElement = dyn_into(container).expect("HtmlElement");
|
||||
|
||||
container.set_property_or_panic("width" , width);
|
||||
container.set_property_or_panic("height", format!("{}px", height));
|
||||
container.set_attribute_or_panic("id", name);
|
||||
container.set_property_or_panic("position", "relative");
|
||||
|
||||
div.append_or_panic(&container);
|
||||
|
||||
Group::new(group).div.append_or_panic(&div);
|
||||
Self { div, header, container }
|
||||
}
|
||||
}
|
57
gui/lib/web-test/src/group.rs
Normal file
57
gui/lib/web-test/src/group.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use crate::system::web::document;
|
||||
use crate::system::web::dyn_into;
|
||||
use crate::system::web::create_element;
|
||||
use crate::system::web::get_element_by_id;
|
||||
use crate::system::web::AttributeSetter;
|
||||
use crate::system::web::StyleSetter;
|
||||
use crate::system::web::NodeInserter;
|
||||
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
// =============
|
||||
// === Group ===
|
||||
// =============
|
||||
|
||||
/// Helper to group test containers
|
||||
pub struct Group {
|
||||
pub div : HtmlElement,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn new(name:&str) -> Self {
|
||||
let div:HtmlElement = match get_element_by_id(name) {
|
||||
// If id=name exists, we use it.
|
||||
Ok(div) => dyn_into(div).expect("div should be a HtmlElement"),
|
||||
// If it doesn't exist, we create a new element.
|
||||
Err(_) => {
|
||||
let div = create_element("div");
|
||||
let div = div.expect("TestGroup failed to create div");
|
||||
let div = dyn_into::<_, HtmlElement>(div).expect("HtmlElement");
|
||||
|
||||
div.set_attribute_or_panic("id" , name);
|
||||
div.set_property_or_panic ("display" , "flex");
|
||||
div.set_property_or_panic ("flex-wrap" , "wrap");
|
||||
div.set_property_or_panic ("border" , "1px solid black");
|
||||
div.set_property_or_panic ("margin-bottom", "10px");
|
||||
|
||||
let header = create_element("center");
|
||||
let header = header.expect("TestGroup failed to create header");
|
||||
let header = dyn_into::<_, HtmlElement>(header);
|
||||
let header = header.expect("HtmlElement");
|
||||
let border = "1px solid black";
|
||||
|
||||
header.set_inner_html(name);
|
||||
header.set_property_or_panic("border-bottom", border);
|
||||
header.set_property_or_panic("width" , "100%");
|
||||
div.append_or_panic(&header);
|
||||
|
||||
let document = document().expect("Document is not present");
|
||||
let body = document.body().expect("Body is not present");
|
||||
body.append_or_panic(&div);
|
||||
div
|
||||
},
|
||||
};
|
||||
Self { div }
|
||||
}
|
||||
}
|
21
gui/lib/web-test/src/lib.rs
Normal file
21
gui/lib/web-test/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
#![feature(arbitrary_self_types)]
|
||||
|
||||
mod system {
|
||||
pub use basegl_system_web as web;
|
||||
}
|
||||
|
||||
use basegl_prelude as prelude;
|
||||
|
||||
pub use wasm_bindgen_test::wasm_bindgen_test_configure as web_configure;
|
||||
pub use web_test_proc_macro::*;
|
||||
pub use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
mod bencher;
|
||||
mod group;
|
||||
mod container;
|
||||
mod bench_container;
|
||||
|
||||
pub use bencher::Bencher;
|
||||
pub use group::Group;
|
||||
pub use container::Container;
|
||||
pub use bench_container::BenchContainer;
|
@ -1,13 +0,0 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
Loading…
Reference in New Issue
Block a user