mirror of
https://github.com/enso-org/enso.git
synced 2024-12-28 15:33:23 +03:00
World Integration 🌎🕊️ to Css3dSystem (https://github.com/enso-org/ide/pull/133)
CSS 3D System x World integration
Original commit: 7c47432a09
This commit is contained in:
parent
692432e498
commit
685c6d0c37
5
gui/.github/workflows/build.yml
vendored
5
gui/.github/workflows/build.yml
vendored
@ -45,9 +45,6 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
- name: Install Firefox
|
||||
run: brew cask install firefox
|
||||
if: runner.os == 'macOS'
|
||||
- name: Run wasm-pack tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
@ -55,7 +52,7 @@ jobs:
|
||||
args: >
|
||||
--manifest-path=script/rust/Cargo.toml
|
||||
--bin test-all
|
||||
-- --headless --firefox
|
||||
-- --headless --chrome
|
||||
|
||||
size:
|
||||
name: Check size
|
||||
|
@ -38,7 +38,7 @@ pub struct EventListener<T:?Sized> {
|
||||
}
|
||||
|
||||
impl<T:?Sized> EventListener<T> {
|
||||
fn new<Str:AsRef<str>>(target:EventTarget, name:Str, callback:Closure<T>) -> Self {
|
||||
fn new<S:Str>(target:EventTarget, name:S, callback:Closure<T>) -> Self {
|
||||
let name = name.as_ref().to_string();
|
||||
Self { target,name,callback }
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ use crate::control::io::mouse::button::*;
|
||||
|
||||
macro_rules! define_events {
|
||||
( $( $js_event:ident :: $name:ident ),* $(,)? ) => {$(
|
||||
#[derive(Debug,Clone,From,Shrinkwrap)]
|
||||
/// Mouse event wrapper.
|
||||
#[derive(Debug,Clone,From,Shrinkwrap)]
|
||||
pub struct $name {
|
||||
raw: web_sys::$js_event
|
||||
}
|
||||
|
@ -344,6 +344,10 @@ impl DisplayObjectData {
|
||||
Self {rc}
|
||||
}
|
||||
|
||||
pub fn with_logger<F:FnOnce(&Logger)>(&self, f:F) {
|
||||
f(&self.rc.borrow().logger)
|
||||
}
|
||||
|
||||
pub fn dispatch(&self, event:&DynEvent) {
|
||||
self.rc.borrow_mut().dispatch(event)
|
||||
}
|
||||
|
@ -20,9 +20,12 @@ use crate::system::web::resize_observer::ResizeObserver;
|
||||
use crate::system::web;
|
||||
use crate::display::object::DisplayObjectOps;
|
||||
use crate::system::gpu::data::uniform::Uniform;
|
||||
use crate::system::web::dom::html::Css3dRenderer;
|
||||
use crate::system::web::dyn_into;
|
||||
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
use crate::control::io::mouse2::MouseManager;
|
||||
use crate::control::io::mouse2;
|
||||
@ -62,11 +65,17 @@ impl Shape {
|
||||
Self {rc}
|
||||
}
|
||||
|
||||
pub fn from_element(element:&HtmlElement) -> Self {
|
||||
let bounding_box = element.get_bounding_client_rect();
|
||||
let width = bounding_box.width() as f32;
|
||||
let height = bounding_box.height() as f32;
|
||||
Self::new(width,height)
|
||||
}
|
||||
|
||||
pub fn from_window(window:&web_sys::Window) -> Self {
|
||||
let width = window.inner_width().unwrap().as_f64().unwrap() as f32;
|
||||
let height = window.inner_height().unwrap().as_f64().unwrap() as f32;
|
||||
let rc = Rc::new(RefCell::new(ShapeData::new(width,height)));
|
||||
Self{rc}
|
||||
Self::new(width,height)
|
||||
}
|
||||
|
||||
|
||||
@ -215,23 +224,24 @@ shared! { Scene
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct SceneData {
|
||||
root : DisplayObjectData,
|
||||
canvas : web_sys::HtmlCanvasElement,
|
||||
context : Context,
|
||||
symbols : SymbolRegistry,
|
||||
symbols_dirty : SymbolRegistryDirty,
|
||||
camera : Camera2d,
|
||||
shape : Shape,
|
||||
shape_dirty : ShapeDirty,
|
||||
logger : Logger,
|
||||
listeners : Listeners,
|
||||
variables : UniformScope,
|
||||
pipeline : RenderPipeline,
|
||||
composer : RenderComposer,
|
||||
stats : Stats,
|
||||
pixel_ratio : Uniform<f32>,
|
||||
zoom_uniform : Uniform<f32>,
|
||||
mouse : Mouse,
|
||||
root : DisplayObjectData,
|
||||
canvas : web_sys::HtmlCanvasElement,
|
||||
context : Context,
|
||||
css3d_renderer : Css3dRenderer,
|
||||
symbols : SymbolRegistry,
|
||||
symbols_dirty : SymbolRegistryDirty,
|
||||
camera : Camera2d,
|
||||
shape : Shape,
|
||||
shape_dirty : ShapeDirty,
|
||||
logger : Logger,
|
||||
listeners : Listeners,
|
||||
variables : UniformScope,
|
||||
pipeline : RenderPipeline,
|
||||
composer : RenderComposer,
|
||||
stats : Stats,
|
||||
pixel_ratio : Uniform<f32>,
|
||||
zoom_uniform : Uniform<f32>,
|
||||
mouse : Mouse,
|
||||
|
||||
|
||||
#[derivative(Debug="ignore")]
|
||||
@ -255,11 +265,11 @@ impl {
|
||||
let sub_logger = logger.sub("symbols");
|
||||
let variables = UniformScope::new(logger.sub("global_variables"),&context);
|
||||
let symbols = SymbolRegistry::new(&variables,&stats,&context,sub_logger,on_change);
|
||||
let window = crate::system::web::window();
|
||||
let shape = Shape::from_window(&window);
|
||||
let shape_data = shape.screen_shape();
|
||||
let width = shape_data.width;
|
||||
let height = shape_data.height;
|
||||
let canvas_parent = dyn_into::<_,HtmlElement>(canvas.parent_node().unwrap()).unwrap();
|
||||
let shape = Shape::from_element(&canvas_parent);
|
||||
let screen_shape = shape.screen_shape();
|
||||
let width = screen_shape.width;
|
||||
let height = screen_shape.height;
|
||||
let listeners = Self::init_listeners(&logger,&canvas,&shape,&shape_dirty);
|
||||
let symbols_dirty = dirty_flag;
|
||||
let camera = Camera2d::new(logger.sub("camera"),width,height);
|
||||
@ -284,8 +294,15 @@ impl {
|
||||
let height = shape.canvas_shape().height as i32;
|
||||
let composer = RenderComposer::new(&pipeline,&context,&variables,width,height);
|
||||
|
||||
let css3d_renderer = Css3dRenderer::from_element_or_panic(&logger,canvas_parent);
|
||||
|
||||
Self { pipeline,composer,root,canvas,context,symbols,camera,symbols_dirty,shape,shape_dirty
|
||||
, logger,listeners,variables,on_resize,stats,pixel_ratio,mouse,zoom_uniform }
|
||||
, logger,listeners,variables,on_resize,stats,pixel_ratio,mouse,zoom_uniform
|
||||
, css3d_renderer }
|
||||
}
|
||||
|
||||
pub fn css3d_renderer(&self) -> Css3dRenderer {
|
||||
self.css3d_renderer.clone()
|
||||
}
|
||||
|
||||
pub fn canvas(&self) -> web_sys::HtmlCanvasElement {
|
||||
@ -351,6 +368,7 @@ impl {
|
||||
}
|
||||
self.logger.info("Rendering meshes.");
|
||||
self.symbols.render(&self.camera);
|
||||
self.css3d_renderer.render(&self.camera);
|
||||
|
||||
self.composer.run();
|
||||
})
|
||||
|
@ -1,8 +1,9 @@
|
||||
//! Root module for all example scenes.
|
||||
|
||||
pub mod sprite_system;
|
||||
pub mod css3d_system;
|
||||
pub mod shapes;
|
||||
pub mod easing_animator;
|
||||
pub mod glyph_system;
|
||||
pub mod shapes;
|
||||
pub mod sprite_system;
|
||||
pub mod text_selecting;
|
||||
pub mod text_typing;
|
||||
|
86
gui/lib/core/src/examples/css3d_system.rs
Normal file
86
gui/lib/core/src/examples/css3d_system.rs
Normal file
@ -0,0 +1,86 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::system::web;
|
||||
use web::dom::html::Css3dSystem;
|
||||
use web::dom::html::Css3dObject;
|
||||
use web::dom::html::Css3dOrder;
|
||||
use web::StyleSetter;
|
||||
use crate::display::object::DisplayObject;
|
||||
use crate::display::object::DisplayObjectOps;
|
||||
use crate::display::symbol::geometry::Sprite;
|
||||
use crate::display::symbol::geometry::SpriteSystem;
|
||||
use crate::display::world::*;
|
||||
use crate::prelude::*;
|
||||
use crate::animation::animator::fixed_step::FixedStepAnimator;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
use nalgebra::Vector3;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use crate::display::navigation::navigator::Navigator;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(dead_code)]
|
||||
pub fn run_example_css3d_system() {
|
||||
web::forward_panic_hook_to_console();
|
||||
web::set_stdout();
|
||||
init(WorldData::new("canvas"));
|
||||
}
|
||||
|
||||
fn init(world:World) {
|
||||
let scene = world.scene();
|
||||
let camera = scene.camera();
|
||||
let screen = camera.screen();
|
||||
let navigator = Navigator::new(&scene, &camera).expect("Couldn't create navigator");
|
||||
let sprite_system = SpriteSystem::new(&world);
|
||||
let css3d_system = Css3dSystem::new(&world);
|
||||
world.add_child(&sprite_system);
|
||||
world.add_child(&css3d_system);
|
||||
|
||||
let mut sprites: Vec<Sprite> = default();
|
||||
let mut css3d_objects: Vec<Css3dObject> = default();
|
||||
let count = 10;
|
||||
for i in 0 .. count {
|
||||
let x = i as f32;
|
||||
let width = screen.width * 1.5 / count as f32;
|
||||
let height = screen.height;
|
||||
if i % 2 == 0 {
|
||||
let height = height * 0.75;
|
||||
let dimensions = Vector2::new(width, height);
|
||||
let y = screen.height / 2.0;
|
||||
let position = Vector3::new(width / 1.5 * x + width / 2.0, y, 0.0);
|
||||
let sprite = sprite_system.new_instance();
|
||||
sprite.size().set(dimensions);
|
||||
sprite.mod_position(|t| *t = position);
|
||||
sprites.push(sprite);
|
||||
} else {
|
||||
let dimensions = Vector2::new(width, height);
|
||||
let position = Vector3::new(width / 1.5 * x + width / 2.0, height / 2.0, 0.0);
|
||||
let mut object = css3d_system.new_instance("div").expect("Couldn't create div");
|
||||
let r = ((x + 0.0) * 16.0) as u8;
|
||||
let g = ((x + 2.0) * 32.0) as u8;
|
||||
let b = ((x + 4.0) * 64.0) as u8;
|
||||
let color = iformat!("rgb({r},{g},{b})");
|
||||
object.dom().set_style_or_panic("background-color",color);
|
||||
object.set_dimensions(dimensions);
|
||||
object.mod_position(|t| *t = position);
|
||||
css3d_objects.push(object);
|
||||
}
|
||||
}
|
||||
world.display_object().update();
|
||||
|
||||
let css3d_position = vec![Css3dOrder::Front, Css3dOrder::Back];
|
||||
let mut i = 0;
|
||||
let animator = FixedStepAnimator::new(2.0, move |_| {
|
||||
let _keep_alive = &world;
|
||||
let _keep_alive = &navigator;
|
||||
let _keep_alive = &sprites;
|
||||
let _keep_alive = &sprite_system;
|
||||
let _keep_alive = &css3d_system;
|
||||
|
||||
i = (i + 1) % 2;
|
||||
for (j, object) in css3d_objects.iter_mut().enumerate() {
|
||||
object.set_css3d_order(css3d_position[(i + j) % 2]);
|
||||
}
|
||||
});
|
||||
std::mem::forget(animator);
|
||||
}
|
@ -23,15 +23,15 @@ use js_sys::Math;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
/// A simplified Canvas object used in the EasingAnimator example.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Canvas {
|
||||
canvas : HtmlCanvasElement,
|
||||
context : CanvasRenderingContext2d
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
/// Interpolable properties for our example code.
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Properties {
|
||||
/// Position property.
|
||||
pub position : Vector2<f32>,
|
||||
@ -78,7 +78,7 @@ impl Canvas {
|
||||
pub fn new(container_id:&str) -> Self {
|
||||
let canvas = create_element("canvas").unwrap();
|
||||
let canvas: HtmlCanvasElement = canvas.dyn_into().unwrap();
|
||||
canvas.set_property_or_panic("border", "1px solid black");
|
||||
canvas.set_style_or_panic("border", "1px solid black");
|
||||
|
||||
canvas.set_width (256);
|
||||
canvas.set_height(256);
|
||||
@ -228,11 +228,11 @@ impl Example {
|
||||
where F1:FnEasing, F2:FnEasing, F3:FnEasing {
|
||||
let example : HtmlElement = create_element("div").unwrap().dyn_into().unwrap();
|
||||
example.set_attribute_or_panic("id", name);
|
||||
example.set_property_or_panic("margin", "10px");
|
||||
example.set_style_or_panic("margin", "10px");
|
||||
let container : HtmlElement = get_element_by_id("examples").unwrap().dyn_into().unwrap();
|
||||
let header : HtmlElement = create_element("center").unwrap().dyn_into().unwrap();
|
||||
header.set_property_or_panic("background-color", "black");
|
||||
header.set_property_or_panic("color", "white");
|
||||
header.set_style_or_panic("background-color", "black");
|
||||
header.set_style_or_panic("color", "white");
|
||||
header.set_inner_html(name);
|
||||
example.append_or_panic(&header);
|
||||
container.append_or_panic(&example);
|
||||
@ -305,10 +305,10 @@ macro_rules! example {
|
||||
pub fn run_example_easing_animator() {
|
||||
let container : HtmlElement = create_element("div").unwrap().dyn_into().unwrap();
|
||||
container.set_attribute_or_panic("id", "examples");
|
||||
container.set_property_or_panic("display", "flex");
|
||||
container.set_property_or_panic("flex-wrap", "wrap");
|
||||
container.set_property_or_panic("position", "absolute");
|
||||
container.set_property_or_panic("top", "0px");
|
||||
container.set_style_or_panic("display", "flex");
|
||||
container.set_style_or_panic("flex-wrap", "wrap");
|
||||
container.set_style_or_panic("position", "absolute");
|
||||
container.set_style_or_panic("top", "0px");
|
||||
get_element_by_id("app").unwrap().append_or_panic(&container);
|
||||
example!(expo);
|
||||
example!(bounce);
|
||||
|
@ -1,9 +1,10 @@
|
||||
//! This module contains all the submodules of the CSS3D rendering system.
|
||||
|
||||
mod html_scene;
|
||||
mod html_object;
|
||||
mod html_renderer;
|
||||
mod css3d_system;
|
||||
mod css3d_object;
|
||||
mod css3d_renderer;
|
||||
|
||||
pub use html_scene::HtmlScene;
|
||||
pub use html_object::HtmlObject;
|
||||
pub use html_renderer::HtmlRenderer;
|
||||
pub use css3d_object::Css3dObject;
|
||||
pub use css3d_object::Css3dOrder;
|
||||
pub use css3d_renderer::Css3dRenderer;
|
||||
pub use css3d_system::Css3dSystem;
|
||||
|
220
gui/lib/core/src/system/web/dom/html/css3d_object.rs
Normal file
220
gui/lib/core/src/system/web/dom/html/css3d_object.rs
Normal file
@ -0,0 +1,220 @@
|
||||
//! This module contains the implementation of Css3dObject, a struct used to represent CSS3D
|
||||
//! elements.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display::object::DisplayObjectData;
|
||||
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 crate::system::web::NodeRemover;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
use nalgebra::Vector3;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Css3dOrder ===
|
||||
// ==================
|
||||
|
||||
/// This enumeration is used for moving the object from front to back of the surrounding HtmlElement
|
||||
/// (usually a WebGL canvas).
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub enum Css3dOrder {
|
||||
Front,
|
||||
Back
|
||||
}
|
||||
|
||||
impl Default for Css3dOrder {
|
||||
fn default() -> Self {
|
||||
Css3dOrder::Front
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============================
|
||||
// === Css3dObjectProperties ===
|
||||
// =============================
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Css3dObjectProperties {
|
||||
display_object : DisplayObjectData,
|
||||
dom : HtmlElement,
|
||||
dimensions : Vector2<f32>,
|
||||
css3d_order : Css3dOrder
|
||||
}
|
||||
|
||||
impl Drop for Css3dObjectProperties {
|
||||
fn drop(&mut self) {
|
||||
self.display_object.with_logger(|logger| {
|
||||
self.dom.remove_from_parent_or_warn(logger);
|
||||
});
|
||||
self.display_object.unset_parent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === Css3dObjectData ===
|
||||
// =======================
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub(super) struct Css3dObjectData {
|
||||
properties : Rc<RefCell<Css3dObjectProperties>>
|
||||
}
|
||||
|
||||
impl Css3dObjectData {
|
||||
fn new
|
||||
( display_object : DisplayObjectData
|
||||
, dom : HtmlElement
|
||||
, dimensions : Vector2<f32>
|
||||
, css3d_order : Css3dOrder) -> Self {
|
||||
let properties = Css3dObjectProperties {display_object,dom,dimensions,css3d_order};
|
||||
let properties = Rc::new(RefCell::new(properties));
|
||||
Self {properties}
|
||||
}
|
||||
|
||||
fn set_css3d_order(&self, css3d_order: Css3dOrder) {
|
||||
self.properties.borrow_mut().css3d_order = css3d_order
|
||||
}
|
||||
|
||||
fn css3d_order(&self) -> Css3dOrder {
|
||||
self.properties.borrow().css3d_order
|
||||
}
|
||||
|
||||
fn position(&self) -> Vector3<f32> {
|
||||
self.properties.borrow().display_object.position()
|
||||
}
|
||||
|
||||
fn set_dimensions(&self, dimensions:Vector2<f32>) {
|
||||
let mut properties = self.properties.borrow_mut();
|
||||
properties.dimensions = dimensions;
|
||||
properties.display_object.with_logger(|logger| {
|
||||
properties.dom.set_style_or_warn("width", format!("{}px", dimensions.x), logger);
|
||||
properties.dom.set_style_or_warn("height", format!("{}px", dimensions.y), logger);
|
||||
});
|
||||
}
|
||||
|
||||
fn dimensions(&self) -> Vector2<f32> {
|
||||
self.properties.borrow().dimensions
|
||||
}
|
||||
|
||||
fn dom(&self) -> HtmlElement {
|
||||
self.properties.borrow().dom.clone()
|
||||
}
|
||||
|
||||
fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
|
||||
let mut position = self.position();
|
||||
f(&mut position);
|
||||
self.properties.borrow().display_object.set_position(position);
|
||||
}
|
||||
|
||||
fn mod_scale<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
|
||||
self.properties.borrow().display_object.mod_scale(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Css3dObject ===
|
||||
// ===================
|
||||
|
||||
/// A structure for representing a HtmlElement in the 3d world.
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Css3dObject {
|
||||
pub(super) data : Css3dObjectData
|
||||
}
|
||||
|
||||
impl Css3dObject {
|
||||
/// Creates a Css3dObject from element name.
|
||||
pub(super) fn new<L:Into<Logger>,S:Str>(logger:L, dom_name:S) -> Result<Self> {
|
||||
let dom = dyn_into(create_element(dom_name.as_ref())?)?;
|
||||
Ok(Self::from_element(logger,dom))
|
||||
}
|
||||
|
||||
/// Creates a Css3dObject from a web_sys::HtmlElement.
|
||||
pub(super) fn from_element<L:Into<Logger>>(logger:L, element:HtmlElement) -> Self {
|
||||
let logger = logger.into();
|
||||
element.set_style_or_warn("position", "absolute", &logger);
|
||||
element.set_style_or_warn("width" , "0px" , &logger);
|
||||
element.set_style_or_warn("height" , "0px" , &logger);
|
||||
let dom = element;
|
||||
let display_object = DisplayObjectData::new(logger);
|
||||
let dimensions = Vector2::new(0.0, 0.0);
|
||||
let css3d_order = default();
|
||||
let data = Css3dObjectData::new(
|
||||
display_object,
|
||||
dom,
|
||||
dimensions,
|
||||
css3d_order
|
||||
);
|
||||
Self {data}
|
||||
}
|
||||
|
||||
/// Creates a Css3dObject from a HTML string.
|
||||
pub(super) fn from_html_string<L:Into<Logger>,T:Str>(logger:L, html_string:T) -> Result<Self> {
|
||||
let element = create_element("div")?;
|
||||
element.set_inner_html(html_string.as_ref());
|
||||
match element.first_element_child() {
|
||||
Some(element) => {
|
||||
let element = dyn_into(element)?;
|
||||
Ok(Self::from_element(logger,element))
|
||||
},
|
||||
None => Err(Error::missing("valid HTML")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets Css3dOrder.
|
||||
pub fn set_css3d_order(&mut self, css3d_order: Css3dOrder) {
|
||||
self.data.set_css3d_order(css3d_order)
|
||||
}
|
||||
|
||||
/// Gets Css3dOrder.
|
||||
pub fn css3d_order(&self) -> Css3dOrder {
|
||||
self.data.css3d_order()
|
||||
}
|
||||
|
||||
/// Sets the underlying HtmlElement dimension.
|
||||
pub fn set_dimensions(&mut self, dimensions:Vector2<f32>) {
|
||||
self.data.set_dimensions(dimensions)
|
||||
}
|
||||
|
||||
/// Gets the underlying HtmlElement dimension.
|
||||
pub fn dimensions(&self) -> Vector2<f32> {
|
||||
self.data.dimensions()
|
||||
}
|
||||
|
||||
/// Gets Css3dObject's dom.
|
||||
pub fn dom(&self) -> HtmlElement {
|
||||
self.data.dom()
|
||||
}
|
||||
|
||||
/// Gets object's position.
|
||||
pub fn position(&self) -> Vector3<f32> {
|
||||
self.data.position()
|
||||
}
|
||||
|
||||
/// Modifies the position of the object.
|
||||
pub fn mod_position<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
|
||||
self.data.mod_position(f);
|
||||
}
|
||||
|
||||
/// Modifies the scale of the object.
|
||||
pub fn mod_scale<F:FnOnce(&mut Vector3<f32>)>(&self, f:F) {
|
||||
self.data.mod_scale(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Css3dObject> for DisplayObjectData {
|
||||
fn from(t:&Css3dObject) -> Self {
|
||||
t.data.properties.borrow().display_object.clone_ref()
|
||||
}
|
||||
}
|
359
gui/lib/core/src/system/web/dom/html/css3d_renderer.rs
Normal file
359
gui/lib/core/src/system/web/dom/html/css3d_renderer.rs
Normal file
@ -0,0 +1,359 @@
|
||||
//! This module contains the Css3dRenderer, a struct used to render CSS3D elements.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display::object::DisplayObjectData;
|
||||
use crate::display::camera::Camera2d;
|
||||
use crate::display::camera::camera2d::Projection;
|
||||
use crate::system::web::dom::html::{Css3dObject, Css3dSystem};
|
||||
use crate::system::gpu::data::JsBufferView;
|
||||
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::NodeRemover;
|
||||
use crate::system::web::StyleSetter;
|
||||
use crate::system::web::dom::DomContainer;
|
||||
use crate::system::web::dom::ResizeCallback;
|
||||
use crate::system::web::get_element_by_id;
|
||||
use super::css3d_object::Css3dOrder;
|
||||
|
||||
use nalgebra::Vector2;
|
||||
use nalgebra::Matrix4;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlElement;
|
||||
use js_sys::Object;
|
||||
|
||||
|
||||
// ===================
|
||||
// === Js Bindings ===
|
||||
// ===================
|
||||
|
||||
mod js {
|
||||
use super::*;
|
||||
#[wasm_bindgen(inline_js = "
|
||||
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, perspective) {
|
||||
dom.style.perspective = perspective + 'px';
|
||||
}
|
||||
|
||||
export function setup_camera_orthographic(dom, matrix_array) {
|
||||
dom.style.transform = arr_to_css_matrix3d(matrix_array);
|
||||
}
|
||||
|
||||
export function setup_camera_perspective
|
||||
(dom, near, matrix_array) {
|
||||
let translateZ = 'translateZ(' + near + 'px)';
|
||||
let matrix3d = arr_to_css_matrix3d(matrix_array);
|
||||
let transform = translateZ + matrix3d;
|
||||
dom.style.transform = transform;
|
||||
}
|
||||
")]
|
||||
extern "C" {
|
||||
/// Setup perspective CSS 3D projection on DOM.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn setup_perspective(dom: &JsValue, znear: &JsValue);
|
||||
|
||||
/// Setup Camera orthographic projection on DOM.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn setup_camera_orthographic(dom:&JsValue, matrix_array:&JsValue);
|
||||
|
||||
/// Setup Camera perspective projection on DOM.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn setup_camera_perspective(dom:&JsValue, near:&JsValue, matrix_array:&JsValue);
|
||||
|
||||
/// Sets object's CSS 3D transform.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn set_object_transform(dom:&JsValue, matrix_array:&Object);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
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.js_buffer_view();
|
||||
js::set_object_transform(&dom,&matrix_array);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn setup_camera_perspective(dom:&JsValue, near: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.js_buffer_view();
|
||||
js::setup_camera_perspective(
|
||||
&dom,
|
||||
&near.into(),
|
||||
&matrix_array
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn setup_camera_orthographic(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.js_buffer_view();
|
||||
js::setup_camera_orthographic(&dom, &matrix_array)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Utils ===
|
||||
// =============
|
||||
|
||||
/// Inverts Matrix Y coordinates. It's equivalent to scaling by (1.0, -1.0, 1.0).
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =========================
|
||||
// === Css3dRendererData ===
|
||||
// =========================
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Css3dRendererData {
|
||||
pub front_dom : HtmlElement,
|
||||
pub back_dom : HtmlElement,
|
||||
pub front_dom_view_projection : HtmlElement,
|
||||
pub back_dom_view_projection : HtmlElement,
|
||||
logger : Logger
|
||||
}
|
||||
|
||||
impl Css3dRendererData {
|
||||
pub fn new
|
||||
( front_dom : HtmlElement
|
||||
, back_dom : HtmlElement
|
||||
, front_dom_view_projection : HtmlElement
|
||||
, back_dom_view_projection : HtmlElement
|
||||
, logger : Logger) -> Self {
|
||||
Self {logger,front_dom,back_dom, front_dom_view_projection, back_dom_view_projection }
|
||||
}
|
||||
|
||||
fn set_dimensions(&self, dimensions:Vector2<f32>) {
|
||||
let width = format!("{}px", dimensions.x);
|
||||
let height = format!("{}px", dimensions.y);
|
||||
let doms = vec![&self.front_dom, &self.back_dom, &self.front_dom_view_projection, &self.back_dom_view_projection];
|
||||
for dom in doms {
|
||||
dom.set_style_or_warn("width" , &width, &self.logger);
|
||||
dom.set_style_or_warn("height", &height, &self.logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Css3dRenderer ===
|
||||
// =====================
|
||||
|
||||
/// `Css3dRenderer` is a renderer for `Css3dObject`s. It integrates with other rendering contexts,
|
||||
/// such as WebGL, by placing two HtmlElements in front and behind of the Canvas element,
|
||||
/// allowing the move `Css3dObject`s between these two layers, mimicking z-index ordering.
|
||||
///
|
||||
/// To make use of its functionalities, the API user can create a `Css3dSystem` by using
|
||||
/// the `Css3dRenderer::new_system` method which creates and manages instances of
|
||||
/// `Css3dObject`s.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Css3dRenderer {
|
||||
container : DomContainer,
|
||||
data : Rc<Css3dRendererData>
|
||||
}
|
||||
|
||||
impl Css3dRenderer {
|
||||
/// Creates a Css3dRenderer inside an element.
|
||||
pub fn from_element_or_panic(logger:&Logger, element:HtmlElement) -> Self {
|
||||
let logger = logger.sub("Css3dRenderer");
|
||||
let container = DomContainer::from_element(element);
|
||||
let front_dom = create_div();
|
||||
let back_dom = create_div();
|
||||
let front_camera_dom = create_div();
|
||||
let back_camera_dom = create_div();
|
||||
|
||||
front_dom.set_style_or_warn("position","absolute",&logger);
|
||||
front_dom.set_style_or_warn("top","0px",&logger);
|
||||
front_dom.set_style_or_warn("overflow","hidden",&logger);
|
||||
front_dom.set_style_or_warn("overflow","hidden",&logger);
|
||||
front_dom.set_style_or_warn("width","100%",&logger);
|
||||
front_dom.set_style_or_warn("height","100%",&logger);
|
||||
front_dom.set_style_or_warn("pointer-events","none",&logger);
|
||||
back_dom.set_style_or_warn("position","absolute",&logger);
|
||||
back_dom.set_style_or_warn("top","0px",&logger);
|
||||
back_dom.set_style_or_warn("overflow","hidden",&logger);
|
||||
back_dom.set_style_or_warn("overflow","hidden",&logger);
|
||||
back_dom.set_style_or_warn("width","100%",&logger);
|
||||
back_dom.set_style_or_warn("height","100%",&logger);
|
||||
back_dom.set_style_or_warn("pointer-events","none",&logger);
|
||||
back_dom.set_style_or_warn("z-index","-1",&logger);
|
||||
front_camera_dom.set_style_or_warn("width", "100%", &logger);
|
||||
front_camera_dom.set_style_or_warn("height", "100%", &logger);
|
||||
front_camera_dom.set_style_or_warn("transform-style", "preserve-3d", &logger);
|
||||
back_camera_dom.set_style_or_warn("width", "100%", &logger);
|
||||
back_camera_dom.set_style_or_warn("height", "100%", &logger);
|
||||
back_camera_dom.set_style_or_warn("transform-style", "preserve-3d", &logger);
|
||||
|
||||
container.dom.append_or_warn(&front_dom,&logger);
|
||||
container.dom.append_or_warn(&back_dom,&logger);
|
||||
front_dom.append_or_warn(&front_camera_dom, &logger);
|
||||
back_dom.append_or_warn(&back_camera_dom, &logger);
|
||||
|
||||
let data = Css3dRendererData::new(
|
||||
front_dom,
|
||||
back_dom,
|
||||
front_camera_dom,
|
||||
back_camera_dom,
|
||||
logger);
|
||||
let data = Rc::new(data);
|
||||
Self{container,data}.init()
|
||||
}
|
||||
|
||||
/// Creates a Css3dRenderer.
|
||||
pub fn new(logger:&Logger, dom_id:&str) -> Result<Self> {
|
||||
Ok(Self::from_element_or_panic(logger,dyn_into(get_element_by_id(dom_id)?)?))
|
||||
}
|
||||
|
||||
pub(super) fn new_system(&self) -> Css3dSystem {
|
||||
let css3d_renderer = self.clone();
|
||||
let logger = self.data.logger.sub("Css3dSystem");
|
||||
let display_object = DisplayObjectData::new(&logger);
|
||||
Css3dSystem {display_object,css3d_renderer,logger}
|
||||
}
|
||||
|
||||
fn init(mut self) -> Self {
|
||||
let dimensions = self.dimensions();
|
||||
self.set_dimensions(dimensions);
|
||||
let data = self.data.clone();
|
||||
self.add_resize_callback(move |dimensions:&Vector2<f32>| {
|
||||
data.set_dimensions(*dimensions);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new instance of Css3dObject and adds it to parent.
|
||||
pub(super) fn new_instance<S:Str>
|
||||
(&self, dom_name:S, parent:DisplayObjectData) -> Result<Css3dObject> {
|
||||
let front_camera = self.data.front_dom_view_projection.clone();
|
||||
let back_camera = self.data.back_dom_view_projection.clone();
|
||||
let logger = self.data.logger.sub("object");
|
||||
let object = Css3dObject::new(logger,dom_name);
|
||||
object.as_ref().map(|object| {
|
||||
parent.add_child(object);
|
||||
let display_object : DisplayObjectData = object.into();
|
||||
display_object.set_on_render(enclose!((object,display_object) move || {
|
||||
let object_dom = object.dom();
|
||||
let mut transform = display_object.matrix();
|
||||
transform.iter_mut().for_each(|a| *a = eps(*a));
|
||||
|
||||
let camera_node = match object.css3d_order() {
|
||||
Css3dOrder::Front => &front_camera,
|
||||
Css3dOrder::Back => &back_camera
|
||||
};
|
||||
|
||||
let parent_node = object.dom().parent_node();
|
||||
if !camera_node.is_same_node(parent_node.as_ref()) {
|
||||
display_object.with_logger(|logger| {
|
||||
object_dom.remove_from_parent_or_warn(logger);
|
||||
camera_node.append_or_warn(&object_dom,logger);
|
||||
});
|
||||
}
|
||||
|
||||
set_object_transform(&object_dom, &transform);
|
||||
}));
|
||||
}).ok();
|
||||
object
|
||||
}
|
||||
|
||||
/// Renders `Camera`'s point of view.
|
||||
pub fn render(&self, camera:&Camera2d) {
|
||||
let trans_cam = camera.transform().matrix().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 half_dim = self.container.dimensions() / 2.0;
|
||||
let fovy_slope = camera.half_fovy_slope();
|
||||
let near = half_dim.y / fovy_slope;
|
||||
|
||||
match camera.projection() {
|
||||
Projection::Perspective{..} => {
|
||||
js::setup_perspective(&self.data.front_dom, &near.into());
|
||||
js::setup_perspective(&self.data.back_dom, &near.into());
|
||||
setup_camera_perspective(&self.data.front_dom_view_projection, near, &trans_cam);
|
||||
setup_camera_perspective(&self.data.back_dom_view_projection, near, &trans_cam);
|
||||
},
|
||||
Projection::Orthographic => {
|
||||
setup_camera_orthographic(&self.data.front_dom_view_projection, &trans_cam);
|
||||
setup_camera_orthographic(&self.data.back_dom_view_projection, &trans_cam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a ResizeCallback.
|
||||
pub fn add_resize_callback<T:ResizeCallback>(&mut self, callback:T) {
|
||||
self.container.add_resize_callback(callback);
|
||||
}
|
||||
|
||||
/// Sets Css3dRenderer's container dimensions.
|
||||
pub fn set_dimensions(&mut self, dimensions:Vector2<f32>) {
|
||||
self.data.set_dimensions(dimensions);
|
||||
self.container.set_dimensions(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Getters ===
|
||||
|
||||
impl Css3dRenderer {
|
||||
/// Gets Css3dRenderer's container.
|
||||
pub fn container(&self) -> &DomContainer {
|
||||
&self.container
|
||||
}
|
||||
|
||||
/// Gets Css3dRenderer's DOM.
|
||||
pub fn dom(&self) -> &HtmlElement {
|
||||
&self.data.front_dom
|
||||
}
|
||||
|
||||
/// Gets the Css3dRenderer's dimensions.
|
||||
pub fn dimensions(&self) -> Vector2<f32> {
|
||||
self.container.dimensions()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Utils ===
|
||||
// =============
|
||||
|
||||
fn create_div() -> HtmlElement {
|
||||
let element = create_element("div").expect("Couldn't create element");
|
||||
dyn_into(element).expect("Couldn't cast to HtmlElement")
|
||||
}
|
||||
|
||||
/// eps is used to round very small values to 0.0 for numerical stability
|
||||
pub fn eps(value: f32) -> f32 {
|
||||
if value.abs() < 1e-10 { 0.0 } else { value }
|
||||
}
|
37
gui/lib/core/src/system/web/dom/html/css3d_system.rs
Normal file
37
gui/lib/core/src/system/web/dom/html/css3d_system.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Css3dObject;
|
||||
use crate::display::object::DisplayObjectData;
|
||||
use crate::display::world::World;
|
||||
use crate::system::web::Result;
|
||||
use crate::system::web::dom::html::Css3dRenderer;
|
||||
|
||||
use logger::Logger;
|
||||
|
||||
/// Css3dSystem enables us to create instances of HtmlElement objects in the 3d world.
|
||||
#[derive(Debug)]
|
||||
pub struct Css3dSystem {
|
||||
pub(super) display_object : DisplayObjectData,
|
||||
pub(super) css3d_renderer : Css3dRenderer,
|
||||
pub(super) logger : Logger
|
||||
}
|
||||
|
||||
impl Css3dSystem {
|
||||
/// Creates a new instance of Css3dSystem.
|
||||
pub fn new(world:&World) -> Self {
|
||||
let scene = world.scene();
|
||||
let css3d_renderer = scene.css3d_renderer();
|
||||
css3d_renderer.new_system()
|
||||
}
|
||||
|
||||
/// Creates a new instance of Css3dObject.
|
||||
pub fn new_instance<S:Str>(&self, dom_name:S) -> Result<Css3dObject> {
|
||||
self.css3d_renderer.new_instance(dom_name, self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Css3dSystem> for DisplayObjectData {
|
||||
fn from(t:&Css3dSystem) -> Self {
|
||||
t.display_object.clone_ref()
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
//! This module contains the implementation of HTMLObject, a struct used to represent CSS3D
|
||||
//! elements.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display::object::DisplayObjectData;
|
||||
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;
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === HtmlObject ===
|
||||
// ==================
|
||||
|
||||
/// A structure for representing a 3D HTMLElement in a `HTMLScene`.
|
||||
#[derive(Shrinkwrap, Debug, Clone)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct HtmlObject {
|
||||
#[shrinkwrap(main_field)]
|
||||
/// HTMLObject's hierarchical transforms.
|
||||
pub display_object : DisplayObjectData,
|
||||
|
||||
/// The DOM to be rendered with CSS3D.
|
||||
pub dom : HtmlElement,
|
||||
|
||||
dimensions : Vector2<f32>,
|
||||
}
|
||||
|
||||
impl HtmlObject {
|
||||
/// Creates a HTMLObject from element name.
|
||||
pub fn new<L:Into<Logger>>(logger:L, dom_name:&str) -> Result<Self> {
|
||||
let dom = dyn_into(create_element(dom_name)?)?;
|
||||
Ok(Self::from_element(logger,dom))
|
||||
}
|
||||
|
||||
/// Creates a HTMLObject from a web_sys::HtmlElement.
|
||||
pub fn from_element<L:Into<Logger>>(logger:L, element:HtmlElement) -> Self {
|
||||
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 display_object = DisplayObjectData::new(logger.into());
|
||||
let dimensions = Vector2::new(0.0, 0.0);
|
||||
Self {display_object,dom,dimensions}
|
||||
}
|
||||
|
||||
/// Creates a HTMLObject from a HTML string.
|
||||
pub fn from_html_string<L:Into<Logger>, T:AsRef<str>>(logger:L, html_string:T) -> Result<Self> {
|
||||
let element = create_element("div")?;
|
||||
element.set_inner_html(html_string.as_ref());
|
||||
match element.first_element_child() {
|
||||
Some(element) => Ok(Self::from_element(logger,dyn_into(element)?)),
|
||||
None => Err(Error::missing("valid HTML")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the underlying HtmlElement dimension.
|
||||
pub fn set_dimensions(&mut self, width: f32, height: f32) {
|
||||
self.dimensions = Vector2::new(width, 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 dimensions(&self) -> &Vector2<f32> {
|
||||
&self.dimensions
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
//! This module contains the HTMLRenderer, a struct used to render CSS3D elements.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::display::camera::Camera2d;
|
||||
use crate::display::camera::camera2d::Projection;
|
||||
use crate::system::web::dom::html::HtmlScene;
|
||||
use crate::system::gpu::data::JsBufferView;
|
||||
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 crate::system::web::dom::DomContainer;
|
||||
use crate::system::web::dom::ResizeCallback;
|
||||
|
||||
use js_sys::Object;
|
||||
use nalgebra::Vector2;
|
||||
use nalgebra::Matrix4;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Js Bindings ===
|
||||
// ===================
|
||||
|
||||
mod js {
|
||||
use super::*;
|
||||
#[wasm_bindgen(module = "/src/system/web/dom/html/snippets.js")]
|
||||
extern "C" {
|
||||
#[allow(unsafe_code)]
|
||||
pub fn set_object_transform(dom:&JsValue, matrix_array:&Object);
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn setup_perspective(dom: &JsValue, znear: &JsValue);
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn setup_camera_orthographic(dom:&JsValue, matrix_array:&JsValue);
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn setup_camera_perspective
|
||||
( dom : &JsValue
|
||||
, near : &JsValue
|
||||
, matrix_array : &JsValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// eps is used to round very small values to 0.0 for numerical stability
|
||||
pub fn eps(value: f32) -> f32 {
|
||||
if value.abs() < 1e-10 { 0.0 } else { value }
|
||||
}
|
||||
|
||||
/// Inverts Matrix Y coordinates.
|
||||
/// It's equivalent to scaling by (1.0, -1.0, 1.0).
|
||||
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
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
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.js_buffer_view();
|
||||
js::set_object_transform(&dom, &matrix_array);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn setup_camera_perspective
|
||||
(dom:&JsValue, near: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.js_buffer_view();
|
||||
js::setup_camera_perspective(
|
||||
&dom,
|
||||
&near.into(),
|
||||
&matrix_array
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn setup_camera_orthographic(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.js_buffer_view();
|
||||
js::setup_camera_orthographic(&dom, &matrix_array)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === HTMLRendererData ===
|
||||
// ========================
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HTMLRendererData {
|
||||
pub dom : HtmlElement,
|
||||
pub camera : HtmlElement
|
||||
}
|
||||
|
||||
impl HTMLRendererData {
|
||||
pub fn new(dom:HtmlElement, camera:HtmlElement) -> Self {
|
||||
Self {dom,camera}
|
||||
}
|
||||
|
||||
pub fn set_dimensions(&self, dimensions : Vector2<f32>) {
|
||||
let width = format!("{}px", dimensions.x);
|
||||
let height = format!("{}px", dimensions.y);
|
||||
self.dom.set_property_or_panic("width", &width);
|
||||
self.dom.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(Debug)]
|
||||
pub struct HtmlRenderer {
|
||||
container : DomContainer,
|
||||
data : Rc<HTMLRendererData>
|
||||
}
|
||||
|
||||
impl HtmlRenderer {
|
||||
/// Creates a HTMLRenderer.
|
||||
pub fn new(dom_id: &str) -> Result<Self> {
|
||||
let container = DomContainer::from_id(dom_id)?;
|
||||
let dom: HtmlElement = dyn_into(create_element("div")?)?;
|
||||
let camera : HtmlElement = dyn_into(create_element("div")?)?;
|
||||
|
||||
dom.set_property_or_panic("position", "absolute");
|
||||
dom.set_property_or_panic("top" , "0px");
|
||||
dom.set_property_or_panic("overflow", "hidden");
|
||||
dom.set_property_or_panic("overflow", "hidden");
|
||||
dom.set_property_or_panic("width" , "100%");
|
||||
dom.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");
|
||||
|
||||
container.dom.append_or_panic(&dom);
|
||||
dom.append_or_panic(&camera);
|
||||
|
||||
let data = Rc::new(HTMLRendererData::new(dom,camera));
|
||||
let mut htmlrenderer = Self {container,data};
|
||||
|
||||
htmlrenderer.init_listeners();
|
||||
Ok(htmlrenderer)
|
||||
}
|
||||
|
||||
fn init_listeners(&mut self) {
|
||||
let dimensions = self.dimensions();
|
||||
let data = self.data.clone();
|
||||
self.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 Camera2d, scene: &HtmlScene) {
|
||||
camera.update();
|
||||
let trans_cam = camera.transform().matrix().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 half_dim = self.container.dimensions() / 2.0;
|
||||
let fovy_slope = camera.half_fovy_slope();
|
||||
let near = half_dim.y / fovy_slope;
|
||||
|
||||
match camera.projection() {
|
||||
Projection::Perspective{..} => {
|
||||
js::setup_perspective(&self.data.dom, &near.into());
|
||||
setup_camera_perspective(
|
||||
&self.data.camera,
|
||||
near,
|
||||
&trans_cam
|
||||
);
|
||||
},
|
||||
Projection::Orthographic => {
|
||||
setup_camera_orthographic(&self.data.camera, &trans_cam);
|
||||
}
|
||||
}
|
||||
|
||||
let scene : &HtmlScene = &scene;
|
||||
for object in &mut scene.into_iter() {
|
||||
object.update();
|
||||
let mut transform = object.matrix();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a ResizeCallback.
|
||||
pub fn add_resize_callback<T:ResizeCallback>(&mut self, callback : T) {
|
||||
self.container.add_resize_callback(callback);
|
||||
}
|
||||
|
||||
/// Sets HTMLRenderer's container dimensions.
|
||||
pub fn set_dimensions(&mut self, dimensions : Vector2<f32>) {
|
||||
self.container.set_dimensions(dimensions);
|
||||
self.data.set_dimensions(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Getters ===
|
||||
|
||||
impl HtmlRenderer {
|
||||
/// Gets HTMLRenderer's container.
|
||||
pub fn container(&self) -> &DomContainer {
|
||||
&self.container
|
||||
}
|
||||
|
||||
/// Gets HTMLRenderer's DOM.
|
||||
pub fn dom(&self) -> &HtmlElement {
|
||||
&self.data.dom
|
||||
}
|
||||
|
||||
/// Gets the Scene Renderer's dimensions.
|
||||
pub fn dimensions(&self) -> Vector2<f32> {
|
||||
self.container.dimensions()
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
//! This module contains `HTMLScene`, a struct to hold `HTMLObject`s.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::HtmlObject;
|
||||
use crate::display::object::DisplayObjectData;
|
||||
use data::opt_vec::*;
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === HtmlScene ===
|
||||
// =================
|
||||
|
||||
/// A collection for holding 3D `Object`s.
|
||||
#[derive(Debug)]
|
||||
pub struct HtmlScene {
|
||||
display_object : DisplayObjectData,
|
||||
objects : OptVec<HtmlObject>
|
||||
}
|
||||
|
||||
impl HtmlScene {
|
||||
/// Searches for a HtmlElement identified by id and appends to it.
|
||||
pub fn new<L:Into<Logger>>(logger:L) -> Self {
|
||||
let display_object = DisplayObjectData::new(logger.into());
|
||||
let objects = default();
|
||||
Self{display_object,objects}
|
||||
}
|
||||
|
||||
/// Moves a HTMLObject to the Scene and returns an index to it.
|
||||
pub fn add_child(&mut self, object: HtmlObject) -> Ix {
|
||||
self.display_object.add_child(&object.display_object);
|
||||
self.objects.insert(object)
|
||||
}
|
||||
|
||||
/// Removes and retrieves a HTMLObject based on the index provided by
|
||||
pub fn remove_child(&mut self, index: Ix) -> Option<HtmlObject> {
|
||||
let object = self.objects.remove(index);
|
||||
if let Some(object) = &object {
|
||||
self.display_object.remove_child(&object.display_object);
|
||||
}
|
||||
object
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Gets mutable iterator.
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_, HtmlObject> { self.objects.iter_mut() }
|
||||
|
||||
/// Gets iterator.
|
||||
pub fn iter(&self) -> Iter<'_, HtmlObject> { self.objects.iter() }
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a HtmlScene {
|
||||
type Item = &'a HtmlObject;
|
||||
type IntoIter = Iter<'a, HtmlObject>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut HtmlScene {
|
||||
type Item = &'a mut HtmlObject;
|
||||
type IntoIter = IterMut<'a, HtmlObject>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
function arr_to_css_matrix3d(a) {
|
||||
return "matrix3d(" + a.join(',') + ")"
|
||||
}
|
||||
|
||||
// Sets object's CSS 3D transform.
|
||||
export function set_object_transform(dom, matrix_array) {
|
||||
let css = arr_to_css_matrix3d(matrix_array);
|
||||
dom.style.transform = "translate(-50%, -50%)" + css;
|
||||
}
|
||||
|
||||
// Setup perspective CSS 3D projection on DOM.
|
||||
export function setup_perspective(dom, perspective) {
|
||||
dom.style.perspective = perspective + "px";
|
||||
}
|
||||
|
||||
// Setup Camera orthographic projection on DOM.
|
||||
export function setup_camera_orthographic(dom, matrix_array) {
|
||||
dom.style.transform = arr_to_css_matrix3d(matrix_array);
|
||||
}
|
||||
|
||||
// Setup Camera perspective projection on DOM.
|
||||
export function setup_camera_perspective
|
||||
(dom, near, matrix_array) {
|
||||
let translateZ = "translateZ(" + near + "px)";
|
||||
let matrix3d = arr_to_css_matrix3d(matrix_array);
|
||||
let transform = translateZ + matrix3d;
|
||||
dom.style.transform = transform;
|
||||
}
|
171
gui/lib/core/tests/css3d_system.rs
Normal file
171
gui/lib/core/tests/css3d_system.rs
Normal file
@ -0,0 +1,171 @@
|
||||
//! 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::system::web::dom::html::Css3dObject;
|
||||
use basegl::system::web::dom::html::Css3dSystem;
|
||||
use basegl::system::web::dom::html::Css3dRenderer;
|
||||
use basegl::system::web::StyleSetter;
|
||||
use basegl::system::web::get_performance;
|
||||
use web_test::*;
|
||||
use web_sys::Performance;
|
||||
use nalgebra::Vector3;
|
||||
use logger::Logger;
|
||||
use basegl::system::web;
|
||||
use basegl::display::world::{WorldData, World};
|
||||
use basegl::display::navigation::navigator::Navigator;
|
||||
use basegl::display::object::DisplayObjectOps;
|
||||
use basegl::display::object::DisplayObject;
|
||||
use nalgebra::Vector2;
|
||||
use basegl::system::web::AttributeSetter;
|
||||
use basegl::system::web::NodeInserter;
|
||||
use basegl::system::web::dyn_into;
|
||||
use basegl::system::web::get_element_by_id;
|
||||
use basegl::system::web::create_element;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
#[web_test(no_container)]
|
||||
fn invalid_container() {
|
||||
let logger = Logger::new("invalid_container");
|
||||
let renderer = Css3dRenderer::new(&logger, "nonexistent_id");
|
||||
assert!(renderer.is_err(),"Tried to attach to a non-existent HtmlElement and succeeded.");
|
||||
}
|
||||
|
||||
fn initialize_system(name:&str,color:&str) -> (World,Css3dSystem) {
|
||||
web::set_stdout();
|
||||
let canvas_name = format!("canvas_{}",name);
|
||||
let container = dyn_into::<_,HtmlElement>(get_element_by_id(name).unwrap()).unwrap();
|
||||
let canvas = dyn_into::<_,HtmlElement>(create_element("canvas").unwrap()).unwrap();
|
||||
canvas.set_attribute_or_panic("id", &canvas_name);
|
||||
canvas.set_style_or_panic("width", "100%");
|
||||
canvas.set_style_or_panic("height", "100%");
|
||||
container.append_or_panic(&canvas);
|
||||
let world = WorldData::new(&canvas_name);
|
||||
let css3d_system = Css3dSystem::new(&world);
|
||||
container.set_style_or_panic("background-color", color);
|
||||
world.add_child(&css3d_system);
|
||||
(world,css3d_system)
|
||||
}
|
||||
|
||||
fn create_scene(system:&Css3dSystem) -> Vec<Css3dObject> {
|
||||
let mut objects = Vec::new();
|
||||
// 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 = system.new_instance("div").unwrap();
|
||||
object.set_dimensions(Vector2::new(10.0, 10.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;
|
||||
let factor = 120.0 / 9.0;
|
||||
let position = Vector3::new(x * factor + 160.0, y * factor + 120.0, z * factor);
|
||||
object.mod_position(|t| *t = position);
|
||||
|
||||
// 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_style_or_panic("background-color", color);
|
||||
objects.push(object);
|
||||
}
|
||||
}
|
||||
objects
|
||||
}
|
||||
|
||||
#[web_test]
|
||||
fn rhs_coordinates() {
|
||||
let (world,css3d_system) = initialize_system("rhs_coordinates", "black");
|
||||
let scene = world.scene();
|
||||
let camera = scene.camera();
|
||||
let navigator = Navigator::new(&scene, &camera).expect("Couldn't create navigator");
|
||||
|
||||
let scene = create_scene(&css3d_system);
|
||||
|
||||
world.display_object().update();
|
||||
|
||||
world.on_frame(move |_| {
|
||||
let _keep_alive = &scene;
|
||||
let _keep_alive = &css3d_system;
|
||||
let _keep_alive = &navigator;
|
||||
}).forget();
|
||||
std::mem::forget(world);
|
||||
}
|
||||
|
||||
fn make_sphere(mut scene : &mut Vec<Css3dObject>, performance : &Performance) {
|
||||
use super::set_gradient_bg;
|
||||
|
||||
let t = (performance.now() / 1000.0) as f32;
|
||||
let length = scene.len() as f32;
|
||||
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;
|
||||
let x = x * 10.0;
|
||||
let y = y * 10.0;
|
||||
let z = z * 10.0;
|
||||
object.mod_position(|t| *t = Vector3::new(x, y, z));
|
||||
|
||||
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_x400_update(b: &mut Bencher) {
|
||||
let (world,css3d_system) = initialize_system("object_x400_update", "black");
|
||||
let scene = world.scene();
|
||||
let camera = scene.camera();
|
||||
let navigator = Navigator::new(&scene, &camera).expect("Couldn't create navigator");
|
||||
|
||||
let mut objects = Vec::new();
|
||||
for _ in 0..400 {
|
||||
let mut object = css3d_system.new_instance("div").expect("Failed to create object");
|
||||
object.set_dimensions(Vector2::new(1.0, 1.0));
|
||||
object.mod_scale(|t| *t = Vector3::new(0.5, 0.5, 0.5));
|
||||
objects.push(object);
|
||||
}
|
||||
|
||||
let performance = get_performance().expect("Couldn't get performance obj");
|
||||
b.iter(move || {
|
||||
let _keep_alive = &navigator;
|
||||
let _keep_alive = &css3d_system;
|
||||
make_sphere(&mut objects, &performance);
|
||||
world.display_object().set_scale(Vector3::new(5.0, 5.0, 5.0));
|
||||
world.display_object().set_position(Vector3::new(160.0, 120.0, 0.0));
|
||||
world.display_object().update();
|
||||
})
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
//! 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::camera::Camera2d;
|
||||
use basegl::system::web::dom::html::HtmlScene;
|
||||
use basegl::system::web::dom::html::HtmlObject;
|
||||
use basegl::system::web::dom::html::HtmlRenderer;
|
||||
use basegl::system::web::StyleSetter;
|
||||
use basegl::system::web::get_performance;
|
||||
use web_test::*;
|
||||
use web_sys::Performance;
|
||||
use nalgebra::Vector3;
|
||||
use logger::Logger;
|
||||
|
||||
#[web_test(no_container)]
|
||||
fn invalid_container() {
|
||||
let renderer = HtmlRenderer::new("nonexistent_id");
|
||||
assert!(renderer.is_err(), "nonexistent_id should not exist");
|
||||
}
|
||||
|
||||
fn create_scene(logger:&Logger, renderer:&HtmlRenderer) -> HtmlScene {
|
||||
let mut scene: HtmlScene = HtmlScene::new(logger);
|
||||
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(logger, "div").unwrap();
|
||||
object.set_dimensions(10.0, 10.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;
|
||||
let factor = 120.0 / 9.0;
|
||||
let position = Vector3::new(x * factor + 160.0, y * factor + 120.0, z * factor);
|
||||
object.set_position(position);
|
||||
|
||||
// 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_child(object);
|
||||
}
|
||||
}
|
||||
assert_eq!(scene.len(), 30, "We should have 30 HTMLObjects");
|
||||
scene
|
||||
}
|
||||
|
||||
#[web_test]
|
||||
fn rhs_coordinates() {
|
||||
let logger = Logger::new("rhs_coordinates");
|
||||
let renderer = HtmlRenderer::new("rhs_coordinates")
|
||||
.expect("Renderer couldn't be created");
|
||||
let scene = create_scene(&logger, &renderer);
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
}
|
||||
|
||||
#[web_bench]
|
||||
fn camera_movement(b: &mut Bencher) {
|
||||
let logger = Logger::new("camera_movement");
|
||||
let renderer = HtmlRenderer::new("camera_movement")
|
||||
.expect("Renderer couldn't be created");
|
||||
let scene = create_scene(&logger, &renderer);
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
|
||||
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(Vector3::new(t.sin() * 50.0, t.cos() * 50.0, 200.0));
|
||||
|
||||
renderer.render(&mut camera, &scene);
|
||||
})
|
||||
}
|
||||
|
||||
fn make_sphere(mut scene : &mut HtmlScene, 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 HtmlScene = &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;
|
||||
let x = x * 5.0 + 160.0;
|
||||
let y = y * 5.0 + 120.0;
|
||||
let z = z * 5.0;
|
||||
object.set_position(Vector3::new(x, y, z));
|
||||
|
||||
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_x400_update(b: &mut Bencher) {
|
||||
let logger = Logger::new("object_x400_update");
|
||||
let renderer = HtmlRenderer::new("object_x400_update")
|
||||
.expect("Renderer couldn't be created");
|
||||
let mut scene = HtmlScene::new(&logger);
|
||||
renderer.container().dom.set_property_or_panic("background-color", "black");
|
||||
|
||||
for _ in 0..400 {
|
||||
let mut object = HtmlObject::new(&logger, "div")
|
||||
.expect("Failed to create object");
|
||||
object.set_dimensions(1.0, 1.0);
|
||||
object.set_scale(Vector3::new(0.5, 0.5, 0.5));
|
||||
scene.add_child(object);
|
||||
}
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
|
||||
let performance = get_performance()
|
||||
.expect("Couldn't get performance obj");
|
||||
|
||||
// We move the Camera 29 units away from the center.
|
||||
camera.set_position(Vector3::new(0.0, 0.0, 29.0));
|
||||
|
||||
b.iter(move || {
|
||||
make_sphere(&mut scene, &performance);
|
||||
renderer.render(&mut camera, &scene);
|
||||
})
|
||||
}
|
||||
}
|
@ -13,37 +13,44 @@ mod tests {
|
||||
use basegl::animation::physics::inertia::PhysicsSimulator;
|
||||
use basegl::animation::physics::inertia::PhysicsProperties;
|
||||
use basegl::animation::animator::fixed_step::FixedStepAnimator;
|
||||
use basegl::system::web::dom::html::HtmlRenderer;
|
||||
use basegl::system::web::dom::html::HtmlObject;
|
||||
use basegl::system::web::dom::html::HtmlScene;
|
||||
use basegl::display::camera::Camera2d;
|
||||
use basegl::system::web::dom::html::Css3dSystem;
|
||||
use web_test::*;
|
||||
use nalgebra::{zero, Vector3};
|
||||
use js_sys::Math::random;
|
||||
use logger::Logger;
|
||||
use basegl::display::world::WorldData;
|
||||
use basegl::display::object::DisplayObjectOps;
|
||||
use basegl::system::web::dyn_into;
|
||||
use basegl::system::web::get_element_by_id;
|
||||
use basegl::system::web::create_element;
|
||||
use web_sys::HtmlElement;
|
||||
use nalgebra::Vector2;
|
||||
use basegl::system::web::NodeInserter;
|
||||
use basegl::system::web::AttributeSetter;
|
||||
use basegl::system::web::set_stdout;
|
||||
use basegl::display::object::DisplayObject;
|
||||
|
||||
#[web_bench]
|
||||
fn simulator(b : &mut Bencher) {
|
||||
let renderer = HtmlRenderer::new("simulator").expect("Renderer couldn't be created");
|
||||
renderer.container().dom.set_property_or_panic("background-color", "black");
|
||||
#[web_test]
|
||||
fn simulator() {
|
||||
set_stdout();
|
||||
let name = "simulator";
|
||||
let canvas_name = format!("canvas_{}",name);
|
||||
let container = dyn_into::<_,HtmlElement>(get_element_by_id(name).unwrap()).unwrap();
|
||||
let canvas = create_element("canvas").unwrap();
|
||||
canvas.set_attribute_or_panic("id", &canvas_name);
|
||||
container.append_or_panic(&canvas);
|
||||
let world = WorldData::new(&canvas_name);
|
||||
let css3d_system = Css3dSystem::new(&world);
|
||||
world.add_child(&css3d_system);
|
||||
|
||||
let logger = Logger::new("simulator");
|
||||
let mut scene = HtmlScene::new(&logger);
|
||||
container.set_style_or_panic("background-color", "black");
|
||||
|
||||
let mut target = HtmlObject::new(&logger, "div").unwrap();
|
||||
target.set_dimensions(10.0, 10.0);
|
||||
target.dom.set_property_or_panic("background-color", "green");
|
||||
scene.add_child(target.clone());
|
||||
let mut target = css3d_system.new_instance("div").unwrap();
|
||||
target.set_dimensions(Vector2::new(10.0, 10.0));
|
||||
target.dom().set_style_or_panic("background-color", "green");
|
||||
|
||||
let mut object = HtmlObject::new(&logger, "div").unwrap();
|
||||
object.set_dimensions(10.0, 10.0);
|
||||
object.dom.set_property_or_panic("background-color", "red");
|
||||
scene.add_child(object.clone());
|
||||
|
||||
let view_dim = renderer.dimensions();
|
||||
assert_eq!((view_dim.x, view_dim.y), (320.0, 240.0));
|
||||
|
||||
let mut camera = Camera2d::new(logger,view_dim.x,view_dim.y);
|
||||
let mut object = css3d_system.new_instance("div").unwrap();
|
||||
object.set_dimensions(Vector2::new(10.0, 10.0));
|
||||
object.dom().set_style_or_panic("background-color", "red");
|
||||
|
||||
let mass = 2.0;
|
||||
let position = object.position();
|
||||
@ -58,25 +65,25 @@ mod tests {
|
||||
steps_per_second,
|
||||
properties.clone(),
|
||||
move |position| {
|
||||
object.set_position(position);
|
||||
object.mod_position(|t| *t = position);
|
||||
world.display_object().update();
|
||||
}
|
||||
);
|
||||
|
||||
// Updates spring's fixed point every two seconds.
|
||||
let every = 2.0;
|
||||
let animator = FixedStepAnimator::new(1.0 / every, move |_| {
|
||||
let every = 2.0;
|
||||
let animator = FixedStepAnimator::new(1.0 / every, move |_| {
|
||||
let _keep_alive = &simulator;
|
||||
let _keep_alive = &css3d_system;
|
||||
|
||||
let x = 320.0 * random() as f32;
|
||||
let y = 240.0 * random() as f32;
|
||||
let z = 0.0;
|
||||
let position = Vector3::new(x, y, z);
|
||||
properties.mod_spring(|spring| spring.fixed_point = position);
|
||||
target.set_position(position);
|
||||
target.mod_position(|t| *t = position);
|
||||
});
|
||||
|
||||
b.iter(move || {
|
||||
let _keep_alive = &simulator;
|
||||
let _keep_alive = &animator;
|
||||
renderer.render(&mut camera, &scene);
|
||||
});
|
||||
std::mem::forget(animator);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ default = []
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = { version = "^0.2", features = ["nightly"] }
|
||||
enso-prelude = { version = "0.1.0" , path = "../prelude" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.4"
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
use std::fmt::Debug;
|
||||
use wasm_bindgen::JsValue;
|
||||
use enso_prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::console;
|
||||
@ -26,7 +27,7 @@ impl LogMsg for &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fn() -> S, S: AsRef<str>> LogMsg for F {
|
||||
impl<F: Fn() -> S, S:Str> LogMsg for F {
|
||||
fn with_log_msg<G: FnOnce(&str) -> T, T>(&self, f:G) -> T {
|
||||
f(self().as_ref())
|
||||
}
|
||||
@ -44,12 +45,12 @@ pub struct Logger {
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Logger {
|
||||
pub fn new<T: AsRef<str>>(path:T) -> Self {
|
||||
pub fn new<T:Str>(path:T) -> Self {
|
||||
let path = path.as_ref().to_string();
|
||||
Self {path}
|
||||
}
|
||||
|
||||
pub fn sub<T: AsRef<str>>(&self, path: T) -> Self {
|
||||
pub fn sub<T:Str>(&self, path: T) -> Self {
|
||||
Self::new(format!("{}.{}", self.path, path.as_ref()))
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ default = ["console_error_panic_hook"]
|
||||
[dependencies]
|
||||
data = { version = "0.1.0" , path = "../../data" }
|
||||
enso-prelude = { version = "0.1.0" , path = "../../prelude" }
|
||||
logger = { version = "0.1.0" , path = "../../logger" }
|
||||
js-sys = { version = "0.3.28" }
|
||||
wasm-bindgen = { version = "^0.2" , features = ["nightly"] }
|
||||
failure = { version = "0.1.5" }
|
||||
|
@ -141,8 +141,8 @@ impl DomContainer {
|
||||
|
||||
/// 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.dom.set_style_or_panic("width" , format!("{}px", dimensions.x));
|
||||
self.dom.set_style_or_panic("height", format!("{}px", dimensions.y));
|
||||
self.data.set_dimensions(dimensions);
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ use web_sys::MouseEvent;
|
||||
use web_sys::EventTarget;
|
||||
use js_sys::Function;
|
||||
use std::fmt::Debug;
|
||||
use logger::Logger;
|
||||
|
||||
pub use web_sys::console;
|
||||
use wasm_bindgen::prelude::*;
|
||||
@ -162,15 +163,11 @@ pub fn get_performance() -> Result<Performance> {
|
||||
|
||||
/// Trait used to set HtmlElement attributes.
|
||||
pub trait AttributeSetter {
|
||||
fn set_attribute_or_panic<T, U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str>;
|
||||
fn set_attribute_or_panic<T:Str,U:Str>(&self, name:T, value:U);
|
||||
}
|
||||
|
||||
impl AttributeSetter for web_sys::HtmlElement {
|
||||
fn set_attribute_or_panic<T,U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str> {
|
||||
impl AttributeSetter for web_sys::Element {
|
||||
fn set_attribute_or_panic<T:Str,U:Str>(&self, name:T, value:U) {
|
||||
let name = name.as_ref();
|
||||
let value = value.as_ref();
|
||||
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
|
||||
@ -181,28 +178,43 @@ impl AttributeSetter for web_sys::HtmlElement {
|
||||
|
||||
/// Trait used to set css styles.
|
||||
pub trait StyleSetter {
|
||||
fn set_property_or_panic<T,U>(&self, name:T, value:U)
|
||||
where T : AsRef<str>,
|
||||
U : AsRef<str>;
|
||||
fn set_style_or_panic<T:Str,U:Str>(&self, name:T, value:U);
|
||||
fn set_style_or_warn<T:Str,U:Str>(&self, name:T, value:U, logger:&Logger);
|
||||
}
|
||||
|
||||
impl StyleSetter for web_sys::HtmlElement {
|
||||
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);
|
||||
fn set_style_or_panic<T:Str,U:Str>(&self, name:T, value:U) {
|
||||
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);
|
||||
}
|
||||
|
||||
fn set_style_or_warn<T:Str,U:Str>(&self, name:T, value:U, logger:&Logger) {
|
||||
let name = name.as_ref();
|
||||
let value = value.as_ref();
|
||||
let values = format!("\"{}\" = \"{}\" on \"{:?}\"",name,value,self);
|
||||
let warn_msg : &str = &format!("Failed to set style {}",values);
|
||||
if self.style().set_property(name, value).is_err() {
|
||||
logger.warning(warn_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait used to insert `Node`s.
|
||||
pub trait NodeInserter {
|
||||
fn append_or_panic (&self, node:&Node);
|
||||
fn append_or_panic(&self, node:&Node);
|
||||
|
||||
fn append_or_warn(&self, node:&Node, logger:&Logger);
|
||||
|
||||
fn prepend_or_panic(&self, node:&Node);
|
||||
|
||||
fn prepend_or_warn(&self, node:&Node, logger:&Logger);
|
||||
|
||||
fn insert_before_or_panic(&self,node:&Node,reference_node:&Node);
|
||||
|
||||
fn insert_before_or_warn(&self,node:&Node,reference_node:&Node, logger:&Logger);
|
||||
}
|
||||
|
||||
impl NodeInserter for Node {
|
||||
@ -212,34 +224,80 @@ impl NodeInserter for Node {
|
||||
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);
|
||||
fn append_or_warn(&self, node:&Node, logger:&Logger) {
|
||||
let warn_msg : &str = &format!("Failed to append child {:?} to {:?}",node,self);
|
||||
if self.append_child(node).is_err() {
|
||||
logger.warning(warn_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 prepend_or_warn(&self, node:&Node, logger:&Logger) {
|
||||
let warn_msg : &str = &format!("Failed to prepend child \"{:?}\" to \"{:?}\"",node,self);
|
||||
let first_c = self.first_child();
|
||||
if self.insert_before(node, first_c.as_ref()).is_err() {
|
||||
logger.warning(warn_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);
|
||||
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);
|
||||
}
|
||||
|
||||
fn insert_before_or_warn(&self, node:&Node, ref_node:&Node, logger:&Logger) {
|
||||
let warn_msg : &str =
|
||||
&format!("Failed to insert {:?} before {:?} in {:?}",node,ref_node,self);
|
||||
if self.insert_before(node, Some(ref_node)).is_err() {
|
||||
logger.warning(warn_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait used to remove `Node`s.
|
||||
pub trait NodeRemover {
|
||||
fn remove_from_parent_or_panic(&self);
|
||||
|
||||
fn remove_from_parent_or_warn(&self, logger:&Logger);
|
||||
|
||||
fn remove_child_or_panic(&self, node:&Node);
|
||||
|
||||
fn remove_child_or_warn(&self, node:&Node, logger:&Logger);
|
||||
}
|
||||
|
||||
impl NodeRemover for Node {
|
||||
fn remove_from_parent_or_panic(&self) {
|
||||
if let Some(parent) = self.parent_node() {
|
||||
let panic_msg = |_| panic!("Failed to remove {:?} from parent", self);
|
||||
parent.remove_child(self).unwrap_or_else(panic_msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_from_parent_or_warn(&self, logger:&Logger) {
|
||||
if let Some(parent) = self.parent_node() {
|
||||
let warn_msg : &str = &format!("Failed to remove {:?} from parent", self);
|
||||
if parent.remove_child(self).is_err() {
|
||||
logger.warning(warn_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_child_or_panic(&self, node:&Node) {
|
||||
let panic_msg = |_|
|
||||
panic!("Failed to remove child {:?} from {:?}",node,self);
|
||||
let panic_msg = |_| panic!("Failed to remove child {:?} from {:?}",node,self);
|
||||
self.remove_child(node).unwrap_or_else(panic_msg);
|
||||
}
|
||||
|
||||
fn remove_child_or_warn(&self, node:&Node, logger:&Logger) {
|
||||
let warn_msg : &str = &format!("Failed to remove child {:?} from {:?}",node,self);
|
||||
if self.remove_child(node).is_err() {
|
||||
logger.warning(warn_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inline_js = "export function request_animation_frame2(f) { requestAnimationFrame(f) }")]
|
||||
|
@ -30,12 +30,12 @@ impl BenchContainer {
|
||||
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_style_or_panic("margin" , "0px 2px");
|
||||
div.set_style_or_panic("height" , "24px");
|
||||
div.set_style_or_panic("bottom-border" , "1px solid black");
|
||||
div.set_style_or_panic("display" , "flex");
|
||||
div.set_style_or_panic("justify-content", "space-between");
|
||||
div.set_style_or_panic("align-items" , "center");
|
||||
|
||||
div.set_inner_html("<div>00.00ms</div>\
|
||||
<div>0 iterations</div>\
|
||||
@ -53,7 +53,7 @@ impl BenchContainer {
|
||||
let header_height = 17.0;
|
||||
let height = format!("{}px", height + header_height + 25.0);
|
||||
|
||||
container.div.set_property_or_panic("height", height);
|
||||
container.div.set_style_or_panic("height", height);
|
||||
container.div.insert_before_or_panic(&div, &container.container);
|
||||
|
||||
let measurement = div;
|
||||
|
@ -29,25 +29,25 @@ impl Container {
|
||||
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");
|
||||
div.set_style_or_panic("width" , &width);
|
||||
div.set_style_or_panic("height" , format!("{}px", height + 17.0));
|
||||
div.set_style_or_panic("border" , "1px solid black");
|
||||
div.set_style_or_panic("position", "relative");
|
||||
div.set_style_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");
|
||||
header.set_style_or_panic("width" , &width);
|
||||
header.set_style_or_panic("height", format!("{}px", 16.0));
|
||||
header.set_style_or_panic("border-bottom", "1px solid black");
|
||||
header.set_style_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_style_or_panic("width" , width);
|
||||
container.set_style_or_panic("height", format!("{}px", height));
|
||||
container.set_attribute_or_panic("id", name);
|
||||
container.set_property_or_panic("position", "relative");
|
||||
container.set_style_or_panic("position", "relative");
|
||||
|
||||
div.append_or_panic(&container);
|
||||
|
||||
|
@ -31,10 +31,10 @@ impl Group {
|
||||
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");
|
||||
div.set_style_or_panic ("display" , "flex");
|
||||
div.set_style_or_panic ("flex-wrap" , "wrap");
|
||||
div.set_style_or_panic ("border" , "1px solid black");
|
||||
div.set_style_or_panic ("margin-bottom", "10px");
|
||||
|
||||
let header = create_element("center");
|
||||
let header = header.expect("TestGroup failed to create header");
|
||||
@ -43,8 +43,8 @@ impl Group {
|
||||
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%");
|
||||
header.set_style_or_panic("border-bottom", border);
|
||||
header.set_style_or_panic("width" , "100%");
|
||||
div.append_or_panic(&header);
|
||||
|
||||
let document = document().expect("Document is not present");
|
||||
|
Loading…
Reference in New Issue
Block a user